public List <string> ProcessSubGrid(ClientCellProfileLeafSubgrid lastPassSubGrid) { var rows = new List <string>(); if (RecordCountLimitReached()) { return(rows); } int runningIndexLLHCoords = 0; if (_requestArgument.CoordType == CoordType.LatLon) { _llhCoords = SetupLLPositions(_siteModel.CSIB(), lastPassSubGrid); } lastPassSubGrid.CalculateWorldOrigin(out var subGridWorldOriginX, out var subGridWorldOriginY); SubGridUtilities.SubGridDimensionalIterator((x, y) => { if (RecordCountLimitReached()) { return; } var cell = lastPassSubGrid.Cells[x, y]; if (cell.PassCount == 0) // Nothing for us to do, as cell is empty { return; } var easting = subGridWorldOriginX + (x + 0.5) * lastPassSubGrid.CellSize; var northing = subGridWorldOriginY + (y + 0.5) * lastPassSubGrid.CellSize; rows.Add(FormatADataRow(cell, easting, northing, runningIndexLLHCoords)); runningIndexLLHCoords++; _totalRowCountSoFar++; }); return(rows); }
/// <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); }