예제 #1
0
        public void CanConvertLatLngToPixelOffset()
        {
            List <WGSPoint> latLngs = new List <WGSPoint>
            {
                new WGSPoint(36.210.LatDegreesToRadians(), -115.025.LonDegreesToRadians()),
                new WGSPoint(36.205.LatDegreesToRadians(), -115.029.LonDegreesToRadians()),
                new WGSPoint(36.200.LatDegreesToRadians(), -115.018.LonDegreesToRadians())
            };
            var topLeft     = new Point(100, 250);
            var pixelPoints = TileServiceUtils.LatLngToPixelOffset(latLngs, topLeft, 32768);

            var expectedPoints = new PointF[3]
            {
                new PointF {
                    X = 1513777.25F, Y = 3287930F
                },
                new PointF {
                    X = 1513684F, Y = 3288074.5F
                },
                new PointF {
                    X = 1513940.38F, Y = 3288218.75F
                },
            };

            for (int i = 0; i < 3; i++)
            {
                Assert.Equal(expectedPoints[i], pixelPoints[i]);
            }
        }
예제 #2
0
        /// <summary>
        /// Gets a tile at a higher zoom level and scales part of it for the requested tile
        /// </summary>
        /// <param name="topLeftTile">The top left tile coordinates</param>
        /// <param name="zoomLevel">The requested zoom level</param>
        /// <param name="path">The file path</param>
        /// <param name="generatedName">The name of the DXF file (for design and alignment files it is generated)</param>
        /// <param name="filespaceId">The filespace ID</param>
        /// <param name="maxZoomLevel">The maximum zoom level for which tiles have been generated</param>
        /// <param name="numTiles">The number of tiles for the requested zoom level</param>
        /// <returns>A scaled tile</returns>
        private async Task <byte[]> GetTileAtHigherZoom(Point topLeftTile, int zoomLevel, string path,
                                                        string generatedName, string filespaceId, int maxZoomLevel, int numTiles)
        {
            int zoomLevelFound = maxZoomLevel;

            // Calculate the tile coords of the higher zoom level tile that covers the requested tile
            Point ptRequestedTile  = new Point(topLeftTile.y, topLeftTile.x);
            Point ptRequestedPixel = WebMercatorProjection.TileToPixel(ptRequestedTile);

            int   numTilesAtRequestedZoomLevel = numTiles;
            Point ptRequestedWorld             =
                WebMercatorProjection.PixelToWorld(ptRequestedPixel, numTilesAtRequestedZoomLevel);

            int   numTilesAtFoundZoomLevel = TileServiceUtils.NumberOfTiles(maxZoomLevel);
            Point ptHigherPixel            = WebMercatorProjection.WorldToPixel(ptRequestedWorld, numTilesAtFoundZoomLevel);

            Point ptHigherTile = WebMercatorProjection.PixelToTile(ptHigherPixel);
            //Note PixelToTile uses Math.Floor so this tile coordinate will be the top left of the tile

            // With the new tile coords of the higher zoom level tile, see if it exists on TCC
            string fullHigherTileName =
                $"{FileUtils.ZoomPath(FileUtils.TilePath(path, generatedName), zoomLevelFound)}/{ptHigherTile.y}/{ptHigherTile.x}.png";

            log.LogDebug("DxfTileExecutor: looking for higher tile {0}", fullHigherTileName);

            byte[] tileData = await DownloadTile(filespaceId, fullHigherTileName, "higher");

            if (tileData != null)
            {
                tileData = ScaleTile(tileData, zoomLevel - zoomLevelFound, ptHigherTile, ptRequestedWorld,
                                     numTilesAtFoundZoomLevel);
            }
            return(tileData);
        }
예제 #3
0
        /// <summary>
        /// Gets a map tile with the project boundary drawn on it.
        /// </summary>
        /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param>
        /// <param name="project">The project to draw the boundary for</param>
        /// <returns>A bitmap</returns>
        public byte[] GetProjectBitmap(MapParameters parameters, ProjectData project)
        {
            log.LogInformation($"GetProjectBitmap: project {project.ProjectUID}");

            const int PROJECT_BOUNDARY_COLOR = 0x0080FF; //Note: packed is abgr order
            const int STROKE_TRANSPARENCY    = 0x73;     //0.45 of FF
            Rgba32    PROJECT_BOUNDARY_RGBA  = new Rgba32((uint)((STROKE_TRANSPARENCY << 24) | PROJECT_BOUNDARY_COLOR));
            const int PROJECT_OUTLINE_WIDTH  = 4;

            byte[] projectImage = null;

            if (project != null)
            {
                using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight))
                {
                    var      projectPoints = project.ProjectGeofenceWKT.GeometryToPoints();
                    PointF[] pixelPoints   = TileServiceUtils.LatLngToPixelOffset(projectPoints, parameters.pixelTopLeft, parameters.numTiles);

                    bitmap.Mutate(ctx => ctx.DrawPolygon(PROJECT_BOUNDARY_RGBA, PROJECT_OUTLINE_WIDTH, pixelPoints));

                    projectImage = bitmap.BitmapToByteArray();
                }
            }

            return(projectImage);
        }
예제 #4
0
 public void CanCalculateZoomLevel()
 {
     Assert.Equal(1, TileServiceUtils.CalculateZoomLevel(Math.PI / 2, Math.PI));
     Assert.Equal(11, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000, Math.PI / 1000));
     Assert.Equal(21, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000000, Math.PI / 1000000));
     Assert.Equal(24, TileServiceUtils.CalculateZoomLevel(Math.PI / 2000000000, Math.PI / 1000000000));
 }
예제 #5
0
        public void CanConvertLatLngToPixel(double latDegrees, double lngDegrees, float xExpected, float yExpected)
        {
            var pixelPoint = TileServiceUtils.LatLngToPixel(latDegrees.LatDegreesToRadians(), lngDegrees.LonDegreesToRadians(), 32768);

            Assert.Equal(xExpected, pixelPoint.x, 0);
            Assert.Equal(yExpected, pixelPoint.y, 0);
        }
예제 #6
0
        public void RoundingZoomLevelTest()
        {
            double diff1 = 0.000365585907798893;
            double diff2 = 0.000766990393942846;

            double diff1_1 = 0.000365339467977011;
            double diff2_1 = 0.000766990393942818;

            var res1 = TileServiceUtils.CalculateZoomLevel(diff1, diff2);
            var res2 = TileServiceUtils.CalculateZoomLevel(diff1_1, diff2_1);

            Assert.Equal(res1, res2);
            Assert.Equal(13, res1);
        }
예제 #7
0
        /// <summary>
        /// Gets a map tile with geofences drawn on it.
        /// </summary>
        /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param>
        /// <param name="sites">List of geofences for the customer</param>
        /// <returns>A bitmap</returns>
        private byte[] GetGeofencesBitmap(MapParameters parameters, IEnumerable <GeofenceData> sites, bool isSites)
        {
            log.LogInformation("GetGeofencesBitmap");

            const int DEFAULT_SITE_COLOR = 0x0055FF;

            byte[] sitesImage = null;

            if (sites != null && sites.Any())
            {
                // Exclude sites that are too small to be displayed in the current viewport.
                double viewPortArea = Math.Abs(parameters.bbox.minLatDegrees - parameters.bbox.maxLatDegrees) * Math.Abs(parameters.bbox.minLngDegrees - parameters.bbox.maxLngDegrees);
                double minArea      = viewPortArea / 10000;

                using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight))
                {
                    foreach (var site in sites)
                    {
                        log.LogDebug($"GetGeofencesBitmap examining site {site.GeofenceUID}");
                        //Old geofences may not have AreaSqMeters set.
                        if (site.AreaSqMeters > 0 && site.AreaSqMeters < minArea)
                        {
                            log.LogDebug($"GetGeofencesBitmap excluding site {site.GeofenceUID} due to area");
                            continue;
                        }

                        var sitePoints = site.GeometryWKT.GeometryToPoints().ToList();

                        //Exclude site if outside bbox
                        bool outside = TileServiceUtils.Outside(parameters.bbox, sitePoints);

                        if (outside)
                        {
                            log.LogDebug($"GetGeofencesBitmap excluding site {site.GeofenceUID} outside bbox");
                        }
                        else
                        {
                            int  siteColor   = site.FillColor > 0 ? site.FillColor : (isSites ? DEFAULT_SITE_COLOR : DEFAULT_CUSTOM_BOUNDARY_COLOR);
                            bool transparent = isSites ? site.IsTransparent : true;
                            DrawGeofence(parameters, bitmap, site.GeofenceUID.ToString(), sitePoints, siteColor, transparent);
                        }
                    }

                    sitesImage = bitmap.BitmapToByteArray();
                }
            }

            return(sitesImage);
        }
예제 #8
0
        /// <summary>
        /// Gets a map tile with load/dump locations drawn on it.
        /// </summary>
        /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param>
        /// <param name="loadDumpLocations">List of Load/Dump locations</param>
        /// <returns>A bitmap</returns>
        public byte[] GetLoadDumpBitmap(MapParameters parameters, List <LoadDumpLocation> loadDumpLocations)
        {
            log.LogInformation($"GetLoadDumpBitmap");

            //Note: packed is abgr order
            const uint LOAD_COLOR = 0xFF008F01; //Green 0x018F00
            const uint DUMP_COLOR = 0xFFFF3304; //Blue 0x0433FF
            Rgba32     LOAD_RGBA  = new Rgba32(LOAD_COLOR);
            Rgba32     DUMP_RGBA  = new Rgba32(DUMP_COLOR);
            var        loadPen    = new Pen <Rgba32>(LOAD_RGBA, 1);
            var        dumpPen    = new Pen <Rgba32>(DUMP_RGBA, 1);

            byte[] loadDumpImage = null;
            if (loadDumpLocations != null && loadDumpLocations.Any())
            {
                using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight))
                {
                    IEnumerable <WGSPoint> loads = loadDumpLocations
                                                   .Select(x => new WGSPoint(x.loadLatitude.LatDegreesToRadians(), x.loadLongitude.LonDegreesToRadians())).ToList();
                    PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(loads, parameters.pixelTopLeft, parameters.numTiles);
                    foreach (var p in pixelPoints)
                    {
                        //Coloring one pixel doesn't show up well therefore do rectangle of 4 pixels
                        var x    = (int)p.X;
                        var y    = (int)p.Y;
                        var rect = new RectangleF(x, y, 2, 2);
                        bitmap.Mutate(ctx => ctx.Draw(loadPen, rect));
                        //bitmap[(int) p.X, (int) p.Y] = LOAD_RGBA;
                    }

                    IEnumerable <WGSPoint> dumps = loadDumpLocations
                                                   .Select(x => new WGSPoint(x.dumpLatitude.LatDegreesToRadians(), x.dumpLongitude.LonDegreesToRadians())).ToList();
                    pixelPoints = TileServiceUtils.LatLngToPixelOffset(dumps, parameters.pixelTopLeft, parameters.numTiles);
                    foreach (var p in pixelPoints)
                    {
                        var x    = (int)p.X;
                        var y    = (int)p.Y;
                        var rect = new RectangleF(x, y, 2, 2);
                        bitmap.Mutate(ctx => ctx.Draw(dumpPen, rect));
                        //bitmap[(int) p.X, (int) p.Y] = DUMP_RGBA;
                    }

                    loadDumpImage = bitmap.BitmapToByteArray();
                }
            }

            return(loadDumpImage);
        }
예제 #9
0
        public void PolygonOutsideBoundingBox()
        {
            var bbox = new MapBoundingBox
            {
                minLat = 36.0, minLng = -115.9, maxLat = 36.5, maxLng = -115.0
            };
            var points = new List <WGSPoint>
            {
                new WGSPoint(35.0, -116.0),
                new WGSPoint(35.5, -116.0),
                new WGSPoint(35.5, -116.5),
                new WGSPoint(35.0, -116.5),
                new WGSPoint(35.0, -116.0),
            };

            Assert.True(TileServiceUtils.Outside(bbox, points));
        }
예제 #10
0
        public void OverlayTilesReturnsTileForEmptyList()
        {
            var mapParameters = new MapParameters {
                mapWidth = 4, mapHeight = 4
            };
            var result = TileServiceUtils.OverlayTiles(mapParameters, new Dictionary <TileOverlayType, byte[]>());

            byte[] expectedResult = null;
            using (Image <Rgba32> bitmap = new Image <Rgba32>(mapParameters.mapWidth, mapParameters.mapHeight))
            {
                expectedResult = bitmap.BitmapToByteArray();
            }

            for (int i = 0; i < expectedResult.Length; i++)
            {
                Assert.Equal(expectedResult[i], result[i]);
            }
        }
예제 #11
0
        /// <summary>
        /// Gets a single tile with various types of data overlayed on it according to what is requested.
        /// </summary>
        /// <param name="request">The tile request</param>
        /// <returns>A TileResult</returns>
        public async Task <byte[]> GetMapData(TileGenerationRequest request)
        {
            log.LogInformation("Getting map tile");
            log.LogDebug("TileGenerationRequest: " + JsonConvert.SerializeObject(request));

            log.LogDebug("Awaiting tiles to be completed");

            var overlays = await Task.WhenAll(request.overlays.Select(overlay => RequestTile(request, overlay)));

            log.LogDebug($"Tiles completed: {overlays.Count()} overlays");

            var overlayTile =
                TileServiceUtils.OverlayTiles(request.mapParameters, overlays.ToDictionary(k => k.Item1, v => v.Item2));

            log.LogDebug("Tiles overlaid");
            overlayTile = ScaleTile(request, overlayTile);
            log.LogDebug("Tiles scaled");
            return(overlayTile);
        }
예제 #12
0
        public void PolygonIntersectsBoundingBox()
        {
            var bbox = new MapBoundingBox
            {
                minLat = 36.0,
                minLng = -115.9,
                maxLat = 36.5,
                maxLng = -115.0
            };
            var points = new List <WGSPoint>
            {
                new WGSPoint(35.9, -115.5),
                new WGSPoint(36.3, -115.5),
                new WGSPoint(36.3, -115.7),
                new WGSPoint(35.9, -115.7),
                new WGSPoint(35.9, -115.5),
            };

            Assert.False(TileServiceUtils.Outside(bbox, points));
        }
예제 #13
0
        private void TryZoomIn(MapParameters parameters, out int requiredWidth, out int requiredHeight, out Point pixelMin, out Point pixelMax)
        {
            pixelMin = TileServiceUtils.LatLngToPixel(parameters.bbox.minLat, parameters.bbox.minLng, parameters.numTiles);
            pixelMax = TileServiceUtils.LatLngToPixel(parameters.bbox.maxLat, parameters.bbox.maxLng, parameters.numTiles);

            requiredWidth  = (int)Math.Abs(pixelMax.x - pixelMin.x);
            requiredHeight = (int)Math.Abs(pixelMax.y - pixelMin.y);

            //See if we can zoom in - occurs when the requested tile size is much larger than the bbox
            var   zoomedWidth    = requiredWidth;
            var   zoomedHeight   = requiredHeight;
            int   zoomLevel      = parameters.zoomLevel;
            Point zoomedPixelMin = pixelMin;
            Point zoomedPixelMax = pixelMax;
            long  numTiles       = parameters.numTiles;

            //allow a 15% margin extra otherwise if the tile is only a few pixels bigger than the calculated zoom
            //we use the smaller zoom level and end up with lots of space around the data.
            //AdjustBoundingBoxToFit handles the bigger size.
            var mapWidth  = parameters.mapWidth * 1.15;
            var mapHeight = parameters.mapHeight * 1.15;

            while (zoomedWidth < mapWidth && zoomedHeight < mapHeight && zoomLevel < MAX_ZOOM_LEVEL)
            {
                parameters.zoomLevel = zoomLevel;
                parameters.numTiles  = numTiles;
                requiredWidth        = zoomedWidth;
                requiredHeight       = zoomedHeight;
                pixelMin             = zoomedPixelMin;
                pixelMax             = zoomedPixelMax;

                zoomLevel++;
                numTiles = TileServiceUtils.NumberOfTiles(zoomLevel);

                zoomedPixelMin = TileServiceUtils.LatLngToPixel(parameters.bbox.minLat, parameters.bbox.minLng, numTiles);
                zoomedPixelMax = TileServiceUtils.LatLngToPixel(parameters.bbox.maxLat, parameters.bbox.maxLng, numTiles);

                zoomedWidth  = (int)Math.Abs(zoomedPixelMax.x - zoomedPixelMin.x);
                zoomedHeight = (int)Math.Abs(zoomedPixelMax.y - zoomedPixelMin.y);
            }
        }
예제 #14
0
        private void DrawGeofence(MapParameters parameters, Image <Rgba32> bitmap, string uid, IEnumerable <WGSPoint> points, int color, bool isTransparent)
        {
            const byte FILL_TRANSPARENCY   = 0x40; //0.25 of FF
            const byte STROKE_TRANSPARENCY = 0x73; //0.45 of FF
            const int  SITE_OUTLINE_WIDTH  = 2;

            log.LogDebug($"DrawGeofence drawing site or boundary {uid}");
            var red   = (byte)((color & 0xFF0000) >> 16);
            var green = (byte)((color & 0x00FF00) >> 8);
            var blue  = (byte)(color & 0x0000FF);

            PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(points, parameters.pixelTopLeft, parameters.numTiles);
            if (!isTransparent)
            {
                var fillColor = new Rgba32(red, green, blue, FILL_TRANSPARENCY);
                bitmap.Mutate(ctx => ctx.FillPolygon(fillColor, pixelPoints));
            }
            var lineColor = new Rgba32(red, green, blue, STROKE_TRANSPARENCY);

            bitmap.Mutate(ctx => ctx.DrawPolygon(lineColor, SITE_OUTLINE_WIDTH, pixelPoints));
        }
예제 #15
0
        /// <summary>
        /// Gets a map tile with alignment center lines drawn on it.
        /// </summary>
        /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param>
        /// <param name="projectId">Legacy project ID</param>
        /// <param name="alignmentPointsList">Points for the project's alignment files</param>
        /// <returns>A bitmap</returns>
        public byte[] GetAlignmentsBitmap(MapParameters parameters, long projectId, List <List <WGSPoint> > alignmentPointsList)
        {
            log.LogInformation($"GetAlignmentsBitmap: project {projectId}");

            byte[] alignmentsImage = null;
            if (alignmentPointsList != null && alignmentPointsList.Any())
            {
                using (Image <Rgba32> bitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight))
                {
                    foreach (var alignmentPoints in alignmentPointsList)
                    {
                        if (alignmentPoints != null && alignmentPoints.Any())
                        {
                            PointF[] pixelPoints = TileServiceUtils.LatLngToPixelOffset(alignmentPoints, parameters.pixelTopLeft, parameters.numTiles);
                            bitmap.Mutate(ctx => ctx.DrawLines(Rgba32.Red, 1, pixelPoints));
                        }
                    }
                    alignmentsImage = bitmap.BitmapToByteArray();
                }
            }
            return(alignmentsImage);
        }
예제 #16
0
        /// <summary>
        /// Gets a map tile with DXF linework on it.
        /// </summary>
        /// <param name="parameters">Map parameters such as bounding box, tile size, zoom level etc.</param>
        /// <param name="dxfFiles">The list of DXF files to overlay tiles for</param>
        /// <returns>A bitmap</returns>
        public async Task <byte[]> GetDxfBitmap(MapParameters parameters, IEnumerable <FileData> dxfFiles)
        {
            log.LogInformation("GetDxfBitmap");

            byte[] overlayData = null;

            if (dxfFiles != null && dxfFiles.Any())
            {
                List <byte[]> tileList = new List <byte[]>();
                foreach (var dxfFile in dxfFiles)
                {
                    if (dxfFile.ImportedFileType == ImportedFileType.Linework)
                    {
                        tileList.Add(await JoinDxfTiles(parameters, dxfFile));
                    }
                }

                log.LogDebug("Overlaying DXF bitmaps");
                overlayData = TileServiceUtils.OverlayTiles(parameters, tileList);
            }

            return(overlayData);
        }
예제 #17
0
        /// <summary>
        /// Get the map parameters for the report tile
        /// </summary>
        public MapParameters GetMapParameters(string bbox, int width, int height, bool addMargin, bool adjustBoundingBox)
        {
            log.LogDebug($"GetMapParameters: bbox={bbox}, width={width}, height={height}, addMargin={addMargin}, adjustBoundingBox={adjustBoundingBox}");

            var            bboxRadians = boundingBoxHelper.GetBoundingBox(bbox);
            MapBoundingBox mapBox      = new MapBoundingBox
            {
                minLat = bboxRadians.BottomLeftLat,
                minLng = bboxRadians.BottomLeftLon,
                maxLat = bboxRadians.TopRightLat,
                maxLng = bboxRadians.TopRightLon
            };

            int  zoomLevel = TileServiceUtils.CalculateZoomLevel(mapBox.maxLat - mapBox.minLat, mapBox.maxLng - mapBox.minLng);
            long numTiles  = TileServiceUtils.NumberOfTiles(zoomLevel);

            MapParameters parameters = new MapParameters
            {
                bbox      = mapBox,
                zoomLevel = zoomLevel,
                numTiles  = numTiles,
                mapWidth  = width,
                mapHeight = height,
                addMargin = addMargin
            };

            if (adjustBoundingBox)
            {
                boundingBoxService.AdjustBoundingBoxToFit(parameters);
            }

            parameters.pixelTopLeft = TileServiceUtils.LatLngToPixel(mapBox.maxLat, mapBox.minLng, parameters.numTiles);
            log.LogDebug("MapParameters: " + JsonConvert.SerializeObject(parameters));

            return(parameters);
        }
예제 #18
0
        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));
        }
예제 #19
0
        /// <summary>
        /// Joins standard size DXF tiles together to form one large tile for the report
        /// </summary>
        private async Task <byte[]> JoinDxfTiles(MapParameters parameters, FileData dxfFile)
        {
            log.LogDebug($"JoinDxfTiles: {dxfFile.ImportedFileUid}, {dxfFile.Name}");

            //Find the tiles that the bounding box fits into.
            MasterDataModels.Point tileTopLeft      = MasterDataModels.WebMercatorProjection.PixelToTile(parameters.pixelTopLeft);
            MasterDataModels.Point pixelBottomRight = TileServiceUtils.LatLngToPixel(
                parameters.bbox.minLat, parameters.bbox.maxLng, parameters.numTiles);
            MasterDataModels.Point tileBottomRight = MasterDataModels.WebMercatorProjection.PixelToTile(pixelBottomRight);

            int xnumTiles = (int)(tileBottomRight.x - tileTopLeft.x) + 1;
            int ynumTiles = (int)(tileBottomRight.y - tileTopLeft.y) + 1;
            int width     = xnumTiles * MasterDataModels.WebMercatorProjection.TILE_SIZE;
            int height    = ynumTiles * MasterDataModels.WebMercatorProjection.TILE_SIZE;

            using (Image <Rgba32> tileBitmap = new Image <Rgba32>(width, height))
            {
                //Find the offset of the bounding box top left point inside the top left tile
                var point = new MasterDataModels.Point
                {
                    x = tileTopLeft.x * MasterDataModels.WebMercatorProjection.TILE_SIZE,
                    y = tileTopLeft.y * MasterDataModels.WebMercatorProjection.TILE_SIZE
                };
                //Clip to the actual bounding box within the tiles.
                int clipWidth    = parameters.mapWidth;
                int clipHeight   = parameters.mapHeight;
                int xClipTopLeft = (int)(parameters.pixelTopLeft.x - point.x);
                int yClipTopLeft = (int)(parameters.pixelTopLeft.y - point.y);
                //Unlike System.Drawing, which allows the clipRect to have negative x, y and which moves as well as clips when used with DrawImage
                //as the source rectangle, ImageSharp does not respect negative values. So we will have to do extra work in this situation.
                if (xClipTopLeft < 0)
                {
                    clipWidth   += xClipTopLeft;
                    xClipTopLeft = 0;
                }
                if (yClipTopLeft < 0)
                {
                    clipHeight  += yClipTopLeft;
                    yClipTopLeft = 0;
                }
                var clipRect = new Rectangle(xClipTopLeft, yClipTopLeft, clipWidth, clipHeight);

                //Join all the DXF tiles into one large tile
                await JoinDataOceanTiles(dxfFile, tileTopLeft, tileBottomRight, tileBitmap, parameters.zoomLevel);

                //Now clip the large tile
                tileBitmap.Mutate(ctx => ctx.Crop(clipRect));
                if (clipWidth >= parameters.mapWidth && clipHeight >= parameters.mapHeight)
                {
                    return(tileBitmap.BitmapToByteArray());
                }

                //and resize it if required tile area was overlapping rather than within the large tile
                //(negative clip values above)
                using (Image <Rgba32> resizedBitmap = new Image <Rgba32>(parameters.mapWidth, parameters.mapHeight))
                {
                    Point offset = new Point(parameters.mapWidth - clipWidth, parameters.mapHeight - clipHeight);
                    resizedBitmap.Mutate(ctx => ctx.DrawImage(tileBitmap, PixelBlenderMode.Normal, 1f, offset));
                    return(resizedBitmap.BitmapToByteArray());
                }
            }
        }
예제 #20
0
        protected override async Task <ContractExecutionResult> ProcessAsyncEx <T>(T item)
        {
            var request = item as DxfTileRequest;

            if (request == null)
            {
                ThrowRequestTypeCastException <DxfTileRequest>();
            }

            string filespaceId = FileDescriptorExtensions.GetFileSpaceId(configStore, log);

            //Calculate zoom level
            int 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);
            int   numTiles      = TileServiceUtils.NumberOfTiles(zoomLevel);
            Point topLeftLatLng = new Point(request.bbox.TopRightLat.LatRadiansToDegrees(),
                                            request.bbox.BottomLeftLon.LonRadiansToDegrees());
            Point topLeftTile = WebMercatorProjection.LatLngToTile(topLeftLatLng, numTiles);

            log.LogDebug("DxfTileExecutor: zoomLevel={0}, numTiles={1}, xtile={2}, ytile={3}", zoomLevel, numTiles,
                         topLeftTile.x, topLeftTile.y);

            log.LogDebug("DxfTileExecutor: {0} files", request.files.Count());

            //Short circuit overlaying if there no files to overlay as ForAll is an expensive operation
            if (!request.files.Any())
            {
                byte[] emptyOverlayData = null;
                using (Bitmap bitmap = new Bitmap(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE))
                {
                    emptyOverlayData = bitmap.BitmapToByteArray();
                }
                return(new TileResult(emptyOverlayData));
            }

            log.LogDebug(string.Join(",", request.files.Select(f => f.Name).ToList()));

            List <byte[]> tileList = new List <byte[]>();

            var fileTasks = request.files.Select(async file =>
            {
                //foreach (var file in request.files)
                //Check file type to see if it has tiles
                if (file.ImportedFileType == ImportedFileType.Alignment ||
                    file.ImportedFileType == ImportedFileType.DesignSurface ||
                    file.ImportedFileType == ImportedFileType.Linework)
                {
                    if (zoomLevel >= file.MinZoomLevel)
                    {
                        var suffix           = FileUtils.GeneratedFileSuffix(file.ImportedFileType);
                        string generatedName = FileUtils.GeneratedFileName(file.Name, suffix, FileUtils.DXF_FILE_EXTENSION);
                        byte[] tileData      = null;
                        if (zoomLevel <= file.MaxZoomLevel || file.MaxZoomLevel == 0) //0 means not calculated
                        {
                            tileData = await GetTileAtRequestedZoom(topLeftTile, zoomLevel, file.Path, generatedName, filespaceId);
                        }
                        else if (zoomLevel - file.MaxZoomLevel <= 5) //Don't try to scale if the difference is too excessive
                        {
                            tileData = await GetTileAtHigherZoom(topLeftTile, zoomLevel, file.Path, generatedName, filespaceId,
                                                                 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)
                        {
                            tileList.Add(tileData);
                        }
                    }
                }
            });

            await Task.WhenAll(fileTasks);

            //Overlay the tiles. Return an empty tile if none to overlay.
            log.LogDebug("DxfTileExecutor: Overlaying {0} tiles", tileList.Count);
            byte[] overlayData          = null;
            System.Drawing.Point origin = new System.Drawing.Point(0, 0);
            using (Bitmap bitmap = new Bitmap(WebMercatorProjection.TILE_SIZE, WebMercatorProjection.TILE_SIZE))
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    foreach (byte[] tileData in tileList)
                    {
                        using (var tileStream = new MemoryStream(tileData))
                        {
                            Image image = Image.FromStream(tileStream);
                            g.DrawImage(image, origin);
                        }
                    }
                    overlayData = bitmap.BitmapToByteArray();
                }

            return(new TileResult(overlayData));
        }
예제 #21
0
 public void CanCalculateNumberOfTiles()
 {
     Assert.Equal(1024, TileServiceUtils.NumberOfTiles(10));
 }