コード例 #1
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);
        }
コード例 #2
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);
        }
コード例 #3
0
        //Places will be the name for interactible or important areas on the map. Generally, a Place in the DB.
        //(vs Area, which is a PlusCode of any size)

        //All elements in the table with Geometry will be valid, and the TagParser rules will determine which ones are game elements
        //this allows it to be customized much easier, and changed on the fly without reloading data.

        /// <summary>
        /// The core for pulling in locations from PraxisMapper. Can do a new search on an existing list of Place or pulls from the database if none is provided. Adds padding as set from config automatically.
        /// </summary>
        /// <param name="area">The GeoArea to intersect locations against, and include ones that do. </param>
        /// <param name="source">Null to load from the database, or a List of Places to narrow down</param>
        /// <param name="filterSize">Removes any areas with a length or perimeter over this value. Defaults to 0 to include everything.</param>
        /// <param name="styleSet">A 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>
        /// <param name="includePoints">If false, removes Points from the source before returning the results</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> GetPlaces(GeoArea area, List <DbTables.Place> source = null, double filterSize = 0, string styleSet = "mapTiles", bool skipTags = false, bool includePoints = true)
        {
            //parameters i will need to restore later.
            //bool includeGenerated = false;

            //The flexible core of the lookup functions. Takes an area, returns results that intersect from Source. If source is null, looks into the DB.
            //Intersects is the only indexable function on a geography column I would want here. Distance and Equals can also use the index, but I don't need those in this app.
            List <DbTables.Place> places;

            if (source == null)
            {
                var paddedArea = GeometrySupport.MakeBufferedGeoArea(area);
                var location   = Converters.GeoAreaToPolygon(paddedArea); //Prepared items don't work on a DB lookup.
                var db         = new PraxisContext();
                db.Database.SetCommandTimeout(new TimeSpan(0, 5, 0));
                if (skipTags) //Should make the load slightly faster if we're parsing existing items that already got tags applied
                {
                    places = db.Places.Where(md => location.Intersects(md.ElementGeometry) && md.AreaSize >= filterSize && (includePoints || md.SourceItemType != 1)).OrderByDescending(w => w.ElementGeometry.Area).ThenByDescending(w => w.ElementGeometry.Length).ToList();
                    return(places); //Jump out before we do ApplyTags
                }
                else
                {
                    places = db.Places.Include(s => s.Tags).Where(md => location.Intersects(md.ElementGeometry) && md.AreaSize >= filterSize && (includePoints || md.SourceItemType != 1)).OrderByDescending(w => w.ElementGeometry.Area).ThenByDescending(w => w.ElementGeometry.Length).ToList();
                }
            }
            else
            {
                var location = Converters.GeoAreaToPreparedPolygon(area);
                places = source.Where(md => location.Intersects(md.ElementGeometry) && md.AreaSize >= filterSize && (includePoints || md.SourceItemType != 1)).Select(md => md.Clone()).ToList();
            }

            if (!skipTags)
            {
                TagParser.ApplyTags(places, styleSet); //populates the fields we don't save to the DB.
            }
            return(places);
        }
コード例 #4
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());
        }
コード例 #5
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.");
        }
コード例 #6
0
        /// <summary>
        /// Takes the first way in a list, and searches for any/all ways that connect to it to form a closed polygon. Returns null if a closed shape cannot be formed.
        /// Call this repeatedly on a list until it is empty to find all polygons in a list of ways.
        /// </summary>
        /// <param name="shapeList">The list of ways to search. Will remove all ways that join to the first one or any joined to it from this list.</param>
        /// <returns>A Polygon if ways were able to be joined into a closed shape with the first way in the list, or null if not.</returns>
        private static Polygon GetShapeFromLines(ref List <CompleteWay> shapeList)
        {
            //NOTE/TODO: if this is a relation of lines that aren't a polygon (EX: a very long hiking trail), this should probably return the combined linestring?

            List <Node> currentShape = new List <Node>();
            var         firstShape   = shapeList.FirstOrDefault();

            if (firstShape == null)
            {
                Log.WriteLog("shapelist has 0 ways in shapelist?", Log.VerbosityLevels.Errors);
                return(null);
            }

            Node originalStartPoint = firstShape.Nodes.First();

            shapeList.Remove(firstShape);
            var nextStartnode = firstShape.Nodes.Last();
            var closedShape   = false;

            currentShape.AddRange(firstShape.Nodes);
            while (closedShape == false)
            {
                var lineToAdd = shapeList.FirstOrDefault(s => s.Nodes.First().Id == nextStartnode.Id);
                if (lineToAdd == null)
                {
                    //check other direction
                    lineToAdd = shapeList.FirstOrDefault(s => s.Nodes.Last().Id == nextStartnode.Id);
                    if (lineToAdd == null)
                    {
                        return(null);
                    }
                    else
                    {
                        lineToAdd.Nodes = lineToAdd.Nodes.Reverse().ToArray(); //This way was drawn backwards relative to the original way.
                    }
                }
                currentShape.AddRange(lineToAdd.Nodes.Skip(1));
                nextStartnode = lineToAdd.Nodes.Last();
                shapeList.Remove(lineToAdd);

                if (nextStartnode.Id == originalStartPoint.Id)
                {
                    closedShape = true;
                }
                if (shapeList.Count == 0 && !closedShape)
                {
                    return(null);
                }
            }

            if (currentShape.Count <= 3)
            {
                Log.WriteLog("Didn't find enough points to turn into a polygon. Probably an error in the source data.", Log.VerbosityLevels.Errors);
                return(null);
            }

            var poly = factory.CreatePolygon(currentShape.Select(s => new Coordinate((float)s.Longitude, (float)s.Latitude)).ToArray());

            poly = GeometrySupport.CCWCheck(poly);
            if (poly == null)
            {
                Log.WriteLog("Found a shape that isn't CCW either way. Error.", Log.VerbosityLevels.Errors);
                return(null);
            }
            return(poly);
        }