Exemplo n.º 1
0
        public static bool SetSecureAreaData(string plusCode, string key, byte[] value, string password, double?expiration = null)
        {
            var db = new PraxisContext();

            byte[] encryptedValue = EncryptValue(value, password, out byte[] IVs);

            var row = db.AreaGameData.FirstOrDefault(p => p.PlusCode == plusCode && p.DataKey == key);

            if (row == null)
            {
                row              = new DbTables.AreaGameData();
                row.DataKey      = key;
                row.PlusCode     = plusCode;
                row.GeoAreaIndex = Converters.GeoAreaToPolygon(OpenLocationCode.DecodeValid(plusCode.ToUpper()));
                db.AreaGameData.Add(row);
            }
            if (expiration.HasValue)
            {
                row.Expiration = DateTime.Now.AddSeconds(expiration.Value);
            }
            else
            {
                row.Expiration = null;
            }

            row.DataValue = encryptedValue;
            row.IvData    = IVs;
            return(db.SaveChanges() == 1);
        }
Exemplo n.º 2
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());
        }
Exemplo n.º 3
0
        public static bool SetAreaData(string plusCode, string key, string value, double?expiration = null)
        {
            var db = new PraxisContext();

            if (db.PlayerData.Any(p => p.DeviceID == key || p.DeviceID == value))
            {
                return(false);
            }

            var row = db.AreaGameData.FirstOrDefault(p => p.PlusCode == plusCode && p.DataKey == key);

            if (row == null)
            {
                row              = new DbTables.AreaGameData();
                row.DataKey      = key;
                row.PlusCode     = plusCode;
                row.GeoAreaIndex = Converters.GeoAreaToPolygon(OpenLocationCode.DecodeValid(plusCode.ToUpper()));
                db.AreaGameData.Add(row);
            }
            if (expiration.HasValue)
            {
                row.Expiration = DateTime.Now.AddSeconds(expiration.Value);
            }
            else
            {
                row.Expiration = null;
            }
            row.IvData    = null;
            row.DataValue = value.ToByteArrayUTF8();
            return(db.SaveChanges() == 1);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Determine if a GeoArea (presumably from a PlusCode) intersects with the data contained in the server.
        /// </summary>
        /// <param name="bounds">PreparedGeometry representing the server's usable boundaries</param>
        /// <param name="place">GeoArea to check against the server's bounds</param>
        /// <returns>true if the 2 parameters intersect, or false if they do not.</returns>
        public static bool IsInBounds(IPreparedGeometry bounds, GeoArea place)
        {
            if (DisableBoundsCheck || bounds.Intersects(Converters.GeoAreaToPolygon(place)))
            {
                return(true);
            }

            return(false);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Load all of the key/value pairs in a PlusCode, including pairs saved to a longer PlusCode. Expired and encrypted entries are ignored.
        /// (EX: calling this with an 8 character PlusCode will load all contained 10 character PlusCodes key/value pairs)
        /// </summary>
        /// <param name="plusCode">A valid PlusCode, excluding the + symbol.</param>
        /// <param name="key">If supplied, only returns data on the given key for the area provided. If blank, returns all keys</param>
        /// <returns>a List of results with the PlusCode, keys, and values</returns>
        public static List <CustomDataAreaResult> GetAllDataInArea(string plusCode, string key = "")
        {
            var db           = new PraxisContext();
            var plusCodeArea = OpenLocationCode.DecodeValid(plusCode);
            var plusCodePoly = Converters.GeoAreaToPolygon(plusCodeArea);
            var plusCodeData = db.AreaGameData.Where(d => plusCodePoly.Intersects(d.GeoAreaIndex) && (key == "" || d.DataKey == key) && d.IvData == null)
                               .ToList() //Required to run the next Where on the C# side
                               .Where(row => row.Expiration.GetValueOrDefault(DateTime.MaxValue) > DateTime.Now)
                               .Select(d => new CustomDataAreaResult(d.PlusCode, d.DataKey, d.DataValue.ToUTF8String()))
                               .ToList();

            return(plusCodeData);
        }
Exemplo n.º 6
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);
        }
Exemplo n.º 7
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);
        }
Exemplo n.º 8
0
        //Note: This should have the padding added to area before this is called, if checking for tiles that need regenerated.
        /// <summary>
        /// Checks if places existing in the DB for the given area. Will almost always return true, given admin boundaries are loaded into a database. Used for finding server boundaries and if an area needs maptiles drawn.
        /// </summary>
        /// <param name="area">The area to check for elements</param>
        /// <param name="source">an optional list to use instead of loading from the database.</param>
        /// <returns>true if any Places intersect the given GeoArea, false if not.</returns>
        public static bool DoPlacesExist(GeoArea area, List <DbTables.Place> source = null)
        {
            //As GetPlaces, but only checks if there are entries.
            var  location = Converters.GeoAreaToPolygon(area);
            bool places;

            if (source == null)
            {
                var db = new PraxisContext();
                places = db.Places.Any(md => md.ElementGeometry.Intersects(location));
                return(places);
            }
            else
            {
                places = source.Any(md => md.ElementGeometry.Intersects(location));
            }
            return(places);
        }
Exemplo n.º 9
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);
        }
Exemplo n.º 10
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);
        }
Exemplo n.º 11
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());
        }
Exemplo n.º 12
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.");
        }
Exemplo n.º 13
0
        /// <summary>
        /// Auto-generate some places to be used as gameplay elements in otherwise sparse areas. Given an 8 digit PlusCode, creates and warps some standard shapes in the Cell.
        /// </summary>
        /// <param name="plusCode">The area to generate shape(s) in</param>
        /// <param name="autoSave">If true, saves the areas to the database immediately.</param>
        /// <returns>The list of places created for the given area.</returns>
        public static List <DbTables.Place> CreateInterestingPlaces(string plusCode, bool autoSave = true)
        {
            //expected to receive a Cell8
            // populate it with some interesting regions for players.
            Random   r          = new Random();
            CodeArea cell8      = OpenLocationCode.DecodeValid(plusCode); //Reminder: area is .0025 degrees on a Cell8
            int      shapeCount = 1;                                      // 2; //number of shapes to apply to the Cell8
            double   shapeWarp  = .3;                                     //percentage a shape is allowed to have each vertexs drift by.
            List <DbTables.Place> areasToAdd = new List <DbTables.Place>();

            for (int i = 0; i < shapeCount; i++)
            {
                //Pick a shape
                var masterShape     = possibleShapes.OrderBy(s => r.Next()).First();
                var shapeToAdd      = masterShape.Select(s => new Coordinate(s)).ToList();
                var scaleFactor     = r.Next(10, 36) * .01; //Math.Clamp(r.Next, .1, .35); //Ensure that we get a value that isn't terribly useless. 2 shapes can still cover 70%+ of an empty area this way.
                var positionFactorX = r.NextDouble() * resolutionCell8;
                var positionFactorY = r.NextDouble() * resolutionCell8;
                foreach (Coordinate c in shapeToAdd)
                {
                    //scale it to our resolution
                    c.X *= resolutionCell8;
                    c.Y *= resolutionCell8;

                    //multiply this by some factor smaller than 1, so it doesn't take up the entire Cell
                    //If we use NextDouble() here, it scales each coordinate randomly, which would look very unpredictable. Use the results of one call twice to scale proportionally.
                    //but ponder how good/bad it looks for various shapes if each coordinate is scaled differently.
                    c.X *= scaleFactor;
                    c.Y *= scaleFactor;

                    //Rotate the coordinate set some random number of degrees?
                    //TODO: how to rotate these?

                    //Place the shape somewhere randomly by adding the same X/Y value less than the resolution to each point
                    c.X += positionFactorX;
                    c.Y += positionFactorY;

                    //Fuzz each vertex by adding some random distance on each axis less than 30% of the cell's size in either direction.
                    //10% makes the shapes much more recognizable, but not as interesting. Will continue looking into parameters here to help adjust that.
                    c.X += (r.NextDouble() * resolutionCell8 * shapeWarp) * (r.Next() % 2 == 0 ? 1 : -1);
                    c.Y += (r.NextDouble() * resolutionCell8 * shapeWarp) * (r.Next() % 2 == 0 ? 1 : -1);

                    //Let us know if this shape overlaps a neighboring cell. We probably want to make sure we re-draw map tiles if it does.
                    if (c.X > .0025 || c.Y > .0025)
                    {
                        Log.WriteLog("Coordinate for shape " + i + " in Cell8 " + plusCode + " will be out of bounds: " + c.X + " " + c.Y, Log.VerbosityLevels.High);
                    }

                    //And now add the minimum values for the given Cell8 to finish up coordinates.
                    c.X += cell8.Min.Longitude;
                    c.Y += cell8.Min.Latitude;
                }

                //ShapeToAdd now has a randomized layout, convert it to a polygon.
                shapeToAdd.Add(shapeToAdd.First()); //make it a closed shape
                var polygon = factory.CreatePolygon(shapeToAdd.ToArray());
                polygon = CCWCheck(polygon);        //Sometimes squares still aren't CCW? or this gets un-done somewhere later?
                if (!polygon.IsValid || !polygon.Shell.IsCCW)
                {
                    Log.WriteLog("Invalid geometry generated, retrying", Log.VerbosityLevels.High);
                    i--;
                    continue;
                }

                if (!polygon.CoveredBy(Converters.GeoAreaToPolygon(cell8)))
                {
                    //This should only ever require checking the map tile north/east of the current one, even though the vertex fuzzing can potentially move things negative slightly.
                    Log.WriteLog("This polygon is outside of the Cell8 by " + (cell8.Max.Latitude - shapeToAdd.Max(s => s.Y)) + "/" + (cell8.Max.Longitude - shapeToAdd.Max(s => s.X)), Log.VerbosityLevels.High);
                }
                if (polygon != null)
                {
                    DbTables.Place gmd = new DbTables.Place();
                    gmd.ElementGeometry = polygon;
                    gmd.GameElementName = "generated";
                    gmd.Tags.Add(new PlaceTags()
                    {
                        Key = "praxisGenerated", Value = "true"
                    });
                    areasToAdd.Add(gmd); //this is the line that makes some objects occasionally not be CCW that were CCW before. Maybe its the cast to the generic Geometry item?
                }
                else
                {
                    //Inform me that I did something wrong.
                    Log.WriteLog("failed to convert a randomized shape to a polygon.", Log.VerbosityLevels.Errors);
                    continue;
                }
            }

            //Making this function self-contained
            if (autoSave)
            {
                var db = new PraxisContext();
                foreach (var area in areasToAdd)
                {
                    area.ElementGeometry = CCWCheck((Polygon)area.ElementGeometry); //fixes errors that reappeared above
                }
                db.Places.AddRange(areasToAdd);
                db.SaveChanges();
            }

            return(areasToAdd);
        }
Exemplo n.º 14
0
        /// <summary>
        /// Creates an SVG image instead of a PNG file, but otherwise operates the same as DrawAreaAtSize.
        /// </summary>
        /// <param name="stats">the image properties to draw</param>
        /// <param name="drawnItems">the list of elements to draw. Will load from the database if null.</param>
        /// <param name="styles">a dictionary of TagParserEntries to select to draw</param>
        /// <param name="filterSmallAreas">if true, skips entries below a certain size when drawing.</param>
        /// <returns>a string containing the SVG XML</returns>
        public string DrawAreaAtSizeSVG(ImageStats stats, List <DbTables.Place> drawnItems = null, Dictionary <string, StyleEntry> styles = null, bool filterSmallAreas = true)
        {
            //TODO: make this take CompletePaintOps
            //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.

            if (styles == null)
            {
                styles = TagParser.allStyleGroups.First().Value;
            }

            double minimumSize = 0;

            if (filterSmallAreas)
            {
                minimumSize = stats.degreesPerPixelX; //don't draw elements under 1 pixel in size. at slippy zoom 12, this is approx. 1 pixel for a Cell10.
            }
            var db  = new PraxisContext();
            var geo = Converters.GeoAreaToPolygon(stats.area);

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

            //baseline image data stuff
            //SKBitmap bitmap = new SKBitmap(stats.imageSizeX, stats.imageSizeY, SKColorType.Rgba8888, SKAlphaType.Premul);
            var          bounds = new SKRect(0, 0, stats.imageSizeX, stats.imageSizeY);
            MemoryStream s      = new MemoryStream();
            SKCanvas     canvas = SKSvgCanvas.Create(bounds, s); //output not guaranteed to be complete until the canvas is deleted?!?
            //SKCanvas canvas = new SKCanvas(bitmap);
            var bgColor = SKColor.Parse(styles["background"].PaintOperations.First().HtmlColorCode);

            //Backgound is a named style, unmatched will be the last entry and transparent.
            canvas.Clear(bgColor);
            canvas.Scale(1, -1, stats.imageSizeX / 2, stats.imageSizeY / 2);
            SKPaint paint = new SKPaint();

            //I guess what I want here is a list of an object with an elementGeometry object for the shape, and a paintOp attached to it
            var pass1 = drawnItems.Select(d => new { d.AreaSize, d.ElementGeometry, paintOp = styles[d.GameElementName].PaintOperations });
            var pass2 = new List <CompletePaintOp>(drawnItems.Count() * 2);

            foreach (var op in pass1)
            {
                foreach (var po in op.paintOp)
                {
                    pass2.Add(new CompletePaintOp(op.ElementGeometry, op.AreaSize, po, "", po.LineWidthDegrees * stats.pixelsPerDegreeX));
                }
            }


            foreach (var w in pass2.OrderByDescending(p => p.paintOp.LayerId).ThenByDescending(p => p.areaSize))
            {
                paint = cachedPaints[w.paintOp.Id];
                if (paint.Color.Alpha == 0)
                {
                    continue; //This area is transparent, skip drawing it entirely.
                }
                if (stats.degreesPerPixelX > w.paintOp.MaxDrawRes || stats.degreesPerPixelX < w.paintOp.MinDrawRes)
                {
                    continue; //This area isn't drawn at this scale.
                }
                var path = new SKPath();
                switch (w.elementGeometry.GeometryType)
                {
                //Polygons without holes are super easy and fast: draw the path.
                //Polygons with holes require their own bitmap to be drawn correctly and then overlaid onto the canvas.
                //I want to use paths to fix things for performance reasons, but I have to use Bitmaps because paths apply their blend mode to
                //ALL elements already drawn, not just the last one.
                case "Polygon":
                    var p = w.elementGeometry as Polygon;

                    path.AddPoly(PolygonToSKPoints(p, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY));
                    foreach (var hole in p.InteriorRings)
                    {
                        path.AddPoly(PolygonToSKPoints(hole, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY));
                    }
                    canvas.DrawPath(path, paint);

                    break;

                case "MultiPolygon":
                    foreach (var p2 in ((MultiPolygon)w.elementGeometry).Geometries)
                    {
                        var p2p = p2 as Polygon;
                        path.AddPoly(PolygonToSKPoints(p2p, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY));
                        foreach (var hole in p2p.InteriorRings)
                        {
                            path.AddPoly(PolygonToSKPoints(hole, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY));
                        }
                        canvas.DrawPath(path, paint);
                    }
                    break;

                case "LineString":
                    var firstPoint = w.elementGeometry.Coordinates.First();
                    var lastPoint  = w.elementGeometry.Coordinates.Last();
                    var points     = PolygonToSKPoints(w.elementGeometry, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                    if (firstPoint.Equals(lastPoint))
                    {
                        //This is a closed shape. Check to see if it's supposed to be filled in.
                        if (paint.Style == SKPaintStyle.Fill)
                        {
                            path.AddPoly(points);
                            canvas.DrawPath(path, paint);
                            continue;
                        }
                    }
                    for (var line = 0; line < points.Length - 1; line++)
                    {
                        canvas.DrawLine(points[line], points[line + 1], paint);
                    }
                    break;

                case "MultiLineString":
                    foreach (var p3 in ((MultiLineString)w.elementGeometry).Geometries)
                    {
                        var points2 = PolygonToSKPoints(p3, stats.area, stats.degreesPerPixelX, stats.degreesPerPixelY);
                        for (var line = 0; line < points2.Length - 1; line++)
                        {
                            canvas.DrawLine(points2[line], points2[line + 1], paint);
                        }
                    }
                    break;

                case "Point":
                    var convertedPoint = PolygonToSKPoints(w.elementGeometry, 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))
                    {
                        SKBitmap icon = SKBitmap.Decode(TagParser.cachedBitmaps[w.paintOp.FileName]);     //TODO optimize by creating in Initialize
                        canvas.DrawBitmap(icon, convertedPoint[0]);
                    }
                    else
                    {
                        var circleRadius = (float)(ConstantValues.resolutionCell10 / stats.degreesPerPixelX / 2);     //I want points to be drawn as 1 Cell10 in diameter.
                        canvas.DrawCircle(convertedPoint[0], circleRadius, paint);
                    }
                    break;

                default:
                    Log.WriteLog("Unknown geometry type found, not drawn.");
                    break;
                }
            }
            canvas.Flush();
            canvas.Dispose();
            canvas     = null;
            s.Position = 0;
            var svgData = new StreamReader(s).ReadToEnd();

            return(svgData);
        }