/// <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); } }
/// <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); }
/// <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); }