public void Example() { this.Name = "Elements_Topography"; // <example> // Read topo elevations from a file. var data = JsonConvert.DeserializeObject <Dictionary <string, double[]> >(File.ReadAllText("./elevations.json")); var elevations = data["points"]; var tileSize = WebMercatorProjection.GetTileSizeMeters(15); // Create a topography. var topo = new Topography(Vector3.Origin, tileSize, elevations); // </example> this.Model.AddElement(topo); }
/// <summary> /// Scales a tile /// </summary> /// <param name="tileData">The tile to scale</param> /// <param name="zoomLevelDifference">The difference between the downloaded tile and the rqeuested tile zoom levels</param> /// <param name="ptHigherTile"></param> /// <param name="ptRequestedWorld"></param> /// <param name="numTilesAtFoundZoomLevel">The number of tiles for the higher zoom level</param> /// <returns>A scaled tile</returns> private byte[] ScaleTile(byte[] tileData, int zoomLevelDifference, Point ptHigherTile, Point ptRequestedWorld, int numTilesAtFoundZoomLevel) { // Calculate the tile coords of the BR corner of the higher zoom level tile // so that we can identify which sub-part of it to crop and scale Point ptHigherTileTopLeft = ptHigherTile; Point ptHigherTileBotRight = new Point(ptHigherTile.y + 1, ptHigherTile.x + 1); // Calculate the sub-tile rectangle that we need to crop out of the higher tile // using a simple proportion calculation based on which part of the higher tile // covers the original requested tile in world coordinates var ptHigherWorldTopLeft = WebMercatorProjection.PixelToWorld(WebMercatorProjection.TileToPixel(ptHigherTileTopLeft), numTilesAtFoundZoomLevel); var ptHigherWorldBotRight = WebMercatorProjection.PixelToWorld(WebMercatorProjection.TileToPixel(ptHigherTileBotRight), numTilesAtFoundZoomLevel); var ratioX = (ptRequestedWorld.x - ptHigherWorldTopLeft.x) / (ptHigherWorldBotRight.x - ptHigherWorldTopLeft.x); var ratioY = (ptRequestedWorld.y - ptHigherWorldTopLeft.y) / (ptHigherWorldBotRight.y - ptHigherWorldTopLeft.y); var startX = (int)Math.Floor(WebMercatorProjection.TILE_SIZE * ratioX); var startY = (int)Math.Floor(WebMercatorProjection.TILE_SIZE * ratioY); // Calculate how much up-scaling of higher level zoom tile we need to do // based on the difference between the requested and higher zoom levels var croppedTileSize = WebMercatorProjection.TILE_SIZE / (1 << zoomLevelDifference); // Set the crop rectangle and draw it into a new bitmap, scaling it up to the standard tile size var cropRect = new Rectangle(startX, startY, croppedTileSize, croppedTileSize); log.LogDebug("DxfTileExecutor: crop rectangle x = {0}, y = {1}, size = {2}", startX, startY, croppedTileSize); //source bitmap using (var tileStream = new MemoryStream(tileData)) using (var higherBitmap = Image.Load(tileStream)) { //destination bitmap var target = higherBitmap.Clone(ctx => ctx.Crop(cropRect).Resize(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)); return(target.BitmapToByteArray()); } }
public virtual MapProjection GetProjection(string crsId) { MapProjection projection = null; switch (crsId) { case WorldMercatorProjection.DefaultCrsId: projection = new WorldMercatorProjection(); break; case WebMercatorProjection.DefaultCrsId: projection = new WebMercatorProjection(); break; case EquirectangularProjection.DefaultCrsId: projection = new EquirectangularProjection(); break; case OrthographicProjection.DefaultCrsId: projection = new OrthographicProjection(); break; case AutoEquirectangularProjection.DefaultCrsId: projection = new AutoEquirectangularProjection(); break; case GnomonicProjection.DefaultCrsId: projection = new GnomonicProjection(); break; case StereographicProjection.DefaultCrsId: projection = new StereographicProjection(); break; case "EPSG:97003": // proprietary CRS ID projection = new AzimuthalEquidistantProjection(crsId); break; default: break; } return(projection); }
public void MapboxTopography() { this.Name = "MapboxTopography"; // 0 1 // 2 3 var maps = new[] { "./Topography/Texture_f7c3dc2f-c47c-4638-a962-53ae31719cf5_0.jpg", "./Topography/Texture_df332cc3-62b0-42ac-9041-942e1fd985aa_1.jpg", "./Topography/Texture_12454f24-690a-43e2-826d-e4deae5eb82e_2.jpg", "./Topography/Texture_aa1b1148-0563-4b9d-b1c0-7138024dc974_3.jpg" }; var topos = new[] { "./Topography/Topo_52f0cdf5-2d34-4e1c-927d-79fb5f2caf43_0.png", "./Topography/Topo_5f21ae5e-eeef-4346-af16-860afdc0e829_1.png", "./Topography/Topo_e056d328-5b7d-47d3-b6bc-42d6d245d8ec_2.png", "./Topography/Topo_0a91fd8a-d887-40c2-a729-aac47441f9d8_3.png" }; var tiles = new[] { new Tuple <int, int>(20, 20), new Tuple <int, int>(21, 20), new Tuple <int, int>(20, 21), new Tuple <int, int>(21, 21), }; // Web Mercator Tiling (x,y) // 0,0 1,0 // 0,1 1,1 // Image Coordinates (x,y) // 0,0 1,0 // 0,1 1,1 // Image Texture coordinates (u,v) // 0,1 1,1 // 0,0 1,0 // Mesh Coordinates // 0,1 1,1 // 0,0 1,0 var zoom = 16; var selectedOrigin = WebMercatorProjection.TileIdToCenterWebMercator(tiles[0].Item1, tiles[0].Item2, zoom); var sampleSize = 4; var topographies = new Topography[maps.Length]; for (var i = 0; i < maps.Length; i++) { using (var topo = Image.Load <Rgba32>(topos[i])) { var size = topo.Width / sampleSize; var elevationData = new double[(int)Math.Pow(size, 2)]; var idx = 0; // Read the rows in reverse order as images // start in the top left and meshes start // in the lower left. for (var y = topo.Width - 1; y >= 0; y -= sampleSize) { for (var x = 0; x < topo.Width; x += sampleSize) { var c = topo[x, y]; var height = -10000 + ((c.R * 256 * 256 + c.G * 256 + c.B) * 0.1); elevationData[idx] = height; idx++; } } var tileSize = WebMercatorProjection.GetTileSizeMeters(zoom); var origin = WebMercatorProjection.TileIdToCenterWebMercator(tiles[i].Item1, tiles[i].Item2, zoom) - new Vector3(tileSize / 2.0, tileSize / 2.0) - selectedOrigin; var material = new Material($"Topo_{i}", Colors.White, 0.0f, 0.0f, maps[i]); var topography = new Topography(origin, tileSize, elevationData, material); this.Model.AddElement(topography); topographies[i] = topography; } } // 1 2 // 2 3 topographies[0].AverageEdges(topographies[1], Units.CardinalDirection.East); topographies[0].AverageEdges(topographies[2], Units.CardinalDirection.South); topographies[1].AverageEdges(topographies[3], Units.CardinalDirection.South); topographies[2].AverageEdges(topographies[3], Units.CardinalDirection.East); }
public void MapboxTopography() { this.Name = "Topography_Mapbox"; // 0 1 // 2 3 var maps = new[] { "./Topography/Texture_f7c3dc2f-c47c-4638-a962-53ae31719cf5_0.jpg", "./Topography/Texture_df332cc3-62b0-42ac-9041-942e1fd985aa_1.jpg", "./Topography/Texture_12454f24-690a-43e2-826d-e4deae5eb82e_2.jpg", "./Topography/Texture_aa1b1148-0563-4b9d-b1c0-7138024dc974_3.jpg" }; var topos = new[] { "./Topography/Topo_52f0cdf5-2d34-4e1c-927d-79fb5f2caf43_0.png", "./Topography/Topo_5f21ae5e-eeef-4346-af16-860afdc0e829_1.png", "./Topography/Topo_e056d328-5b7d-47d3-b6bc-42d6d245d8ec_2.png", "./Topography/Topo_0a91fd8a-d887-40c2-a729-aac47441f9d8_3.png" }; var tiles = new[] { new Tuple <int, int>(20, 20), new Tuple <int, int>(21, 20), new Tuple <int, int>(20, 21), new Tuple <int, int>(21, 21), }; // Web Mercator Tiling (x,y) // 0,0 1,0 // 0,1 1,1 // Image Coordinates (x,y) // 0,0 1,0 // 0,1 1,1 // Image Texture coordinates (u,v) // 0,1 1,1 // 0,0 1,0 // Mesh Coordinates // 0,1 1,1 // 0,0 1,0 var zoom = 16; var selectedOrigin = WebMercatorProjection.TileIdToCenterWebMercator(tiles[0].Item1, tiles[0].Item2, zoom); var sampleSize = 8; var topoImage = new Image <Rgba32>(512 * 2, 512 * 2); var mapImage = new Image <Rgba32>(1024 * 2, 1024 * 2); using (var map0 = Image.Load <Rgba32>(maps[0])) using (var map1 = Image.Load <Rgba32>(maps[1])) using (var map2 = Image.Load <Rgba32>(maps[2])) using (var map3 = Image.Load <Rgba32>(maps[3])) using (var topo0 = Image.Load <Rgba32>(topos[0])) using (var topo1 = Image.Load <Rgba32>(topos[1])) using (var topo2 = Image.Load <Rgba32>(topos[2])) using (var topo3 = Image.Load <Rgba32>(topos[3])) { topoImage.Mutate(o => o .DrawImage(topo0, new Point(0, 0), 1.0f) .DrawImage(topo1, new Point(512, 0), 1.0f) .DrawImage(topo2, new Point(0, 512), 1.0f) .DrawImage(topo3, new Point(512, 512), 1.0f)); mapImage.Mutate(o => o .DrawImage(map0, new Point(0, 0), 1.0f) .DrawImage(map1, new Point(1024, 0), 1.0f) .DrawImage(map2, new Point(0, 1024), 1.0f) .DrawImage(map3, new Point(1024, 1024), 1.0f)); var mapImagePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.png"); mapImage.Save(mapImagePath); var size = topoImage.Width / sampleSize; var elevationData = new double[(int)Math.Pow(size, 2)]; var idx = 0; // Read the rows in reverse order as images // start in the top left and meshes start // in the lower left. for (var y = topoImage.Width - 1; y >= 0; y -= sampleSize) { if (y - sampleSize < 0) { y = 0; } for (var x = 0; x < topoImage.Width; x += sampleSize) { if (x + sampleSize > topoImage.Width - 1) { x = topoImage.Width - 1; } var color = topoImage[x, y]; var height = -10000 + ((color.R * 256 * 256 + color.G * 256 + color.B) * 0.1); elevationData[idx] = height; idx++; } } var tileSize = WebMercatorProjection.GetTileSizeMeters(0, zoom); var a = WebMercatorProjection.TileIdToCenterWebMercator(tiles[0].Item1, tiles[0].Item2, zoom); var b = WebMercatorProjection.TileIdToCenterWebMercator(tiles[1].Item1, tiles[1].Item2, zoom); var c = WebMercatorProjection.TileIdToCenterWebMercator(tiles[2].Item1, tiles[2].Item2, zoom); var d = WebMercatorProjection.TileIdToCenterWebMercator(tiles[3].Item1, tiles[3].Item2, zoom); var material = new Material($"Topo", Colors.White, 0.0f, 0.0f, mapImagePath); var topography = new Topography(new[] { a, b, c, d }.Average() - selectedOrigin, tileSize * 2, elevationData, material); this.Model.AddElement(topography); } }
/// <summary> /// Converts the lat/lng point to pixels /// </summary> /// <param name="latitude">The latitude to convert in radians</param> /// <param name="longitude">The longitude to convert in radians</param> /// <param name="numTiles">The number of tiles</param> /// <returns>Pixel point</returns> public static Point LatLngToPixel(double latitude, double longitude, long numTiles) { var point = new Point(latitude.LatRadiansToDegrees(), longitude.LonRadiansToDegrees()); return(WebMercatorProjection.LatLngToPixel(point, numTiles)); }
/// <summary> /// Adjust the bounding box to fit the requested tile size. /// </summary> /// <param name="parameters">The map parameters icluding the bounding box.</param> public void AdjustBoundingBoxToFit(MapParameters parameters) { log.LogInformation($"AdjustBoundingBoxToFit: requestedWidth={parameters.mapWidth}, requestedHeight={parameters.mapHeight}, bbox={parameters.bbox}"); TryZoomIn(parameters, out int requiredWidth, out int requiredHeight, out var pixelMin, out var pixelMax); bool adjust = false; const double MARGIN_FACTOR = 1.1; //10% margin var adjustedRequiredWidth = parameters.addMargin ? (int)Math.Round(requiredWidth * MARGIN_FACTOR) : requiredWidth; var adjustedRequiredHeight = parameters.addMargin ? (int)Math.Round(requiredHeight * MARGIN_FACTOR) : requiredHeight; //Is it bigger or smaller than the requested size? if (adjustedRequiredWidth > parameters.mapWidth || adjustedRequiredHeight > parameters.mapHeight) { log.LogDebug("AdjustBoundingBoxToFit: scaling down"); //We'll make the bounding box bigger in the same shape as the requested tile and scale down once the tile has been drawn. adjust = true; //Need to maintain aspect ratio. Figure out the ratio. double ratioX = (double)requiredWidth / (double)parameters.mapWidth; double ratioY = (double)requiredHeight / (double)parameters.mapHeight; // use whichever multiplier is bigger double ratio = ratioX > ratioY ? ratioX : ratioY; // now we can get the new height and width var factor = parameters.addMargin ? MARGIN_FACTOR : 1.0; int newHeight = Convert.ToInt32(parameters.mapHeight * ratio * factor); int newWidth = Convert.ToInt32(parameters.mapWidth * ratio * factor); var xDiff = Math.Abs(newWidth - requiredWidth) / 2; var yDiff = Math.Abs(newHeight - requiredHeight) / 2; //Pixel origin is top left pixelMin.x -= xDiff; pixelMax.x += xDiff; pixelMin.y += yDiff; pixelMax.y -= yDiff; //Adjust the tile width & height parameters.mapWidth = (int)Math.Abs(pixelMax.x - pixelMin.x); parameters.mapHeight = (int)Math.Abs(pixelMax.y - pixelMin.y); } else { log.LogDebug("AdjustBoundingBoxToFit: expanding to fill tile "); //Expand the bounding box to fill the requested tile size if (adjustedRequiredWidth < parameters.mapWidth) { double scaleWidth = (double)parameters.mapWidth / adjustedRequiredWidth; adjustedRequiredWidth = (int)(scaleWidth * adjustedRequiredWidth); double pixelCenterX = pixelMin.x + (pixelMax.x - pixelMin.x) / 2.0; //Pixel origin is top left pixelMin.x = pixelCenterX - adjustedRequiredWidth / 2.0; pixelMax.x = pixelCenterX + adjustedRequiredWidth / 2.0; adjust = true; } if (adjustedRequiredHeight < parameters.mapHeight) { double scaleHeight = (double)parameters.mapHeight / adjustedRequiredHeight; adjustedRequiredHeight = (int)(scaleHeight * adjustedRequiredHeight); double pixelCenterY = pixelMin.y + (pixelMax.y - pixelMin.y) / 2.0; //Pixel origin is top left pixelMin.y = pixelCenterY + adjustedRequiredHeight / 2.0; pixelMax.y = pixelCenterY - adjustedRequiredHeight / 2.0; adjust = true; } } if (adjust) { //Convert the adjusted bbox to lat/lng var minLatLngDegrees = WebMercatorProjection.PixelToLatLng(pixelMin, parameters.numTiles); var maxLatLngDegrees = WebMercatorProjection.PixelToLatLng(pixelMax, parameters.numTiles); parameters.bbox.minLat = minLatLngDegrees.Latitude.LatDegreesToRadians(); parameters.bbox.maxLat = maxLatLngDegrees.Latitude.LatDegreesToRadians(); parameters.bbox.minLng = minLatLngDegrees.Longitude.LonDegreesToRadians(); parameters.bbox.maxLng = maxLatLngDegrees.Longitude.LonDegreesToRadians(); } log.LogInformation($"AdjustBoundingBoxToFit: returning mapWidth={parameters.mapWidth}, mapHeight={parameters.mapHeight}, bbox={parameters.bbox}"); }
public Vertex ProjectToGeographic() { this.Northing = WebMercatorProjection.yToLat(this.Northing); this.Easting = WebMercatorProjection.xToLon(this.Easting); return(this); }
protected override async Task <ContractExecutionResult> ProcessAsyncEx <T>(T item) { List <FileData> files = null; int zoomLevel = 0; Point topLeftTile = null; int numTiles = 0; BoundingBox2DLatLon bbox = null; if (item is DxfTileRequest request) { files = request.files?.ToList(); bbox = request.bbox; //Calculate zoom level zoomLevel = TileServiceUtils.CalculateZoomLevel(request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon); log.LogDebug("DxfTileExecutor: BBOX differences {0} {1} {2}", request.bbox.TopRightLat - request.bbox.BottomLeftLat, request.bbox.TopRightLon - request.bbox.BottomLeftLon, zoomLevel); numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); Point topLeftLatLng = new Point(request.bbox.TopRightLat.LatRadiansToDegrees(), request.bbox.BottomLeftLon.LonRadiansToDegrees()); topLeftTile = WebMercatorProjection.LatLngToTile(topLeftLatLng, numTiles); log.LogDebug($"DxfTileExecutor: zoomLevel={zoomLevel}, numTiles={numTiles}, xtile={topLeftTile.x}, ytile={topLeftTile.y}"); } else if (item is DxfTile3dRequest request3d) { files = request3d.files?.ToList(); zoomLevel = request3d.zoomLevel; numTiles = TileServiceUtils.NumberOfTiles(zoomLevel); topLeftTile = new Point { x = request3d.xTile, y = request3d.yTile }; } else { ThrowRequestTypeCastException <DxfTileRequest>(); } log.LogDebug($"DxfTileExecutor: {files?.Count ?? 0} files"); //Short circuit overlaying if there no files to overlay as ForAll is an expensive operation if (files == null || !files.Any()) { byte[] emptyOverlayData; using (var bitmap = new Image <Rgba32>(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE)) { emptyOverlayData = bitmap.BitmapToByteArray(); } return(new TileResult(emptyOverlayData)); } log.LogDebug(string.Join(",", files.Select(f => f.Name).ToList())); var tileList = new List <byte[]>(); const string DATA_OCEAN_ROOT_FOLDER_ID_KEY = "DATA_OCEAN_ROOT_FOLDER_ID"; var dataOceanRootFolder = configStore.GetValueString(DATA_OCEAN_ROOT_FOLDER_ID_KEY); if (string.IsNullOrEmpty(dataOceanRootFolder)) { throw new ArgumentException($"Missing environment variable {DATA_OCEAN_ROOT_FOLDER_ID_KEY}"); } //For GeoTIFF files, use the latest version of a file var geoTiffFiles = files.Where(x => x.ImportedFileType == ImportedFileType.GeoTiff).ToList(); if (geoTiffFiles.Any()) { //Find any with multiple versions and remove old ones from the list var latestFiles = geoTiffFiles.GroupBy(g => g.Name).Select(g => g.OrderBy(o => o.SurveyedUtc).Last()).ToList(); foreach (var geoTiffFile in geoTiffFiles) { if (!latestFiles.Contains(geoTiffFile)) { files.Remove(geoTiffFile); } } } var fileTasks = files.Select(async file => { //foreach (var file in request.files) //Check file type to see if it has tiles if (file.ImportedFileType == ImportedFileType.Linework || file.ImportedFileType == ImportedFileType.GeoTiff) { var fullPath = DataOceanFileUtil.DataOceanPath(dataOceanRootFolder, file.CustomerUid, file.ProjectUid); var fileName = DataOceanFileUtil.DataOceanFileName(file.Name, file.ImportedFileType == ImportedFileType.SurveyedSurface || file.ImportedFileType == ImportedFileType.GeoTiff, Guid.Parse(file.ImportedFileUid), file.SurveyedUtc); fileName = DataOceanFileUtil.GeneratedFileName(fileName, file.ImportedFileType); if (zoomLevel >= file.MinZoomLevel) { byte[] tileData = null; if (zoomLevel <= file.MaxZoomLevel || file.MaxZoomLevel == 0) //0 means not calculated { tileData = await GetTileAtRequestedZoom(topLeftTile, zoomLevel, fullPath, fileName); } else if (zoomLevel - file.MaxZoomLevel <= 5) //Don't try to scale if the difference is too excessive { tileData = await GetTileAtHigherZoom(topLeftTile, zoomLevel, fullPath, fileName, file.MaxZoomLevel, numTiles); } else { log.LogDebug( "DxfTileExecutor: difference between requested and maximum zooms too large; not even going to try to scale tile"); } if (tileData != null && tileData.Length > 0) { tileList.Add(tileData); } } } }); await Task.WhenAll(fileTasks); log.LogDebug($"DxfTileExecutor: Overlaying {tileList.Count} tiles"); byte[] overlayData = TileOverlay.OverlayTiles(tileList); return(new TileResult(overlayData)); }