/// <summary> /// Setup GridSize for tile /// </summary> private void Initialise() { QMTileResponse.ResultStatus = RequestErrorStatus.FailedToBuildQuantizedMeshTile; ResultStatus = RequestErrorStatus.FailedToBuildQuantizedMeshTile; // Determine the QM tile resolution by the zoom level if (OverrideGridSize != QMConstants.NoGridSize) { TileGridSize = OverrideGridSize; } else { if (TileZ >= QMConstants.HighResolutionLevel) { TileGridSize = QMConstants.HighResolutionGridSize; } else if (TileZ >= QMConstants.MidResolutionLevel) { TileGridSize = QMConstants.MidResolutionGridSize; } else { TileGridSize = QMConstants.FlatResolutionGridSize; } } // Setup for return. In most cases you want to at least return an empty tile ElevData = new ElevationData(LowestElevation, TileGridSize); // elevation grid }
/// <summary> /// Creates a demo tile. Useful for development /// </summary> /// <returns></returns> private bool BuildDemoTile() { _log.LogDebug($"#Tile.({TileX},{TileY}) Returning demo tile. (X:{TileX}, Y:{TileX},{TileY}, Z:{TileZ}), GridSize{QMConstants.DemoResolutionGridSize}"); // Even empty tiles must have header info correctly calculated if (ElevData.GridSize == QMConstants.NoGridSize) { ElevData = new ElevationData(LowestElevation, QMConstants.DemoResolutionGridSize); // elevation grid } ElevData.MakeDemoTile(TileBoundaryLL); QMTileBuilder tileBuilder = new QMTileBuilder() { TileData = ElevData, GridSize = ElevData.GridSize }; if (!tileBuilder.BuildQuantizedMeshTile()) { _log.LogError($"Tile.({TileX},{TileY}) failed to build demo tile. Error code: {tileBuilder.BuildTileFaultCode}"); return(false); } QMTileResponse.ResultStatus = RequestErrorStatus.OK; QMTileResponse.data = tileBuilder.QuantizedMeshTile; // return QM tile in response ResultStatus = RequestErrorStatus.OK; return(true); }
/// <summary> /// No data so return an empty tile /// </summary> /// <returns></returns> private bool BuildEmptyTile() { _log.LogDebug($"#Tile#.({TileX},{TileY}) Execute End. Returning empty tile. Zoom:{TileZ}, GridSize{QMConstants.FlatResolutionGridSize}"); // Even empty tiles must have header info correctly calculated if (ElevData.GridSize == QMConstants.NoGridSize) { ElevData = new ElevationData(LowestElevation, QMConstants.FlatResolutionGridSize); // elevation grid } ElevData.MakeEmptyTile(TileBoundaryLL, HasLighting); if (ElevData.HasLighting) { ComputeNormalMap(); } QMTileBuilder tileBuilder = new QMTileBuilder() { TileData = ElevData, GridSize = ElevData.GridSize }; if (!tileBuilder.BuildQuantizedMeshTile()) { _log.LogError($"Tile.({TileX},{TileY}) failed to build empty tile. Error code: {tileBuilder.BuildTileFaultCode}"); return(false); } QMTileResponse.ResultStatus = RequestErrorStatus.OK; QMTileResponse.data = tileBuilder.QuantizedMeshTile; // return QM tile in response ResultStatus = RequestErrorStatus.OK; return(true); }
public override void InternalFromBinary(IBinaryRawReader reader) { var version = VersionSerializationHelper.CheckVersionByte(reader, VERSION_NUMBER); if (version == 1) { ResultStatus = (RequestErrorStatus)reader.ReadInt(); } }
protected ServiceException CreateServiceException <T>(RequestErrorStatus resultStatus = RequestErrorStatus.OK) { var errorMessage = string.Format(ERROR_MESSAGE_PRODUCTION_DATA, typeof(T).Name); if (resultStatus != RequestErrorStatus.OK) { errorMessage = string.Format(ERROR_MESSAGE_EX, errorMessage, ContractExecutionStates.FirstNameWithOffset((int)resultStatus)); } return(new ServiceException(HttpStatusCode.BadRequest, new ContractExecutionResult(ContractExecutionStatesEnum.FailedToGetResults, errorMessage))); }
/// <summary> /// These root tiles are static /// </summary> /// <returns></returns> private void MakeRootTile() { _log.LogDebug($"#Tile.({TileX},{TileY}) Returning root tile"); QMTileResponse.ResultStatus = RequestErrorStatus.OK; if (TileY == 0) { QMTileResponse.data = QMConstants.Terrain0; } else { QMTileResponse.data = QMConstants.Terrain1; } ResultStatus = RequestErrorStatus.OK; }
/// <summary> /// Generate quantized mesh tile from the supplied grid /// </summary> /// <returns></returns> public bool BuildQuantizedMeshTile() { try { ComputeHeaderInfo(); // Turn grid into a quantized mesh var vertices = MeshBuilder.MakeQuantizedMesh(ref TileData); var tileHeader = new TerrainTileHeader() { MaximumHeight = TileData.MaximumHeight, MinimumHeight = TileData.MinimumHeight, CenterX = TileData.CenterX, CenterY = TileData.CenterY, CenterZ = TileData.CenterZ, BoundingSphereCenterX = TileData.BoundingSphereCenterX, BoundingSphereCenterY = TileData.BoundingSphereCenterY, BoundingSphereCenterZ = TileData.BoundingSphereCenterZ, BoundingSphereRadius = TileData.BoundingSphereRadius, HorizonOcclusionPointX = TileData.HorizonOcclusionPointX, HorizonOcclusionPointY = TileData.HorizonOcclusionPointY, HorizonOcclusionPointZ = TileData.HorizonOcclusionPointZ }; // This class constructs a tile from the computed mesh var tileBuilder = new TileBuilder(); var tile = tileBuilder.MakeTile(vertices, ref TileData.VertexNormals, tileHeader, MapUtils.GridSizeToTriangleCount(TileData.GridSize), TileData.GridSize, TileData.HasLighting); QuantizedMeshTile = CompressTile ? MapUtils.Compress(tile) : tile; BuildTileFaultCode = RequestErrorStatus.OK; } catch (Exception E) { Log.LogError(E, "BuildQuantizedMeshTile: 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); }
protected ServiceException CreateServiceException <T>(HttpStatusCode statusCode, int contractExecutionStatesEnum, RequestErrorStatus resultStatus = RequestErrorStatus.OK, string detailedMessage = null) { var errorMessage = string.Format(ERROR_MESSAGE, typeof(T).Name); if (resultStatus != RequestErrorStatus.OK) { errorMessage = string.Format(ERROR_MESSAGE_EX, errorMessage, ContractExecutionStates.FirstNameWithOffset((int)resultStatus)); } if (!string.IsNullOrEmpty(detailedMessage)) { errorMessage += $" ({detailedMessage})"; } return(new ServiceException(statusCode, new ContractExecutionResult(contractExecutionStatesEnum, errorMessage))); }
/// <summary> /// Executor that implements requesting and rendering grid information to create the grid rows /// </summary> /// <returns></returns> public async Task <bool> ExecuteAsync() { // Get the lat lon boundary from xyz tile request TileBoundaryLL = MapGeo.TileXYZToRectLL(TileX, TileY, TileZ, out var yFlip); _log.LogInformation($"#Tile#.({TileX},{TileY}) Execute Start. DMode:{DisplayMode}, HasLighting:{HasLighting}, Zoom:{TileZ} FlipY:{yFlip}. TileBoundary:{TileBoundaryLL.ToDisplay()}, DataModel:{DataModelUid}"); if (TileZ == 0) // Send back default root tile { MakeRootTile(); return(true); } SiteModel = DIContext.Obtain <ISiteModels>().GetSiteModel(DataModelUid); if (SiteModel == null) { ResultStatus = RequestErrorStatus.NoSuchDataModel; _log.LogError($"Tile.({TileX},{TileY}) Failed to obtain site model for {DataModelUid}"); return(false); } var siteModelExtentWithSurveyedSurfaces = SiteModel.GetAdjustedDataModelSpatialExtents(null); _log.LogDebug($"Tile.({TileX},{TileY}) Site model extents are {siteModelExtentWithSurveyedSurfaces}. TileBoundary:{TileBoundaryLL.ToDisplay()}"); if (!siteModelExtentWithSurveyedSurfaces.IsValidPlanExtent) // No data return empty tile { return(BuildEmptyTile()); } // We will draw all missing data just below lowest elevation for site LowestElevation = (float)siteModelExtentWithSurveyedSurfaces.MinZ - 1F; Initialise(); // setup tile requirements if (TileGridSize == QMConstants.FlatResolutionGridSize) // Too far out to see detail so return empty tile { return(BuildEmptyTile()); } if (DisplayMode == QMConstants.DisplayModeDemo) // development use only { return(BuildDemoTile()); } try { var setupResult = await SetupPipelineTask(siteModelExtentWithSurveyedSurfaces, SiteModel.CellSize); if (!setupResult) { if (ResultStatus != RequestErrorStatus.InvalidCoordinateRange) { _log.LogError($"Tile.({TileX},{TileY}) Unable to setup pipelinetask."); } return(BuildEmptyTile()); } processor.Process(); if (GriddedElevationsResponse.ResultStatus != RequestErrorStatus.OK) { _log.LogError($"Tile.({TileX},{TileY}) Unable to obtain data for gridded data. GriddedElevationRequestResponse: {GriddedElevationsResponse.ResultStatus.ToString()}"); return(BuildEmptyTile()); } ElevData.HasData = !float.IsPositiveInfinity(task.MinElevation); // check for data // Developer Debugging Only if (ElevData.HasData) { OutputDebugTile("Raw"); } if (!ElevData.HasData) { return(BuildEmptyTile()); } _log.LogInformation($"#Tile#.({TileX},{TileY}) Data successfully sampled. GridSize:{TileGridSize} Min:{task.MinElevation}, Max:{task.MaxElevation} FirstElev:{GriddedElevDataArray[0, 0].Elevation}, LastElev:{GriddedElevDataArray[TileGridSize - 1, TileGridSize - 1].Elevation}"); ElevData.HasLighting = HasLighting; // Transform gridded data into a format the mesh builder can use ConvertGridToDEM(task.MinElevation, task.MaxElevation); // Build a quantized mesh from sampled elevations QMTileBuilder tileBuilder = new QMTileBuilder() { TileData = ElevData, GridSize = TileGridSize }; _log.LogInformation($"Tile.({TileX},{TileY}) BuildQuantizedMeshTile. GridSize:{TileGridSize} Min:{ElevData.MinimumHeight}, Max:{ElevData.MaximumHeight}"); if (!tileBuilder.BuildQuantizedMeshTile()) { _log.LogError($"Tile.({TileX},{TileY}) BuildQuantizedMeshTile returned false with error code: {tileBuilder.BuildTileFaultCode}"); return(false); } QMTileResponse.data = tileBuilder.QuantizedMeshTile; // Make tile from mesh ResultStatus = RequestErrorStatus.OK; QMTileResponse.ResultStatus = ResultStatus; _log.LogDebug($"#Tile#.({TileX},{TileY}) Execute End. Returning production tile. CesiumY:{yFlip}, Zoom:{TileZ}, GridSize:{TileGridSize}"); return(true); } catch (Exception ex) { _log.LogError(ex, $"#Tile#.({TileX},{TileY}). Exception building QuantizedMesh tile: "); return(false); } }
/// <summary> /// Setup pipeline for tile request /// </summary> /// <param name="siteModelExtent">Site Model Extent</param> /// <param name="cellSize">Cell Size</param> /// <returns></returns> private async Task <bool> SetupPipelineTask(BoundingWorldExtent3D siteModelExtent, double cellSize) { var requestDescriptor = Guid.NewGuid(); if (DisplayMode == QMConstants.DisplayModeStandard) { // Note coords are always supplied lat long if (SiteModel.CSIB() == string.Empty) { ResultStatus = RequestErrorStatus.EmptyCoordinateSystem; _log.LogError($"Failed to obtain site model coordinate system CSIB file for Project:{DataModelUid}"); return(false); } } LLHCoords = new[] { new XYZ(MapUtils.Deg2Rad(TileBoundaryLL.West), MapUtils.Deg2Rad(TileBoundaryLL.South), 0), new XYZ(MapUtils.Deg2Rad(TileBoundaryLL.East), MapUtils.Deg2Rad(TileBoundaryLL.North), 0), new XYZ(MapUtils.Deg2Rad(TileBoundaryLL.West), MapUtils.Deg2Rad(TileBoundaryLL.North), 0), new XYZ(MapUtils.Deg2Rad(TileBoundaryLL.East), MapUtils.Deg2Rad(TileBoundaryLL.South), 0) }; // This will change in Part3 once development is complete var strCSIB = DisplayMode == QMConstants.DisplayModeStandard ? SiteModel.CSIB() : DIMENSIONS_2012_DC_CSIB; var NEECoords = DIContext.Obtain <ICoreXWrapper>().LLHToNEE(strCSIB, LLHCoords.ToCoreX_XYZ(), CoreX.Types.InputAs.Radians).ToTRex_XYZ(); GridIntervalX = (NEECoords[1].X - NEECoords[0].X) / (TileGridSize - 1); GridIntervalY = (NEECoords[1].Y - NEECoords[0].Y) / (TileGridSize - 1); _log.LogDebug($"#Tile#.({TileX},{TileY}) TileInfo: Zoom:{TileZ}, TileSizeXY:{Math.Round(NEECoords[1].X - NEECoords[0].X, 3)}m x {Math.Round(NEECoords[2].Y - NEECoords[0].Y, 3)}m, GridInterval(m) X:{Math.Round(GridIntervalX, 3)}, Y:{Math.Round(GridIntervalY, 3)}, GridSize:{TileGridSize}"); var WorldTileHeight = MathUtilities.Hypot(NEECoords[0].X - NEECoords[2].X, NEECoords[0].Y - NEECoords[2].Y); var WorldTileWidth = MathUtilities.Hypot(NEECoords[0].X - NEECoords[3].X, NEECoords[0].Y - NEECoords[3].Y); double dx = NEECoords[2].X - NEECoords[0].X; CenterX = NEECoords[2].X + dx / 2; double dy = NEECoords[2].Y - NEECoords[0].Y; CenterY = NEECoords[0].Y + dy / 2; // 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; SetRotation(TileRotation); _log.LogDebug($"QMTile render executing across tile: [Rotation:{ MathUtilities.RadiansToDegrees(TileRotation)}] " + $" [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}"); RotatedTileBoundingExtents.SetInverted(); foreach (var xyz in NEECoords) { RotatedTileBoundingExtents.Include(xyz.X, xyz.Y); } // Intersect the site model extents with the extents requested by the caller _log.LogDebug($"Tile.({TileX},{TileY}) Calculating intersection of bounding box and site model {DataModelUid}:{siteModelExtent}"); var dataSelectionExtent = new BoundingWorldExtent3D(RotatedTileBoundingExtents); dataSelectionExtent.Intersect(siteModelExtent); if (!dataSelectionExtent.IsValidPlanExtent) { ResultStatus = RequestErrorStatus.InvalidCoordinateRange; _log.LogInformation($"Tile.({TileX},{TileY}) Site model extents {siteModelExtent}, do not intersect RotatedTileBoundingExtents {RotatedTileBoundingExtents}"); return(false); } // 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, cellSize, SubGridTreeConsts.DefaultIndexOriginOffset, out var CellExtents_MinX, out var CellExtents_MinY); SubGridTree.CalculateIndexOfCellContainingPosition(dataSelectionExtent.MaxX, dataSelectionExtent.MaxY, 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); // Setup Task task = DIContext.Obtain <Func <PipelineProcessorTaskStyle, ITRexTask> >()(PipelineProcessorTaskStyle.QuantizedMesh) as QuantizedMeshTask; processor = DIContext.Obtain <IPipelineProcessorFactory>().NewInstanceNoBuild <SubGridsRequestArgument>(requestDescriptor: requestDescriptor, dataModelID: DataModelUid, gridDataType: GridDataType.Height, response: GriddedElevationsResponse, filters: Filters, cutFillDesign: new DesignOffset(), task: task, pipeline: DIContext.Obtain <Func <PipelineProcessorPipelineStyle, ISubGridPipelineBase> >()(PipelineProcessorPipelineStyle.DefaultProgressive), requestAnalyser: DIContext.Obtain <IRequestAnalyser>(), requireSurveyedSurfaceInformation: true, //Rendering.Utilities.DisplayModeRequireSurveyedSurfaceInformation(DisplayMode.Height) && Rendering.Utilities.FilterRequireSurveyedSurfaceInformation(Filters), requestRequiresAccessToDesignFileExistenceMap: false, overrideSpatialCellRestriction: CellExtents, liftParams: LiftParams ); // Set the grid TRexTask parameters for progressive processing processor.Task.RequestDescriptor = requestDescriptor; processor.Task.TRexNodeID = RequestingTRexNodeID; processor.Task.GridDataType = GridDataType.Height; // 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); // Setup new grid array for results GriddedElevDataArray = new GriddedElevDataRow[TileGridSize, TileGridSize]; // build up a data sample grid from SW to NE for (int y = 0; y < TileGridSize; y++) { for (int x = 0; x < TileGridSize; x++) { var x1 = NEECoords[0].X + (GridIntervalX * x); var y1 = NEECoords[0].Y + (GridIntervalY * y); if (Rotating) { Rotate_point(x1, y1, out x1, out y1); } GriddedElevDataArray[x, y].Easting = x1; GriddedElevDataArray[x, y].Northing = y1; GriddedElevDataArray[x, y].Elevation = CellPassConsts.NullHeight; } } _log.LogDebug($"Tile.({TileX},{TileY}) Boundary grid coords:{string.Concat(NEECoords)}"); _log.LogDebug($"Tile.({TileX},{TileY}) First Easting:{GriddedElevDataArray[0, 0].Easting} Northing:{GriddedElevDataArray[0, 0].Northing}"); _log.LogDebug($"Tile.({TileX},{TileY}) Last Easting:{GriddedElevDataArray[TileGridSize - 1, TileGridSize - 1].Easting} Northing:{GriddedElevDataArray[TileGridSize - 1, TileGridSize - 1].Northing}"); // point to container for results task.GriddedElevDataArray = GriddedElevDataArray; task.GridIntervalX = GridIntervalX; task.GridIntervalY = GridIntervalY; task.GridSize = TileGridSize; // Tile boundary task.TileMinX = NEECoords[0].X; task.TileMinY = NEECoords[0].Y; task.TileMaxX = NEECoords[1].X; task.TileMaxY = NEECoords[1].Y; task.LowestElevation = LowestElevation; Azimuth = 0; StartNorthing = NEECoords[0].Y; StartEasting = NEECoords[0].X; // Commented out for purposes of demo until relationship between TRex mediated skip/step selection and the quantised mesh tile vertex based selection are better understood // processor.Pipeline.AreaControlSet = // new AreaControlSet(false, GridIntervalX, GridIntervalY, StartEasting, StartNorthing, Azimuth); if (!processor.Build()) { _log.LogError($"Tile.({TileX},{TileY}) Failed to build pipeline processor for request to model {DataModelUid}"); return(false); } return(true); }