Beispiel #1
0
        /// <summary>
        /// Executor that implements requesting and rendering sub grid information to create the rendered tile
        /// </summary>
        /// <returns></returns>
        public async Task <bool> ExecuteAsync()
        {
            Log.LogInformation($"Performing Execute for DataModel:{DataModelID}, Mode={Mode}, RequestingNodeID={RequestingTRexNodeID}");

            ApplicationServiceRequestStatistics.Instance.NumSubgridPageRequests.Increment();

            Guid RequestDescriptor = Guid.NewGuid();

            using (var processor = DIContext.Obtain <IPipelineProcessorFactory>().NewInstanceNoBuild <SubGridsRequestArgument>(
                       RequestDescriptor,
                       DataModelID,
                       // Patch requests always want time with height information
                       Mode == DisplayMode.Height ? GridDataType.HeightAndTime : GridDataFromModeConverter.Convert(Mode),
                       PatchSubGridsResponse,
                       Filters,
                       CutFillDesign,
                       DIContext.Obtain <Func <PipelineProcessorTaskStyle, ITRexTask> >()(PipelineProcessorTaskStyle.PatchExport),
                       DIContext.Obtain <Func <PipelineProcessorPipelineStyle, ISubGridPipelineBase> >()(PipelineProcessorPipelineStyle.DefaultProgressive),
                       DIContext.Obtain <IRequestAnalyser>(),
                       Rendering.Utilities.DisplayModeRequireSurveyedSurfaceInformation(Mode) &&
                       Rendering.Utilities.FilterRequireSurveyedSurfaceInformation(Filters),
                       Rendering.Utilities.RequestRequiresAccessToDesignFileExistenceMap(Mode, CutFillDesign),
                       BoundingIntegerExtent2D.Inverted(),
                       LiftParams))
            {
                // Set the surface TRexTask parameters for progressive processing
                processor.Task.TRexNodeID = RequestingTRexNodeID;

                // Configure the request analyser to return a single page of results.
                processor.RequestAnalyser.SinglePageRequestNumber    = DataPatchPageNumber;
                processor.RequestAnalyser.SinglePageRequestSize      = DataPatchPageSize;
                processor.RequestAnalyser.SubmitSinglePageOfRequests = true;

                if (!processor.Build())
                {
                    Log.LogError($"Failed to build pipeline processor for request to model {DataModelID}");
                    return(false);
                }

                // If this is the first page requested then count the total number of patches required for all sub grids to be returned
                if (DataPatchPageNumber == 0)
                {
                    PatchSubGridsResponse.TotalNumberOfPagesToCoverFilteredData =
                        (int)Math.Truncate(Math.Ceiling(processor.RequestAnalyser.CountOfSubGridsThatWillBeSubmitted() / (double)DataPatchPageSize));
                }

                processor.Process();

                if (PatchSubGridsResponse.ResultStatus == RequestErrorStatus.OK)
                {
                    PatchSubGridsResponse.SubGrids = ((PatchTask)processor.Task).PatchSubGrids;
                }

                return(true);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Executor that implements creation of the TIN surface
        /// </summary>
        public async Task <bool> ExecuteAsync()
        {
            Log.LogInformation($"Performing Execute for DataModel:{_dataModelId}");

            try
            {
                var requestDescriptor = Guid.NewGuid();
                var siteModel         = DIContext.Obtain <ISiteModels>().GetSiteModel(_dataModelId);

                if (siteModel == null)
                {
                    Log.LogError($"Failed to obtain site model for {_dataModelId}");
                    return(false);
                }

                var datastore = new GenericSubGridTree <float, GenericLeafSubGrid <float> >(siteModel.Grid.NumLevels, siteModel.CellSize);

                // Provide the processor with a customised request analyser configured to return a set of sub grids. These sub grids
                // are the feed stock for the generated TIN surface
                using (var processor = DIContext.Obtain <IPipelineProcessorFactory>().NewInstanceNoBuild <SubGridsRequestArgument>(
                           requestDescriptor,
                           _dataModelId,
                           GridDataFromModeConverter.Convert(DisplayMode.Height),
                           SurfaceSubGridsResponse,
                           _filters,
                           new DesignOffset(),
                           DIContext.Obtain <Func <PipelineProcessorTaskStyle, ITRexTask> >()(PipelineProcessorTaskStyle.SurfaceExport),
                           DIContext.Obtain <Func <PipelineProcessorPipelineStyle, ISubGridPipelineBase> >()(PipelineProcessorPipelineStyle.DefaultProgressive),
                           DIContext.Obtain <IRequestAnalyser>(),
                           Rendering.Utilities.DisplayModeRequireSurveyedSurfaceInformation(DisplayMode.Height) && Rendering.Utilities.FilterRequireSurveyedSurfaceInformation(_filters),
                           false, //Rendering.Utilities.RequestRequiresAccessToDesignFileExistenceMap(DisplayMode.Height),
                           BoundingIntegerExtent2D.Inverted(),
                           _liftParams))
                {
                    // Set the surface TRexTask parameters for progressive processing
                    processor.Task.TRexNodeID = RequestingTRexNodeID;

                    if (!processor.Build())
                    {
                        Log.LogError($"Failed to build pipeline processor for request to model {_dataModelId}");
                        return(false);
                    }

                    processor.Process();

                    if (SurfaceSubGridsResponse.ResultStatus != RequestErrorStatus.OK)
                    {
                        Log.LogError($"Sub grids response status not OK: {SurfaceSubGridsResponse.ResultStatus}");
                        return(false);
                    }

                    // Create the TIN decimator and populate it with the retrieved sub grids
                    foreach (var subGrid in ((SurfaceTask)processor.Task).SurfaceSubgrids)
                    {
                        if (!(datastore.ConstructPathToCell(subGrid.OriginX, subGrid.OriginY, SubGridPathConstructionType.CreatePathToLeaf) is INodeSubGrid newGridNode))
                        {
                            Log.LogError($"Result from data store ConstructPathToCell({subGrid.OriginX}, {subGrid.OriginY}) was null. Aborting...");
                            return(false);
                        }

                        subGrid.Owner = datastore;
                        newGridNode.GetSubGridCellIndex(subGrid.OriginX, subGrid.OriginY, out var subGridIndexX, out var subGridIndexY);
                        newGridNode.SetSubGrid(subGridIndexX, subGridIndexY, subGrid);
                    }
                }

                // Obtain the surface export data smoother and apply it to the tree of queried data before passing it to the decimation engine
                var dataSmoother = DIContext.Obtain <Func <DataSmootherOperation, IDataSmoother> >()(DataSmootherOperation.SurfaceExport) as ITreeDataSmoother <float>;

                datastore = dataSmoother?.Smooth(datastore) ?? datastore;

                var extents = DataStoreExtents(datastore);

                // Make sure we don't export too large an area due to data way outside project extents
                if (extents.Area > Common.Consts.MaxExportAreaM2)
                {
                    // First try and use project boundary extents as our data boundary
                    var canExport = siteModel.SiteModelExtent.Area > 0 && siteModel.SiteModelExtent.Area < Common.Consts.MaxExportAreaM2;
                    if (canExport)
                    {
                        // still use min max height extents
                        Log.LogInformation($"Invalid Plan Extent. Data area too large {extents.Area}. Switching to project extents");
                        extents.MinX = siteModel.SiteModelExtent.MinX;
                        extents.MinY = siteModel.SiteModelExtent.MinY;
                        extents.MaxX = siteModel.SiteModelExtent.MaxX;
                        extents.MaxY = siteModel.SiteModelExtent.MaxY;
                    }
                    else
                    {
                        Log.LogError($"Invalid Plan Extent. Data area too large {extents.Area}.");
                        return(false);
                    }
                }

                // Decimate the elevations into a grid
                var decimator = new GridToTINDecimator(datastore)
                {
                    Tolerance = _tolerance
                };
                decimator.SetDecimationExtents(extents);

                if (!decimator.BuildMesh())
                {
                    Log.LogError($"Decimator returned false with error code: {decimator.BuildMeshFaultCode}");
                    return(false);
                }

                // A decimated TIN has been successfully constructed...  Return it!
                SurfaceSubGridsResponse.TIN = decimator.GetTIN();
            }
            catch (Exception e)
            {
                Log.LogError(e, "ExecutePipeline raised Exception:");
                return(false);
            }

            return(true);
        }
Beispiel #3
0
        /// <summary>
        /// Executor that implements requesting and rendering sub grid information to create the rendered tile
        /// </summary>
        public async Task <SKBitmap> ExecuteAsync()
        {
            // WorkingColorPalette  : TICDisplayPaletteBase;

            _log.LogInformation($"Performing Execute for DataModel:{DataModelID}, Mode={Mode}");

            ApplicationServiceRequestStatistics.Instance.NumMapTileRequests.Increment();

            /*
             * if Assigned(ASNodeImplInstance.RequestCancellations) and
             * ASNodeImplInstance.RequestCancellations.IsRequestCancelled(FExternalDescriptor) then
             * begin
             * if ...SvcLocations.Debug_LogDebugRequestCancellationToFile then
             *   SIGLogMessage.PublishNoODS(Self, 'Request cancelled: ' + FExternalDescriptor.ToString, ...Debug);
             *
             * ResultStatus  = ...RequestHasBeenCancelled;
             * InterlockedIncrement64(ASNodeRequestStats.NumMapTileRequestsCancelled);
             * Exit;
             * end;
             *
             * // The governor is intended to restrict the numbers of heavy weight processes
             * // such as pipelines that interact with the PC layer to request sub grids
             * ScheduledWithGovernor  = ASNodeImplInstance.Governor.Schedule(FExternalDescriptor, Self, gqWMS, ResultStatus);
             * if not ScheduledWithGovernor then
             * Exit;
             */

            var RequestDescriptor = Guid.NewGuid();

            if (_log.IsDebugEnabled())
            {
                if (CoordsAreGrid)
                {
                    _log.LogDebug($"RenderPlanViewTiles Execute: Performing render for request={RequestDescriptor} Args: Project={DataModelID}, Mode={Mode}, CutFillDesign=''{CutFillDesign}'' " +
                                  $"Bound[BL/TR:X/Y]=({BLPoint.X} {BLPoint.Y}, {TRPoint.X} {TRPoint.Y}), Width={NPixelsX}, Height={NPixelsY}");
                }
                else
                {
                    _log.LogDebug($"RenderPlanViewTiles Execute: Performing render for request={RequestDescriptor} Args: Project={DataModelID}, Mode={Mode}, CutFillDesign=''{CutFillDesign}'' " +
                                  $"Bound[BL/TR:Lon/Lat]=({BLPoint.X} {BLPoint.Y}, {TRPoint.X} {TRPoint.Y}), Width={NPixelsX}, Height={NPixelsY}");
                }

                // Include the details of the filters with the logged tile parameters
                if (Filters != null)
                {
                    for (var i = 0; i < Filters.Filters.Length; i++)
                    {
                        _log.LogDebug($"Filter({i}): {Filters.Filters[i]}");
                    }
                }
            }

            // Determine the grid (NEE) coordinates of the bottom/left, top/right WGS-84 positions
            // given the project's coordinate system. If there is no coordinate system then exit.

            var SiteModel = DIContext.Obtain <ISiteModels>().GetSiteModel(DataModelID);

            if (SiteModel == null)
            {
                _log.LogWarning($"Failed to locate site model {DataModelID}");
                return(null);
            }

            _log.LogInformation($"Got Site model {DataModelID}, production data extents are {SiteModel.SiteModelExtent}");

            LLHCoords = new[]
            {
                new XYZ(BLPoint.X, BLPoint.Y, 0),
                new XYZ(TRPoint.X, TRPoint.Y, 0),
                new XYZ(BLPoint.X, TRPoint.Y, 0),
                new XYZ(TRPoint.X, BLPoint.Y, 0)
            };
            _log.LogInformation($"LLHCoords for tile request {string.Concat(LLHCoords)}, CoordsAreGrid {CoordsAreGrid}");

            if (CoordsAreGrid)
            {
                NEECoords = LLHCoords;
            }
            else
            {
                NEECoords = DIContext
                            .Obtain <ICoreXWrapper>()
                            .LLHToNEE(SiteModel.CSIB(), LLHCoords.ToCoreX_XYZ(), CoreX.Types.InputAs.Radians)
                            .ToTRex_XYZ();
            }
            _log.LogInformation($"After conversion NEECoords are {string.Concat(NEECoords)}");

            WorldTileHeight = MathUtilities.Hypot(NEECoords[0].X - NEECoords[2].X, NEECoords[0].Y - NEECoords[2].Y);
            WorldTileWidth  = MathUtilities.Hypot(NEECoords[0].X - NEECoords[3].X, NEECoords[0].Y - NEECoords[3].Y);

            var dx = NEECoords[2].X - NEECoords[0].X;
            var dy = NEECoords[2].Y - NEECoords[0].Y;

            // Calculate the tile rotation as the mathematical angle turned from 0 (due east) to the vector defined by dy/dx
            TileRotation = Math.Atan2(dy, dx);

            // Convert TileRotation to represent the angular deviation rather than a bearing
            TileRotation = (Math.PI / 2) - TileRotation;

            RotatedTileBoundingExtents.SetInverted();
            NEECoords.ForEach(xyz => RotatedTileBoundingExtents.Include(xyz.X, xyz.Y));

            _log.LogInformation($"Tile render executing across tile: [Rotation:{TileRotation}, {MathUtilities.RadiansToDegrees(TileRotation)} degrees] " +
                                $" [BL:{NEECoords[0].X}, {NEECoords[0].Y}, TL:{NEECoords[2].X},{NEECoords[2].Y}, " +
                                $"TR:{NEECoords[1].X}, {NEECoords[1].Y}, BR:{NEECoords[3].X}, {NEECoords[3].Y}] " +
                                $"World Width, Height: {WorldTileWidth}, {WorldTileHeight}, Rotated bounding extents: {RotatedTileBoundingExtents}");

            // Construct the renderer, configure it, and set it on its way
            //  WorkingColorPalette = Nil;

            using (var Renderer = new PlanViewTileRenderer())
            {
                try
                {
                    // Intersect the site model extents with the extents requested by the caller
                    var adjustedSiteModelExtents = SiteModel.GetAdjustedDataModelSpatialExtents(null);

                    _log.LogInformation($"Calculating intersection of bounding box and site model {DataModelID}:{adjustedSiteModelExtents}");

                    var dataSelectionExtent = new BoundingWorldExtent3D(RotatedTileBoundingExtents);
                    dataSelectionExtent.Intersect(adjustedSiteModelExtents);
                    if (!dataSelectionExtent.IsValidPlanExtent)
                    {
                        ResultStatus = RequestErrorStatus.InvalidCoordinateRange;
                        _log.LogInformation($"Site model extents {adjustedSiteModelExtents}, do not intersect RotatedTileBoundingExtents {RotatedTileBoundingExtents}");

                        using var mapView = new MapSurface();

                        mapView.SetBounds(NPixelsX, NPixelsY);

                        var canvas = mapView.BitmapCanvas;
                        mapView.BitmapCanvas = null;

                        return(canvas);
                    }

                    // Compute the override cell boundary to be used when processing cells in the sub grids
                    // selected as a part of this pipeline
                    // Increase cell boundary by one cell to allow for cells on the boundary that cross the boundary

                    SubGridTree.CalculateIndexOfCellContainingPosition(dataSelectionExtent.MinX,
                                                                       dataSelectionExtent.MinY, SiteModel.CellSize, SubGridTreeConsts.DefaultIndexOriginOffset,
                                                                       out var CellExtents_MinX, out var CellExtents_MinY);
                    SubGridTree.CalculateIndexOfCellContainingPosition(dataSelectionExtent.MaxX,
                                                                       dataSelectionExtent.MaxY, SiteModel.CellSize, SubGridTreeConsts.DefaultIndexOriginOffset,
                                                                       out var CellExtents_MaxX, out var CellExtents_MaxY);

                    var CellExtents = new BoundingIntegerExtent2D(CellExtents_MinX, CellExtents_MinY, CellExtents_MaxX, CellExtents_MaxY);
                    CellExtents.Expand(1);

                    var filterSet = FilterUtilities.ConstructFilters(Filters, VolumeType);
                    // Construct PipelineProcessor
                    using var processor = DIContext.Obtain <IPipelineProcessorFactory>().NewInstanceNoBuild <SubGridsRequestArgument>(
                              RequestDescriptor,
                              DataModelID,
                              GridDataFromModeConverter.Convert(Mode),
                              new SubGridsPipelinedResponseBase(),
                              filterSet,
                              CutFillDesign,
                              DIContext.Obtain <Func <PipelineProcessorTaskStyle, ITRexTask> >()(PipelineProcessorTaskStyle.PVMRendering),
                              DIContext.Obtain <Func <PipelineProcessorPipelineStyle, ISubGridPipelineBase> >()(PipelineProcessorPipelineStyle.DefaultProgressive),
                              DIContext.Obtain <IRequestAnalyser>(),
                              Utilities.DisplayModeRequireSurveyedSurfaceInformation(Mode) && Utilities.FilterRequireSurveyedSurfaceInformation(Filters),
                              requestRequiresAccessToDesignFileExistenceMap: Utilities.RequestRequiresAccessToDesignFileExistenceMap(Mode, CutFillDesign),
                              CellExtents,
                              LiftParams
                              );
                    if (filterSet.Filters.Length == 3)
                    {
                        var pipeline = processor.Pipeline as SubGridPipelineProgressive <SubGridsRequestArgument, SubGridRequestsResponse>;
                        pipeline.SubGridsRequestComputeStyle = SubGridsRequestComputeStyle.SimpleVolumeThreeWayCoalescing;
                    }

                    // Set the PVM rendering rexTask parameters for progressive processing
                    processor.Task.TRexNodeID = RequestingTRexNodeID;
                    ((IPVMRenderingTask)processor.Task).TileRenderer = Renderer;

                    // Set the spatial extents of the tile boundary rotated into the north reference frame of the cell coordinate system to act as
                    // a final restriction of the spatial extent used to govern data requests
                    processor.OverrideSpatialExtents.Assign(RotatedTileBoundingExtents);

                    // Prepare the processor
                    if (!processor.Build())
                    {
                        _log.LogError($"Failed to build pipeline processor for request to model {SiteModel.ID}");
                        ResultStatus = RequestErrorStatus.FailedToConfigureInternalPipeline;
                        return(null);
                    }

                    // Test to see if the tile can be satisfied with a representational render indicating where
                    // data is but not what it is (this is useful when the zoom level is far enough away that we
                    // cannot meaningfully render the data). If the size of s sub grid is smaller than
                    // the size of a pixel in the requested tile then do this. Just check the X dimension
                    // as the data display is isotropic.
                    // TODO: Could this be done before creation of the pipeline processor?
                    if (Utilities.SubGridShouldBeRenderedAsRepresentationalDueToScale(WorldTileWidth, WorldTileHeight, NPixelsX, NPixelsY, processor.OverallExistenceMap.CellSize))
                    {
                        return(RenderTileAsRepresentationalDueToScale(processor.OverallExistenceMap)); // There is no need to do anything else
                    }

                    /* TODO - Create a scaled palette to use when rendering the data
                     * // Create a scaled palette to use when rendering the data
                     * if not CreateAndInitialiseWorkingColorPalette then
                     *  begin
                     *  SIGLogMessage.PublishNoODS(Self, Format('Failed to create and initialise working color palette for data: %s in datamodel %d', [TypInfo.GetEnumName(TypeInfo(TICDisplayMode), Ord(FMode)), FDataModelID]), ...Warning);
                     *  Exit;
                     *  end;
                     */

                    // Renderer.WorkingPalette = WorkingColorPalette;

                    Renderer.IsWhollyInTermsOfGridProjection = true; // Ensure the renderer knows we are using grid projection coordinates

                    Renderer.SetBounds(RotatedTileBoundingExtents.CenterX - WorldTileWidth / 2,
                                       RotatedTileBoundingExtents.CenterY - WorldTileHeight / 2,
                                       WorldTileWidth, WorldTileHeight,
                                       NPixelsX, NPixelsY);
                    Renderer.TileRotation = TileRotation;

                    var performRenderStopWatch = Stopwatch.StartNew();
                    ResultStatus = Renderer.PerformRender(Mode, processor, ColorPalettes, Filters, LiftParams);
                    _log.LogInformation($"Renderer.PerformRender completed in {performRenderStopWatch.Elapsed}");

                    if (processor.Response.ResultStatus == RequestErrorStatus.OK)
                    {
                        var canvas = Renderer.Displayer.MapView.BitmapCanvas;
                        Renderer.Displayer.MapView.BitmapCanvas = null;
                        return(canvas);
                    }
                }
                catch (Exception e)
                {
                    _log.LogError(e, "Exception occurred");
                    ResultStatus = RequestErrorStatus.Exception;
                }
            }

            return(null);
        }