Ejemplo n.º 1
0
        /// <summary>
        /// Take a path provided by a user, draw it as a maptile. Potentially useful for exercise trackers. Resulting file must not be saved to the server as that would be user tracking.
        /// </summary>
        /// <param name="pointListAsString">a string of points separate by , and | </param>
        /// <returns>the png file with the path drawn over the mapdata in the area.</returns>
        public byte[] DrawUserPath(string pointListAsString)
        {
            //String is formatted as Lat,Lon~Lat,Lon~ repeating. Characters chosen to not be percent-encoded if submitted as part of the URL.
            //first, convert this to a list of latlon points
            string[]          pointToConvert = pointListAsString.Split("|");
            List <Coordinate> coords         = pointToConvert.Select(p => new Coordinate(double.Parse(p.Split(',')[0]), double.Parse(p.Split(',')[1]))).ToList();

            var     mapBuffer = resolutionCell8 / 2; //Leave some area around the edges of where they went.
            GeoArea mapToDraw = new GeoArea(coords.Min(c => c.Y) - mapBuffer, coords.Min(c => c.X) - mapBuffer, coords.Max(c => c.Y) + mapBuffer, coords.Max(c => c.X) + mapBuffer);

            ImageStats info = new ImageStats(mapToDraw, 1024, 1024);

            LineString line         = new LineString(coords.ToArray());
            var        drawableLine = PolygonToDrawingLine(line, mapToDraw, info.degreesPerPixelX, info.degreesPerPixelY);

            //Now, draw that path on the map.
            var places    = GetPlaces(mapToDraw);
            var baseImage = DrawAreaAtSize(info, places);

            Image <Rgba32> image       = new Image <Rgba32>(info.imageSizeX, info.imageSizeY);
            Rgba32         strokeColor = Rgba32.ParseHex("000000");

            image.Mutate(x => x.Draw(strokeColor, 4, new SixLabors.ImageSharp.Drawing.Path(drawableLine)));

            image.Mutate(x => x.Flip(FlipMode.Vertical)); //Plus codes are south-to-north, so invert the image to make it correct.
            var ms = new MemoryStream();

            image.SaveAsPng(ms);
            return(ms.ToArray());
        }
Ejemplo n.º 2
0
        private byte[] FinishSlippyMapTile(ImageStats info, List <CompletePaintOp> paintOps, string tileKey, string styleSet)
        {
            byte[] results = null;
            results = MapTiles.DrawAreaAtSize(info, paintOps);

            if (!SaveMapTiles())
            {
                var db = new PraxisContext();
                var existingResults = db.SlippyMapTiles.FirstOrDefault(mt => mt.Values == tileKey && mt.StyleSet == styleSet);
                if (existingResults == null)
                {
                    existingResults = new SlippyMapTile()
                    {
                        Values = tileKey, StyleSet = styleSet, AreaCovered = Converters.GeoAreaToPolygon(GeometrySupport.MakeBufferedGeoArea(info.area))
                    };
                    db.SlippyMapTiles.Add(existingResults);
                }

                existingResults.ExpireOn = DateTime.Now.AddYears(10);
                existingResults.TileData = results;
                existingResults.GenerationID++;
                db.SaveChanges();
            }

            return(results);
        }
Ejemplo n.º 3
0
        //Optional parameter allows you to pass in different stuff that the DB alone has, possibly for manual or one-off changes to styling
        //or other elements converted for maptile purposes.
        /// <summary>
        /// //This generic function takes the area to draw and creates an image for it. Can optionally be provided specific elements, a specific style set, and told to filter small areas out of the results.
        /// </summary>
        /// <param name="stats">Image information, including width and height.</param>
        /// <param name="drawnItems">the elments to draw</param>
        /// <param name="styleSet">the style rules to use when drawing</param>
        /// <param name="filterSmallAreas">if true, removes elements from the drawing that take up fewer than 8 pixels.</param>
        /// <returns></returns>
        public byte[] DrawAreaAtSize(ImageStats stats, List <DbTables.Place> drawnItems = null, string styleSet = "mapTiles", bool filterSmallAreas = true)
        {
            //This is the new core drawing function. Takes in an area, the items to draw, and the size of the image to draw.
            //The drawn items get their paint pulled from the TagParser's list. If I need multiple match lists, I'll need to make a way
            //to pick which list of tagparser rules to use.
            //This can work for user data by using the linked Places from the items in PlaceGameData.
            //I need a slightly different function for using AreaGameData, or another optional parameter here

            //This should just get the paint ops then call the core drawing function.
            double minimumSize = 0;

            if (filterSmallAreas)
            {
                minimumSize = stats.degreesPerPixelX * 8; //don't draw small elements. THis runs on perimeter/length
            }

            //Single points are excluded separately so that small areas or lines can still be drawn when points aren't.
            bool includePoints = true;

            if (stats.degreesPerPixelX > ConstantValues.zoom14DegPerPixelX)
            {
                includePoints = false;
            }

            if (drawnItems == null)
            {
                drawnItems = GetPlaces(stats.area, filterSize: minimumSize, includePoints: includePoints);
            }

            var paintOps = MapTileSupport.GetPaintOpsForStoredElements(drawnItems, styleSet, stats);

            return(DrawAreaAtSize(stats, paintOps));
        }
Ejemplo n.º 4
0
        private static IPen SetPenForGameTile(StylePaint tpe)
        {
            //These pens are saved with a fixed drawing width to match game tiles.
            int imgX = 0, imgY = 0;

            MapTileSupport.GetPlusCodeImagePixelSize("22334455", out imgX, out imgY);
            var info = new ImageStats(OpenLocationCode.DecodeValid("22334455"), imgX, imgY);

            var widthMod = resolutionCell11Lon * IMapTiles.GameTileScale;

            string htmlColor = tpe.HtmlColorCode;

            if (htmlColor.Length == 8)
            {
                htmlColor = htmlColor.Substring(2, 6) + htmlColor.Substring(0, 2);
            }

            Pen p;

            if (String.IsNullOrWhiteSpace(tpe.LinePattern) || tpe.LinePattern == "solid")
            {
                p = new Pen(Rgba32.ParseHex(htmlColor), tpe.LineWidthDegrees * (float)info.pixelsPerDegreeX);
            }
            else
            {
                float[] linesAndGaps = tpe.LinePattern.Split('|').Select(t => float.Parse(t)).ToArray();
                p = new Pen(Rgba32.ParseHex(htmlColor), tpe.LineWidthDegrees * (float)info.pixelsPerDegreeX, linesAndGaps);
            }

            return(p);
        }
Ejemplo n.º 5
0
        [Route("/[controller]/SlippyAreaData/{styleSet}/{dataKey}/{zoom}/{x}/{y}.png")]                //slippy map conventions.
        public ActionResult DrawSlippyTileCustomPlusCodes(int x, int y, int zoom, string styleSet, string dataKey)
        {
            try
            {
                PerformanceTracker pt      = new PerformanceTracker("DrawSlippyTileCustomPlusCodes");
                string             tileKey = x.ToString() + "|" + y.ToString() + "|" + zoom.ToString();
                var info = new ImageStats(zoom, x, y, IMapTiles.SlippyTileSizeSquare);

                if (!DataCheck.IsInBounds(cache.Get <IPreparedGeometry>("serverBounds"), info.area))
                {
                    pt.Stop("OOB");
                    return(StatusCode(500));
                }

                byte[] tileData = getExistingSlippyTile(tileKey, styleSet);
                if (tileData != null)
                {
                    pt.Stop(tileKey + "|" + styleSet);
                    return(File(tileData, "image/png"));
                }

                //Make tile
                var places   = GetPlacesForTile(info, null, styleSet);
                var paintOps = MapTileSupport.GetPaintOpsForCustomDataPlusCodes(dataKey, styleSet, info);
                tileData = FinishSlippyMapTile(info, paintOps, tileKey, styleSet);

                pt.Stop(tileKey + "|" + styleSet + "|" + Configuration.GetValue <string>("MapTilesEngine"));
                return(File(tileData, "image/png"));
            }
            catch (Exception ex)
            {
                ErrorLogger.LogError(ex);
                return(StatusCode(500));
            }
        }
Ejemplo n.º 6
0
        [Route("/[controller]/AreaPlaceData/{code}/{styleSet}/{dataKey}")] //Draw an area using place data.
        public ActionResult DrawPlusCodeCustomElements(string code, string styleSet, string dataKey)
        {
            try
            {
                PerformanceTracker pt = new PerformanceTracker("DrawTilePlace");
                MapTileSupport.GetPlusCodeImagePixelSize(code, out var imgX, out var imgY);
                var info = new ImageStats(OpenLocationCode.DecodeValid(code), imgX, imgY);

                if (!DataCheck.IsInBounds(cache.Get <IPreparedGeometry>("serverBounds"), info.area))
                {
                    pt.Stop("OOB");
                    return(StatusCode(500));
                }

                byte[] tileData = getExistingSlippyTile(code, styleSet);
                if (tileData != null)
                {
                    pt.Stop(code + "|" + styleSet);
                    return(File(tileData, "image/png"));
                }

                //Make tile
                var places   = GetPlacesForTile(info, null, styleSet, false);
                var paintOps = MapTileSupport.GetPaintOpsForCustomDataElements(dataKey, styleSet, info);
                tileData = FinishMapTile(info, paintOps, code, styleSet);

                pt.Stop(code + "|" + styleSet + "|" + Configuration.GetValue <string>("MapTilesEngine"));
                return(File(tileData, "image/png"));
            }
            catch (Exception ex)
            {
                ErrorLogger.LogError(ex);
                return(StatusCode(500));
            }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Generates all SlippyMap tiles for a given area and zoom level, and saves them to the database.
        /// </summary>
        /// <param name="buffered">the GeoArea to generate tiles for</param>
        /// <param name="zoomLevel">the zoom level to generate tiles at.</param>
        public static void PregenSlippyMapTilesForArea(GeoArea buffered, int zoomLevel)
        {
            //There is a very similar function for this in Standalone.cs, but this one writes back to the main DB.
            var db = new PraxisContext();

            db.ChangeTracker.AutoDetectChangesEnabled = false;
            var intersectCheck = Converters.GeoAreaToPolygon(buffered);

            //start drawing maptiles and sorting out data.
            var swCornerLon = Converters.GetSlippyXFromLon(intersectCheck.EnvelopeInternal.MinX, zoomLevel);
            var neCornerLon = Converters.GetSlippyXFromLon(intersectCheck.EnvelopeInternal.MaxX, zoomLevel);
            var swCornerLat = Converters.GetSlippyYFromLat(intersectCheck.EnvelopeInternal.MinY, zoomLevel);
            var neCornerLat = Converters.GetSlippyYFromLat(intersectCheck.EnvelopeInternal.MaxY, zoomLevel);

            //declare how many map tiles will be drawn
            var xTiles     = neCornerLon - swCornerLon + 1;
            var yTiles     = swCornerLat - neCornerLat + 1;
            var totalTiles = xTiles * yTiles;

            Log.WriteLog("Starting processing " + totalTiles + " maptiles for zoom level " + zoomLevel);
            long mapTileCounter = 0;

            System.Diagnostics.Stopwatch progressTimer = new System.Diagnostics.Stopwatch();
            progressTimer.Start();

            //foreach (var y in yCoords)
            for (var y = neCornerLat; y <= swCornerLat; y++)
            {
                //Make a collision box for just this row of Cell8s, and send the loop below just the list of things that might be relevant.
                //Add a Cell8 buffer space so all elements are loaded and drawn without needing to loop through the entire area.
                GeoArea thisRow = new GeoArea(Converters.SlippyYToLat(y + 1, zoomLevel) - ConstantValues.resolutionCell8,
                                              Converters.SlippyXToLon(swCornerLon, zoomLevel) - ConstantValues.resolutionCell8,
                                              Converters.SlippyYToLat(y, zoomLevel) + ConstantValues.resolutionCell8,
                                              Converters.SlippyXToLon(neCornerLon, zoomLevel) + resolutionCell8);
                var row         = Converters.GeoAreaToPolygon(thisRow);
                var rowList     = GetPlaces(thisRow);
                var tilesToSave = new ConcurrentBag <SlippyMapTile>();

                Parallel.For(swCornerLon, neCornerLon + 1, (x) =>
                {
                    //make map tile.
                    var info     = new ImageStats(zoomLevel, x, y, IMapTiles.SlippyTileSizeSquare);
                    var acheck   = Converters.GeoAreaToPolygon(info.area);                            //this is faster than using a PreparedPolygon in testing, which was unexpected.
                    var areaList = rowList.Where(a => acheck.Intersects(a.ElementGeometry)).ToList(); //This one is for the maptile

                    var tile = MapTiles.DrawAreaAtSize(info, areaList);
                    tilesToSave.Add(new SlippyMapTile()
                    {
                        TileData = tile, Values = x + "|" + y + "|" + zoomLevel, ExpireOn = DateTime.Now.AddDays(3650), AreaCovered = Converters.GeoAreaToPolygon(info.area), StyleSet = "mapTiles"
                    });

                    mapTileCounter++;
                });
                db.SlippyMapTiles.AddRange(tilesToSave);
                db.SaveChanges();
                Log.WriteLog(mapTileCounter + " tiles processed, " + Math.Round(((mapTileCounter / (double)totalTiles * 100)), 2) + "% complete");
            }//);
            progressTimer.Stop();
            Log.WriteLog("Zoom " + zoomLevel + " map tiles drawn in " + progressTimer.Elapsed.ToString());
        }
        public ActionResult GetPlaceInfo(long sourceElementId, int sourceElementType)
        {
            var db   = new PraxisContext();
            var area = db.Places.Include(e => e.Tags).FirstOrDefault(e => e.SourceItemID == sourceElementId && e.SourceItemType == sourceElementType);

            if (area == null)
            {
                return(View());
            }

            TagParser.ApplyTags(new System.Collections.Generic.List <DbTables.Place>()
            {
                area
            }, "mapTiles");
            ViewBag.areaname = TagParser.GetPlaceName(area.Tags);
            ViewBag.type     = area.GameElementName;

            var geoarea = Converters.GeometryToGeoArea(area.ElementGeometry.Envelope);

            geoarea = new Google.OpenLocationCode.GeoArea(geoarea.SouthLatitude - ConstantValues.resolutionCell10,
                                                          geoarea.WestLongitude - ConstantValues.resolutionCell10,
                                                          geoarea.NorthLatitude + ConstantValues.resolutionCell10,
                                                          geoarea.EastLongitude + ConstantValues.resolutionCell10); //add some padding to the edges.
            ImageStats istats = new ImageStats(geoarea, (int)(geoarea.LongitudeWidth / ConstantValues.resolutionCell11Lon), (int)(geoarea.LatitudeHeight / ConstantValues.resolutionCell11Lat));

            //sanity check: we don't want to draw stuff that won't fit in memory, so check for size and cap it if needed
            if (istats.imageSizeX * istats.imageSizeY > 8000000)
            {
                var ratio   = geoarea.LongitudeWidth / geoarea.LatitudeHeight; //W:H,
                var newSize = (istats.imageSizeY > 2000 ? 2000 : istats.imageSizeY);
                istats = new ImageStats(geoarea, (int)(newSize * ratio), newSize);
            }
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            //var tileSvg = MapTiles.DrawAreaAtSizeSVG(istats); ViewBag.UseSvg = true;
            var tile = MapTiles.DrawAreaAtSize(istats); ViewBag.UseSvg = false;

            sw.Stop();

            ViewBag.imageString = "data:image/png;base64," + Convert.ToBase64String(tile);
            //ViewBag.imageString = tileSvg.Substring(39); //skip the <xml> tag
            ViewBag.timeToDraw  = sw.Elapsed;
            ViewBag.placeCount  = 0;
            ViewBag.areasByType = "";
            var places = Place.GetPlaces(istats.area);

            ViewBag.placeCount = places.Count();
            var    grouped     = places.GroupBy(p => p.GameElementName);
            string areasByType = "";

            foreach (var g in grouped)
            {
                areasByType += g.Key + g.Count() + "<br />";
            }

            ViewBag.areasByType = areasByType;

            return(View());
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Get the image for a PlusCode. Can optionally draw in a specific style set.
        /// </summary>
        /// <param name="area">the PlusCode string to draw. Can be 6-11 digits long</param>
        /// <param name="paintOps">the list of paint operations to run through for drawing</param>
        /// <param name="styleSet">the TagParser style set to use for determining the background color.</param>
        /// <returns>a byte array for the png file of the pluscode image file</returns>
        public static byte[] DrawPlusCode(string area, List <CompletePaintOp> paintOps, string styleSet = "mapTiles")
        {
            int imgX = 0, imgY = 0;

            GetPlusCodeImagePixelSize(area, out imgX, out imgY);

            ImageStats info = new ImageStats(OpenLocationCode.DecodeValid(area), imgX, imgY);

            info.drawPoints = true;
            return(MapTiles.DrawAreaAtSize(info, paintOps));
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Draws 1 tile overtop the other.
        /// </summary>
        /// <param name="info">Unused in this implementation, but requires per the interface.</param>
        /// <param name="bottomTile">the tile to use as the base of the image. Expected to be opaque.</param>
        /// <param name="topTile">The tile to layer on top. Expected to be at least partly transparent or translucent.</param>
        /// <returns></returns>
        public byte[] LayerTiles(ImageStats info, byte[] bottomTile, byte[] topTile)
        {
            Image i1 = Image.Load(bottomTile);
            Image i2 = Image.Load(topTile);

            i1.Mutate(x => x.DrawImage(i2, 1));
            var ms = new MemoryStream();

            i1.SaveAsPng(ms);
            return(ms.ToArray());
        }
Ejemplo n.º 11
0
        private static void DrawOneImage(string code)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            TagParser.ApplyTags(memorySource, "mapTiles");
            ImageStats istats   = new ImageStats(OpenLocationCode.DecodeValid(code), 1024, 1024);
            var        paintOps = MapTileSupport.GetPaintOpsForStoredElements(memorySource, "mapTiles", istats);

            File.WriteAllBytes(config["OutputDataFolder"] + code + ".png", MapTileSupport.DrawPlusCode(code, paintOps, "mapTiles"));
            sw.Stop();
            Log.WriteLog("image drawn from memory in " + sw.Elapsed);
        }
Ejemplo n.º 12
0
        private static Dictionary <string, ImageStats> GetColorStatistics(Dictionary <string, byte[]> images)
        {
            var data = new Dictionary <string, ImageStats>();
            var keys = images.Keys.ToArray();

            foreach (var key in keys)
            {
                var source = ParseImage(images[key]);
                var stats  = new ImageStats(key, source);
                data.Add(key, stats);
            }
            return(data);
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Draw square boxes around each area to approximate how they would behave in an offline app
        /// </summary>
        /// <param name="info">the image information for drawing</param>
        /// <param name="items">the elements to draw.</param>
        /// <returns>byte array of the generated .png tile image</returns>
        public byte[] DrawOfflineEstimatedAreas(ImageStats info, List <DbTables.Place> items)
        {
            SKBitmap bitmap  = new SKBitmap(info.imageSizeX, info.imageSizeY, SKColorType.Rgba8888, SKAlphaType.Premul);
            SKCanvas canvas  = new SKCanvas(bitmap);
            var      bgColor = SKColors.Transparent;

            canvas.Clear(bgColor);
            canvas.Scale(1, -1, info.imageSizeX / 2, info.imageSizeY / 2);
            SKPaint fillpaint = new SKPaint();

            fillpaint.IsAntialias = true;
            fillpaint.Style       = SKPaintStyle.Fill;
            var strokePaint = new SKPaint();

            strokePaint.Color       = SKColors.Black;
            strokePaint.TextSize    = 32;
            strokePaint.StrokeWidth = 3;
            strokePaint.Style       = SKPaintStyle.Stroke;
            strokePaint.TextAlign   = SKTextAlign.Center;
            //TagParser.ApplyTags(items);

            var placeInfo = PraxisCore.Standalone.Standalone.GetPlaceInfo(items.Where(i =>
                                                                                      i.IsGameElement
                                                                                      ).ToList());

            //this is for rectangles.
            foreach (var pi in placeInfo)
            {
                var rect = PlaceInfoToRect(pi, info);
                fillpaint.Color = SKColor.Parse(TagParser.PickStaticColorForArea(pi.Name));
                canvas.DrawRect(rect, fillpaint);
                canvas.DrawRect(rect, strokePaint);
            }

            canvas.Scale(1, -1, info.imageSizeX / 2, info.imageSizeY / 2); //inverts the inverted image again!
            foreach (var pi in placeInfo)
            {
                var rect = PlaceInfoToRect(pi, info);
                canvas.DrawText(pi.Name, rect.MidX, info.imageSizeY - rect.MidY, strokePaint);
            }

            var ms   = new MemoryStream();
            var skms = new SKManagedWStream(ms);

            bitmap.Encode(skms, SKEncodedImageFormat.Png, 100);
            var results = ms.ToArray();

            skms.Dispose(); ms.Close(); ms.Dispose();
            return(results);
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Get the image for a PlusCode. Can optionally draw in a specific style set.
        /// </summary>
        /// <param name="area">the PlusCode string to draw. Can be 6-11 digits long</param>
        /// <param name="styleSet">the TagParser style set to use when drawing</param>
        /// <param name="doubleRes">treat each Cell11 contained as 2x2 pixels when true, 1x1 when not.</param>
        /// <returns></returns>
        public static byte[] DrawPlusCode(string area, string styleSet = "mapTiles")
        {
            //This might be a cleaner version of my V4 function, for working with CellX sized tiles..
            //This will draw at a Cell11 resolution automatically.
            //Split it into a few functions.
            //then get all the area

            GetPlusCodeImagePixelSize(area, out var imgX, out var imgY);
            ImageStats info = new ImageStats(OpenLocationCode.DecodeValid(area), imgX, imgY);

            info.drawPoints = true;
            var places   = GetPlacesForTile(info);
            var paintOps = GetPaintOpsForStoredElements(places, styleSet, info);

            return(MapTiles.DrawAreaAtSize(info, paintOps));
        }
        public ActionResult GetMapTileInfo(int x, int y, int zoom)
        {
            //Draw the map tile, with extra info to send over.
            ImageStats istats = new ImageStats(zoom, x, y, IMapTiles.SlippyTileSizeSquare);

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            var tile = MapTiles.DrawAreaAtSize(istats);

            sw.Stop();

            ViewBag.placeCount  = Place.GetPlaces(istats.area).Count();
            ViewBag.timeToDraw  = sw.Elapsed.ToString();
            ViewBag.imageString = "data:image/png;base64," + Convert.ToBase64String(tile);

            return(View());
        }
Ejemplo n.º 16
0
        private static void DrawPosterOfServer()
        {
            var db     = new PraxisContext();
            var bounds = db.ServerSettings.First();

            var geoArea = new GeoArea(bounds.SouthBound, bounds.WestBound, bounds.NorthBound, bounds.EastBound);
            //do the math to scale image.
            //the smaller side is set to 24", the larger size scales up proportionally up to a max of 36"
            //if the longer side is > 36", scale both down by the difference?

            //36x24 is target poster size, at 300 dpi, our image size will allow for a half-inch of margin on both axes.
            var dpi      = 300;
            var maxXSide = 35 * dpi;
            var maxYSide = 23 * dpi;
            var xSize    = 0;
            var ySize    = 0;

            var heightScale = geoArea.LatitudeHeight / geoArea.LongitudeWidth; //Y pixels per X pixel

            if (heightScale > 1)                                               // Y axis is longer than X axis
            {
                heightScale = geoArea.LongitudeWidth / geoArea.LatitudeHeight;
                maxXSide    = 23 * dpi;
                maxYSide    = 35 * dpi;
                ySize       = maxYSide;
                xSize       = (int)(maxXSide * heightScale);
            }
            else
            {
                xSize = maxXSide;
                ySize = (int)(maxYSide * heightScale);
            }

            Log.WriteLog("Loading all places from DB");
            var places = GetPlaces(geoArea);
            var iStats = new ImageStats(geoArea, xSize, ySize);

            Log.WriteLog("Generating paint operations");
            var paintOps = MapTileSupport.GetPaintOpsForStoredElements(places, "mapTiles", iStats);

            Log.WriteLog("Drawing image");
            var image = MapTiles.DrawAreaAtSize(iStats, paintOps);

            File.WriteAllBytes("ServerPoster.png", image);
            Log.WriteLog("Image saved to disk");
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Take a path provided by a user, draw it as a maptile. Potentially useful for exercise trackers. Resulting file must not be saved to the server as that would be user tracking.
        /// </summary>
        /// <param name="pointListAsString">a string of points separate by , and | </param>
        /// <returns>the png file with the path drawn over the mapdata in the area.</returns>
        public byte[] DrawUserPath(string pointListAsString)
        {
            //String is formatted as Lat,Lon~Lat,Lon~ repeating. Characters chosen to not be percent-encoded if submitted as part of the URL.
            //first, convert this to a list of latlon points
            string[]          pointToConvert = pointListAsString.Split("|");
            List <Coordinate> coords         = pointToConvert.Select(p => new Coordinate(double.Parse(p.Split(',')[0]), double.Parse(p.Split(',')[1]))).ToList();

            var     mapBuffer = resolutionCell8 / 2; //Leave some area around the edges of where they went.
            GeoArea mapToDraw = new GeoArea(coords.Min(c => c.Y) - mapBuffer, coords.Min(c => c.X) - mapBuffer, coords.Max(c => c.Y) + mapBuffer, coords.Max(c => c.X) + mapBuffer);

            ImageStats info = new ImageStats(mapToDraw, 1024, 1024);

            LineString line         = new LineString(coords.ToArray());
            var        drawableLine = PolygonToSKPoints(line, mapToDraw, info.degreesPerPixelX, info.degreesPerPixelY);

            //Now, draw that path on the map.
            var places    = GetPlaces(mapToDraw); //, null, false, false, degreesPerPixelX * 4 ///TODO: restore item filtering
            var baseImage = DrawAreaAtSize(info, places);

            SKBitmap sKBitmap = SKBitmap.Decode(baseImage);
            SKCanvas canvas   = new SKCanvas(sKBitmap);
            SKPaint  paint    = new SKPaint();

            paint.Style       = SKPaintStyle.Stroke;
            paint.StrokeWidth = 4;                    //Larger than normal lines at any zoom level.
            paint.Color       = new SKColor(0, 0, 0); //Pure black, for maximum visibility.
            for (var x = 0; x < drawableLine.Length - 1; x++)
            {
                canvas.DrawLine(drawableLine[x], drawableLine[x + 1], paint);
            }

            var ms   = new MemoryStream();
            var skms = new SKManagedWStream(ms);

            sKBitmap.Encode(skms, SKEncodedImageFormat.Png, 100);
            var results = ms.ToArray();

            skms.Dispose(); ms.Close(); ms.Dispose();
            return(results);
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Draw square boxes around each area to approximate how they would behave in an offline app
        /// </summary>
        /// <param name="info">the image information for drawing</param>
        /// <param name="items">the elements to draw.</param>
        /// <returns>byte array of the generated .png tile image</returns>
        public byte[] DrawOfflineEstimatedAreas(ImageStats info, List <DbTables.Place> items)
        {
            //TODO retest this.
            var image   = new Image <Rgba32>(info.imageSizeX, info.imageSizeY);
            var bgColor = Rgba32.ParseHex("00000000");

            image.Mutate(x => x.Fill(bgColor));
            var fillColor   = Rgba32.ParseHex("000000");
            var strokeColor = Rgba32.ParseHex("000000");

            var placeInfo = Standalone.Standalone.GetPlaceInfo(items.Where(i =>
                                                                           i.IsGameElement
                                                                           ).ToList());

            //this is for rectangles.
            foreach (var pi in placeInfo)
            {
                var rect = PlaceInfoToRect(pi, info);
                fillColor = Rgba32.ParseHex(TagParser.PickStaticColorForArea(pi.Name));
                image.Mutate(x => x.Fill(fillColor, rect));
                image.Mutate(x => x.Draw(strokeColor, 3, rect));
            }

            image.Mutate(x => x.Flip(FlipMode.Vertical));;  //inverts the inverted image again!
            foreach (var pi in placeInfo)
            {
                //NOTE: would be better to load fonts once and share that for the app's lifetime.
                var fonts  = new SixLabors.Fonts.FontCollection();
                var family = fonts.Add("fontHere.ttf");
                var font   = family.CreateFont(12, FontStyle.Regular);
                var rect   = PlaceInfoToRect(pi, info);
                image.Mutate(x => x.DrawText(pi.Name, font, strokeColor, new PointF((float)(pi.lonCenter * info.pixelsPerDegreeX), (float)(pi.latCenter * info.pixelsPerDegreeY))));
            }

            image.Mutate(x => x.Flip(FlipMode.Vertical)); //Plus codes are south-to-north, so invert the image to make it correct.
            var ms = new MemoryStream();

            image.SaveAsPng(ms);
            return(ms.ToArray());
        }
Ejemplo n.º 19
0
        /// <summary>
        /// Combines 2 images into one, given the shared ImageStats for both supplied images.
        /// </summary>
        /// <param name="info">the ImageStats object used to generate both bottom and top tiles.</param>
        /// <param name="bottomTile">the tile to use as the base of the image. Expected to be opaque.</param>
        /// <param name="topTile">The tile to layer on top. Expected to be at least partly transparent or translucent.</param>
        /// <returns></returns>
        public byte[] LayerTiles(ImageStats info, byte[] bottomTile, byte[] topTile)
        {
            SkiaSharp.SKBitmap bitmap = new SkiaSharp.SKBitmap(info.imageSizeX, info.imageSizeY, SkiaSharp.SKColorType.Rgba8888, SkiaSharp.SKAlphaType.Premul);
            SkiaSharp.SKCanvas canvas = new SkiaSharp.SKCanvas(bitmap);
            SkiaSharp.SKPaint  paint  = new SkiaSharp.SKPaint();
            canvas.Scale(1, 1, info.imageSizeX / 2, info.imageSizeY / 2);
            paint.IsAntialias = true;

            var baseBmp = SkiaSharp.SKBitmap.Decode(bottomTile);
            var topBmp  = SkiaSharp.SKBitmap.Decode(topTile);

            canvas.DrawBitmap(baseBmp, 0, 0);
            canvas.DrawBitmap(topBmp, 0, 0);
            var ms   = new MemoryStream();
            var skms = new SkiaSharp.SKManagedWStream(ms);

            bitmap.Encode(skms, SkiaSharp.SKEncodedImageFormat.Png, 100);
            var output = ms.ToArray();

            skms.Dispose(); ms.Close(); ms.Dispose();
            return(output);
        }
Ejemplo n.º 20
0
        /// <summary>
        /// Creates all gameplay tiles for a given area and saves them to the database (or files, if that option is set)
        /// </summary>
        /// <param name="areaToDraw">the GeoArea of the area to create tiles for.</param>
        /// <param name="saveToFiles">If true, writes to files in the output folder. If false, saves to DB.</param>
        public static void PregenMapTilesForArea(GeoArea areaToDraw, bool saveToFiles = false)
        {
            //There is a very similar function for this in Standalone.cs, but this one writes back to the main DB.
            var intersectCheck = Converters.GeoAreaToPolygon(areaToDraw);
            //start drawing maptiles and sorting out data.
            var swCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MinY, intersectCheck.EnvelopeInternal.MinX);
            var neCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MaxY, intersectCheck.EnvelopeInternal.MaxX);

            //declare how many map tiles will be drawn
            var xTiles     = areaToDraw.LongitudeWidth / resolutionCell8;
            var yTiles     = areaToDraw.LatitudeHeight / resolutionCell8;
            var totalTiles = Math.Truncate(xTiles * yTiles);

            Log.WriteLog("Starting processing maptiles for " + totalTiles + " Cell8 areas.");
            long mapTileCounter = 0;

            System.Diagnostics.Stopwatch progressTimer = new System.Diagnostics.Stopwatch();
            progressTimer.Start();

            //now, for every Cell8 involved, draw and name it.
            var yCoords = new List <double>((int)(intersectCheck.EnvelopeInternal.Height / resolutionCell8) + 1);
            var yVal    = swCorner.Decode().SouthLatitude;

            while (yVal <= neCorner.Decode().NorthLatitude)
            {
                yCoords.Add(yVal);
                yVal += resolutionCell8;
            }

            var xCoords = new List <double>((int)(intersectCheck.EnvelopeInternal.Width / resolutionCell8) + 1);
            var xVal    = swCorner.Decode().WestLongitude;

            while (xVal <= neCorner.Decode().EastLongitude)
            {
                xCoords.Add(xVal);
                xVal += resolutionCell8;
            }

            var allPlaces = GetPlaces(areaToDraw);

            object   listLock   = new object();
            DateTime expiration = DateTime.Now.AddYears(10);

            foreach (var y in yCoords)
            {
                System.Diagnostics.Stopwatch thisRowSW = new System.Diagnostics.Stopwatch();
                thisRowSW.Start();
                var db = new PraxisContext();
                db.ChangeTracker.AutoDetectChangesEnabled = false;
                //Make a collision box for just this row of Cell8s, and send the loop below just the list of things that might be relevant.
                //Add a Cell8 buffer space so all elements are loaded and drawn without needing to loop through the entire area.
                GeoArea thisRow     = new GeoArea(y - ConstantValues.resolutionCell8, xCoords.First() - ConstantValues.resolutionCell8, y + ConstantValues.resolutionCell8 + ConstantValues.resolutionCell8, xCoords.Last() + resolutionCell8);
                var     rowList     = GetPlaces(thisRow, allPlaces);
                var     tilesToSave = new List <MapTile>(xCoords.Count());

                Parallel.ForEach(xCoords, x =>
                {
                    //make map tile.
                    var plusCode     = new OpenLocationCode(y, x, 10);
                    var plusCode8    = plusCode.CodeDigits.Substring(0, 8);
                    var plusCodeArea = OpenLocationCode.DecodeValid(plusCode8);
                    var paddedArea   = GeometrySupport.MakeBufferedGeoArea(plusCodeArea);

                    var acheck   = Converters.GeoAreaToPreparedPolygon(paddedArea);                   //Fastest search option is one preparedPolygon against a list of normal geometry.
                    var areaList = rowList.Where(a => acheck.Intersects(a.ElementGeometry)).ToList(); //Get the list of areas in this maptile.

                    int imgX = 0, imgY = 0;
                    GetPlusCodeImagePixelSize(plusCode8, out imgX, out imgY);
                    var info = new ImageStats(plusCodeArea, imgX, imgY);
                    //new setup.
                    var areaPaintOps = GetPaintOpsForStoredElements(areaList, "mapTiles", info);
                    var tile         = DrawPlusCode(plusCode8, areaPaintOps, "mapTiles");

                    if (saveToFiles)
                    {
                        File.WriteAllBytes("GameTiles\\" + plusCode8 + ".png", tile);
                    }
                    else
                    {
                        var thisTile = new MapTile()
                        {
                            TileData = tile, PlusCode = plusCode8, ExpireOn = expiration, AreaCovered = acheck.Geometry, StyleSet = "mapTiles"
                        };
                        lock (listLock)
                            tilesToSave.Add(thisTile);
                    }
                });
                mapTileCounter += xCoords.Count();
                if (!saveToFiles)
                {
                    db.MapTiles.AddRange(tilesToSave);
                    db.SaveChanges();
                }
                Log.WriteLog(mapTileCounter + " tiles processed, " + Math.Round((mapTileCounter / totalTiles) * 100, 2) + "% complete, " + Math.Round(xCoords.Count() / thisRowSW.Elapsed.TotalSeconds, 2) + " tiles per second.");
            }//);
            progressTimer.Stop();
            Log.WriteLog("Area map tiles drawn in " + progressTimer.Elapsed.ToString() + ", averaged " + Math.Round(mapTileCounter / progressTimer.Elapsed.TotalSeconds, 2) + " tiles per second.");
        }
Ejemplo n.º 21
0
        /// <summary>
        /// Converts the offline-standalone PlaceInfo entries into SKRects for drawing on a SlippyMap. Used to visualize the offline mode beahvior of areas.
        /// </summary>
        /// <param name="pi">PlaceInfo object to convert</param>
        /// <param name="info">ImageStats for the resulting map tile</param>
        /// <returns>The SKRect representing the standaloneDb size of the PlaceInfo</returns>
        public SkiaSharp.SKRect PlaceInfoToRect(PraxisCore.StandaloneDbTables.PlaceInfo2 pi, ImageStats info)
        {
            SkiaSharp.SKRect r         = new SkiaSharp.SKRect();
            float            heightMod = (float)pi.height / 2;
            float            widthMod  = (float)pi.width / 2;

            r.Left   = (float)pi.lonCenter - widthMod;
            r.Left   = (float)(r.Left - info.area.WestLongitude) * (float)(1 / info.degreesPerPixelX);
            r.Right  = (float)pi.lonCenter + widthMod;
            r.Right  = (float)(r.Right - info.area.WestLongitude) * (float)(1 / info.degreesPerPixelX);
            r.Top    = (float)pi.latCenter + heightMod;
            r.Top    = (float)(r.Top - info.area.SouthLatitude) * (float)(1 / info.degreesPerPixelY);
            r.Bottom = (float)pi.latCenter - heightMod;
            r.Bottom = (float)(r.Bottom - info.area.SouthLatitude) * (float)(1 / info.degreesPerPixelY);

            return(r);
        }
Ejemplo n.º 22
0
        /// <summary>
        /// A shortcut function to pull GetPlaces info against an ImageStats objects.
        /// </summary>
        /// <param name="stats">the ImageStats containing some of the other parameters to pass through</param>
        /// <param name="source">Null to load from the database, or a List of Places to narrow down</param>
        /// <param name="styleSet">A TagParser style set to run the found locations through for identification.</param>
        /// <param name="skipTags">If true, skips over tagging elements. A performance boost when you have a List to narrow down already.</param>
        /// <returns>A list of Places that intersect the area, have a perimter greater than or equal to filtersize.</returns>
        public static List <DbTables.Place> GetPlacesForTile(ImageStats stats, List <DbTables.Place> source = null, string styleSet = "mapTiles", bool skipTags = false)
        {
            var dataLoadArea = new GeoArea(stats.area.SouthLatitude - ConstantValues.resolutionCell10, stats.area.WestLongitude - ConstantValues.resolutionCell10, stats.area.NorthLatitude + ConstantValues.resolutionCell10, stats.area.EastLongitude + ConstantValues.resolutionCell10);

            return(GetPlaces(dataLoadArea, source, stats.filterSize, styleSet, skipTags, stats.drawPoints));
        }
Ejemplo n.º 23
0
        /// <summary>
        /// Create the map tiles for an offline game, to be stored on the device alongside the database.
        /// </summary>
        /// <param name="relationID">the OSM relation the game will be built around</param>
        /// <param name="buffered">the GeoArea to use as the gameplay area</param>
        /// <param name="allPlaces">List of OSM Elements to use while drawing tiles</param>
        /// <param name="saveToFolder">If true, save images to a named folder. Does not currently save any output if this is false.</param>
        public static void DrawMapTilesStandalone(long relationID, GeoArea buffered, List <DbTables.Place> allPlaces, bool saveToFolder)
        {
            var intersectCheck = Converters.GeoAreaToPolygon(buffered);
            //start drawing maptiles and sorting out data.
            var swCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MinY, intersectCheck.EnvelopeInternal.MinX);
            var neCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MaxY, intersectCheck.EnvelopeInternal.MaxX);

            //declare how many map tiles will be drawn
            var xTiles     = buffered.LongitudeWidth / resolutionCell8;
            var yTiles     = buffered.LatitudeHeight / resolutionCell8;
            var totalTiles = Math.Truncate(xTiles * yTiles);

            Log.WriteLog("Starting processing maptiles for " + totalTiles + " Cell8 areas.");
            long mapTileCounter = 0;

            System.Diagnostics.Stopwatch progressTimer = new System.Diagnostics.Stopwatch();
            progressTimer.Start();

            //now, for every Cell8 involved, draw and name it.
            //This is tricky to run in parallel because it's not smooth increments
            var yCoords = new List <double>();
            var yVal    = swCorner.Decode().SouthLatitude;

            while (yVal <= neCorner.Decode().NorthLatitude)
            {
                yCoords.Add(yVal);
                yVal += resolutionCell8;
            }

            var xCoords = new List <double>();
            var xVal    = swCorner.Decode().WestLongitude;

            while (xVal <= neCorner.Decode().EastLongitude)
            {
                xCoords.Add(xVal);
                xVal += resolutionCell8;
            }
            System.Threading.ReaderWriterLockSlim dbLock = new System.Threading.ReaderWriterLockSlim();


            foreach (var y in yCoords)
            {
                //Make a collision box for just this row of Cell8s, and send the loop below just the list of things that might be relevant.
                //Add a Cell8 buffer space so all elements are loaded and drawn without needing to loop through the entire area.
                GeoArea thisRow = new GeoArea(y - ConstantValues.resolutionCell8, xCoords.First() - ConstantValues.resolutionCell8, y + ConstantValues.resolutionCell8 + ConstantValues.resolutionCell8, xCoords.Last() + resolutionCell8);
                var     row     = Converters.GeoAreaToPolygon(thisRow);
                var     rowList = allPlaces.Where(a => row.Intersects(a.ElementGeometry)).ToList();

                Parallel.ForEach(xCoords, x =>
                                 //foreach (var x in xCoords)
                {
                    //make map tile.
                    var plusCode     = new OpenLocationCode(y, x, 10);
                    var plusCode8    = plusCode.CodeDigits.Substring(0, 8);
                    var plusCodeArea = OpenLocationCode.DecodeValid(plusCode8);

                    var areaForTile = new GeoArea(new GeoPoint(plusCodeArea.SouthLatitude, plusCodeArea.WestLongitude), new GeoPoint(plusCodeArea.NorthLatitude, plusCodeArea.EastLongitude));
                    var acheck      = Converters.GeoAreaToPolygon(areaForTile);                          //this is faster than using a PreparedPolygon in testing, which was unexpected.
                    var areaList    = rowList.Where(a => acheck.Intersects(a.ElementGeometry)).ToList(); //This one is for the maptile

                    //Create the maptile first, so if we save it to the DB/a file we can call the lock once per loop.
                    int imgX = 0, imgY = 0;
                    MapTileSupport.GetPlusCodeImagePixelSize(plusCode8, out imgX, out imgY);
                    var info    = new ImageStats(areaForTile, imgX, imgY); //Each pixel is a Cell11, we're drawing a Cell8. For Cell6 testing this is 1600x2000, just barely within android limits
                    byte[] tile = null;                                    // MapTiles.DrawAreaAtSize(info, areaList);
                    if (tile == null)
                    {
                        Log.WriteLog("Tile at " + x + "," + y + "Failed to draw!");
                        return;
                    }
                    if (saveToFolder) //some apps, like my Solar2D apps, can't use the byte[] in a DB row and need files.
                    {
                        //This split helps (but does not alleviate) Solar2D performance.
                        //A county-sized app will function this way, though sometimes when zoomed out it will not load all map tiles on an android device.
                        Directory.CreateDirectory(relationID + "Tiles\\" + plusCode8.Substring(0, 6));
                        System.IO.File.WriteAllBytes(relationID + "Tiles\\" + plusCode8.Substring(0, 6) + "\\" + plusCode8.Substring(6, 2) + ".pngTile", tile); //Solar2d also can't load pngs directly from an apk file in android, but the rule is extension based.
                    }

                    mapTileCounter++;
                    //if (progressTimer.ElapsedMilliseconds > 15000)
                    //{

                    //    progressTimer.Restart();
                    //}
                });
                Log.WriteLog(mapTileCounter + " tiles processed, " + Math.Round((mapTileCounter / totalTiles) * 100, 2) + "% complete");
            }//);
        }
Ejemplo n.º 24
0
        public Rectangle PlaceInfoToRect(PraxisCore.StandaloneDbTables.PlaceInfo2 pi, ImageStats info)
        {
            //TODO test this.
            Rectangle r         = new Rectangle();
            float     heightMod = (float)pi.height / 2;
            float     widthMod  = (float)pi.width / 2;

            r.Width  = (int)(pi.width * info.pixelsPerDegreeX);
            r.Height = (int)(pi.height * info.pixelsPerDegreeY);
            r.X      = (int)(pi.lonCenter * info.pixelsPerDegreeX);
            r.Y      = (int)(pi.latCenter * info.pixelsPerDegreeY);

            return(r);
        }
Ejemplo n.º 25
0
 private static void GetPaintOps(ref List <CompletePaintOp> list, double areaSize, Geometry elementGeometry, ICollection <StylePaint> midOps, ImageStats stats)
 {
     foreach (var po in midOps)
     {
         if (stats.degreesPerPixelX < po.MaxDrawRes &&
             stats.degreesPerPixelX > po.MinDrawRes && //dppX should be between max and min draw range.
             !(po.HtmlColorCode.Length == 8 && po.HtmlColorCode.StartsWith("00"))    //color is NOT transparent.
             )
         {
             list.Add(new CompletePaintOp(elementGeometry, areaSize, po, "", po.LineWidthDegrees * stats.pixelsPerDegreeX));
         }
     }
 }
Ejemplo n.º 26
0
 /// <summary>
 /// ImageSharp doesn't support this at all. Throws NotImplementedException when called.
 /// </summary>
 public string DrawAreaAtSizeSVG(ImageStats stats, List <DbTables.Place> drawnItems = null, Dictionary <string, StyleEntry> styles = null, bool filterSmallAreas = true)
 {
     throw new NotImplementedException();
 }
Ejemplo n.º 27
0
        public byte[] DrawAreaAtSize(ImageStats stats, List <CompletePaintOp> paintOps)
        {
            //THIS is the core drawing function, and other version should call this so there's 1 function that handles the inner loop.
            //baseline image data stuff
            var image = new Image <Rgba32>(stats.imageSizeX, stats.imageSizeY);

            foreach (var w in paintOps.OrderByDescending(p => p.paintOp.LayerId).ThenByDescending(p => p.areaSize))
            {
                //I need paints for fill commands and images.
                var paint = cachedPaints[w.paintOp.Id];
                var pen   = cachedGameTilePens[w.paintOp.Id];

                if (stats.area.LongitudeWidth != resolutionCell8 || w.paintOp.Randomize || w.paintOp.FromTag)
                {
                    //recreate pen for this operation instead of using cached pen.
                    if (w.paintOp.Randomize) //To randomize the color on every Draw call.
                    {
                        w.paintOp.HtmlColorCode = "99" + ((byte)r.Next(0, 255)).ToString() + ((byte)r.Next(0, 255)).ToString() + ((byte)r.Next(0, 255)).ToString();
                    }

                    if (w.paintOp.FromTag) //FromTag is for when you are saving color data directly to each element, instead of tying it to a styleset.
                    {
                        w.paintOp.HtmlColorCode = w.tagValue;
                    }

                    //TODO: use stats to see if this image is scaled to gameTile values, and if so then use cached pre-made pens?
                    if (String.IsNullOrWhiteSpace(w.paintOp.LinePattern) || w.paintOp.LinePattern == "solid")
                    {
                        pen = new Pen(Rgba32.ParseHex(w.paintOp.HtmlColorCode), (float)w.lineWidthPixels);
                    }
                    else
                    {
                        float[] linesAndGaps = w.paintOp.LinePattern.Split('|').Select(t => float.Parse(t)).ToArray();
                        pen = new Pen(Rgba32.ParseHex(w.paintOp.HtmlColorCode), (float)w.lineWidthPixels, linesAndGaps);
                    }
                }

                //ImageSharp doesn't like humungous areas (16k+ nodes takes a couple minutes), so we have to crop them down here
                Geometry thisGeometry = w.elementGeometry; //default
                //This block below is fairly imporant because of Path.Clip() performance. I would still prefer to do this over the original way of handling holes in paths (draw bitmap of outer polygons, erase holes with eraser paint, draw that bitmap over maptile)
                //it doesn't ALWAYS cause problems if I skip this, but when it does it takes forever to draw some tiles. Keep this in even if it only seems to happen with debug mode on.
                if (w.elementGeometry.Coordinates.Length > 800)
                {
                    thisGeometry = w.elementGeometry.Intersection(Converters.GeoAreaToPolygon(GeometrySupport.MakeBufferedGeoArea(stats.area)));
                }
                if (thisGeometry.Coordinates.Length == 0) //After trimming, linestrings may not have any points in the drawing area.
                {
                    continue;
                }

                switch (thisGeometry.GeometryType)
                {
                case "Polygon":
                    //after trimming this might not work out as well. Don't draw broken/partial polygons? or only as lines?
                    if (thisGeometry.Coordinates.Length > 2)
                    {
                        var drawThis = PolygonToDrawingPolygon(thisGeometry, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                        if (w.paintOp.FillOrStroke == "fill")
                        {
                            image.Mutate(x => x.Fill(dOpts, paint, drawThis));
                        }
                        else
                        {
                            image.Mutate(x => x.Draw(dOpts, pen, drawThis));
                        }
                    }
                    break;

                case "MultiPolygon":
                    foreach (NetTopologySuite.Geometries.Polygon p2 in ((MultiPolygon)thisGeometry).Geometries)
                    {
                        var drawThis2 = PolygonToDrawingPolygon(p2, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                        if (w.paintOp.FillOrStroke == "fill")
                        {
                            image.Mutate(x => x.Fill(dOpts, paint, drawThis2));
                        }
                        else
                        {
                            image.Mutate(x => x.Draw(dOpts, pen, drawThis2));
                        }
                    }
                    break;

                case "LineString":
                    var firstPoint = thisGeometry.Coordinates.First();
                    var lastPoint  = thisGeometry.Coordinates.Last();
                    var line       = LineToDrawingLine(thisGeometry, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);

                    if (firstPoint.Equals(lastPoint) && w.paintOp.FillOrStroke == "fill")
                    {
                        image.Mutate(x => x.Fill(dOpts, paint, new SixLabors.ImageSharp.Drawing.Polygon(new LinearLineSegment(line))));
                    }
                    else
                    {
                        image.Mutate(x => x.DrawLines(dOpts, pen, line));
                    }
                    break;

                case "MultiLineString":
                    foreach (var p3 in ((MultiLineString)thisGeometry).Geometries)
                    {
                        var line2 = LineToDrawingLine(p3, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                        image.Mutate(x => x.DrawLines(dOpts, pen, line2));
                    }
                    break;

                case "Point":
                    var convertedPoint = PointToPointF(thisGeometry, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                    //If this type has an icon, use it. Otherwise draw a circle in that type's color.
                    if (!string.IsNullOrEmpty(w.paintOp.FileName))
                    {
                        //TODO test that this draws in correct position.
                        Image i2 = cachedBitmaps[w.paintOp.FileName];
                        image.Mutate(x => x.DrawImage(i2, (SixLabors.ImageSharp.Point)convertedPoint, 1));
                    }
                    else
                    {
                        var circleRadius = (float)w.lineWidthPixels;     //(float)(ConstantValues.resolutionCell10 / stats.degreesPerPixelX / 2); //I want points to be drawn as 1 Cell10 in diameter.
                        var shape        = new SixLabors.ImageSharp.Drawing.EllipsePolygon(
                            PointToPointF(thisGeometry, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY),
                            new SizeF(circleRadius, circleRadius));
                        image.Mutate(x => x.Fill(dOpts, paint, shape));
                        image.Mutate(x => x.Draw(dOpts, Color.Black, 1, shape));     //NOTE: this gets overlapped by other elements in the same (or higher) layers, maybe outlines should be their own layer again.
                    }
                    break;

                default:
                    Log.WriteLog("Unknown geometry type found, not drawn.");
                    break;
                }
            }

            image.Mutate(x => x.Flip(FlipMode.Vertical)); //Plus codes are south-to-north, so invert the image to make it correct.
            image.Mutate(x => x.BoxBlur(1));              //This does help smooth out some of the rough edges on the game tiles.
            var ms = new MemoryStream();

            image.SaveAsPng(ms);
            return(ms.ToArray());
        }
Ejemplo n.º 28
0
        /// <summary>
        /// Creates the list of paint commands for the given elements, styles, and image area.
        /// </summary>
        /// <param name="places">the list of Places to be drawn</param>
        /// <param name="styleSet">the style set to use when drwaing the elements</param>
        /// <param name="stats">the info on the resulting image for calculating ops.</param>
        /// <returns>a list of CompletePaintOps to be passed into a DrawArea function</returns>
        public static List <CompletePaintOp> GetPaintOpsForStoredElements(List <DbTables.Place> places, string styleSet, ImageStats stats)
        {
            var styles = TagParser.allStyleGroups[styleSet];
            var bgOp   = new CompletePaintOp(Converters.GeoAreaToPolygon(stats.area), 1, styles["background"].PaintOperations.First(), "background", 1);
            var pass1  = places.Select(d => new { d.AreaSize, d.ElementGeometry, paintOp = styles[d.GameElementName].PaintOperations });
            var pass2  = new List <CompletePaintOp>(places.Count() * 2);

            pass2.Add(bgOp);
            foreach (var op in pass1)
            {
                GetPaintOps(ref pass2, op.AreaSize, op.ElementGeometry, op.paintOp, stats);
            }

            return(pass2);
        }
Ejemplo n.º 29
0
        /// <summary>
        /// Creates the list of paint commands for the elements intersecting the given area, with the given data key attached to OSM elements and style set, for the image.
        /// </summary>
        /// <param name="dataKey">the key to pull data from attached to any Osm Elements intersecting the area</param>
        /// <param name="styleSet">the style set to use when drawing the intersecting elements</param>
        /// <param name="stats">the info on the resulting image for calculating ops.</param>
        /// <returns>a list of CompletePaintOps to be passed into a DrawArea function</returns>
        public static List <CompletePaintOp> GetPaintOpsForCustomDataElements(string dataKey, string styleSet, ImageStats stats)
        {
            //NOTE: this is being passed in an Area as a Geometry. The name needs clarified to show its drawing a maptile based on the gameplay data for places in that area.
            var db       = new PraxisContext();
            var poly     = Converters.GeoAreaToPolygon(GeometrySupport.MakeBufferedGeoArea(stats.area));
            var elements = db.PlaceGameData.Include(d => d.Place).Where(d => d.DataKey == dataKey && poly.Intersects(d.Place.ElementGeometry)).ToList();
            var styles   = TagParser.allStyleGroups[styleSet];
            var bgOp     = new CompletePaintOp(Converters.GeoAreaToPolygon(stats.area), 1, styles["background"].PaintOperations.First(), "background", 1);
            var pass1    = elements.Select(d => new { d.Place.AreaSize, d.Place.ElementGeometry, paintOp = styles[d.DataValue.ToUTF8String()].PaintOperations, d.DataValue });
            var pass2    = new List <CompletePaintOp>(elements.Count() * 2);

            pass2.Add(bgOp);
            foreach (var op in pass1)
            {
                GetPaintOps(ref pass2, op.AreaSize, op.ElementGeometry, op.paintOp, stats);
            }

            return(pass2);
        }
Ejemplo n.º 30
0
        //Allows for 1 style to pull a color from the custom data value.
        /// <summary>
        /// Creates the list of paint commands for the PlusCode cells intersecting the given area, with the given data key and style set, for the image. In this case, the color will be the tag's value.
        /// </summary>
        /// <param name="dataKey">the key to pull data from attached to any Osm Elements intersecting the area</param>
        /// <param name="styleSet">the style set to use when drawing the intersecting elements</param>
        /// <param name="stats">the info on the resulting image for calculating ops.</param>
        /// <returns>a list of CompletePaintOps to be passed into a DrawArea function</returns>
        public static List <CompletePaintOp> GetPaintOpsForCustomDataPlusCodesFromTagValue(string dataKey, string styleSet, ImageStats stats)
        {
            var db       = new PraxisContext();
            var poly     = Converters.GeoAreaToPolygon(GeometrySupport.MakeBufferedGeoArea(stats.area));
            var elements = db.AreaGameData.Where(d => d.DataKey == dataKey && poly.Intersects(d.GeoAreaIndex)).ToList();
            var styles   = TagParser.allStyleGroups[styleSet];
            var bgOp     = new CompletePaintOp(Converters.GeoAreaToPolygon(stats.area), 1, styles["background"].PaintOperations.First(), "background", 1);
            var pass1    = elements.Select(d => new { d.GeoAreaIndex.Area, d.GeoAreaIndex, paintOp = styles["tag"].PaintOperations, d.DataValue });
            var pass2    = new List <CompletePaintOp>(elements.Count() * 2); //assuming each element has a Fill and Stroke op separately

            pass2.Add(bgOp);
            foreach (var op in pass1)
            {
                GetPaintOps(ref pass2, op.Area, op.GeoAreaIndex, op.paintOp, stats);
            }

            return(pass2);
        }