示例#1
0
        /// <summary>
        /// Returns the smallest (most-important) element in a list, to identify which element a client should use.
        /// </summary>
        /// <param name="entriesHere">the list of elements to pull data from</param>
        /// <returns>the name, areatype, and client facing ID of the OSM element to use</returns>
        public static TerrainData DetermineAreaPlace(List <DbTables.Place> entriesHere)
        {
            //Which Place in this given Area is the one that should be displayed on the game/map as the name? picks the smallest one.
            //This one only returns the smallest entry, for games that only need to check the most interesting area in a cell.
            var entry = entriesHere.Last();

            return(new TerrainData()
            {
                Name = TagParser.GetPlaceName(entry.Tags), areaType = entry.GameElementName, PrivacyId = entry.PrivacyId
            });
        }
示例#2
0
        /// <summary>
        /// Scores the list of places given in full
        /// </summary>
        /// <param name="places">The places to report a score for each.</param>
        /// <returns>a string of names and scores, </returns>
        public static string GetScoresForFullArea(List <DbTables.Place> places)
        {
            //As above, but counts the Places' full area, not the area in the given Cell8 or Cell10.
            List <Tuple <string, long, Guid> > areaSizes = new List <Tuple <string, long, Guid> >(places.Count());

            foreach (var place in places)
            {
                areaSizes.Add(Tuple.Create(TagParser.GetPlaceName(place.Tags), GetScoreForSinglePlace(place.ElementGeometry), place.PrivacyId));
            }
            return(string.Join("\r\n", areaSizes.Select(a => a.Item1 + "|" + a.Item2 + "|" + a.Item3)));
        }
        public void UpdateExistingEntries(List <DbTables.Place> entries)
        {
            foreach (var entry in entries)
            {
                //check existing entry, see if it requires being updated
                var existingData = Places.FirstOrDefault(md => md.SourceItemID == entry.SourceItemID && md.SourceItemType == entry.SourceItemType);
                if (existingData != null)
                {
                    if (existingData.AreaSize != entry.AreaSize)
                    {
                        existingData.AreaSize = entry.AreaSize;
                    }
                    if (existingData.GameElementName != entry.GameElementName)
                    {
                        existingData.GameElementName = entry.GameElementName;
                    }

                    bool expireTiles = false;
                    if (!existingData.ElementGeometry.EqualsTopologically(entry.ElementGeometry)) //TODO: this might need to be EqualsExact?
                    {
                        //update the geometry for this object.
                        existingData.ElementGeometry = entry.ElementGeometry;
                        expireTiles = true;
                    }
                    if (!existingData.Tags.OrderBy(t => t.Key).SequenceEqual(entry.Tags.OrderBy(t => t.Key)))
                    {
                        existingData.Tags = entry.Tags;
                        var styleA = TagParser.GetStyleForOsmWay(existingData.Tags);
                        var styleB = TagParser.GetStyleForOsmWay(entry.Tags);
                        if (styleA != styleB)
                        {
                            expireTiles = true; //don't force a redraw on tags unless we change our drawing rules.
                        }
                    }

                    if (expireTiles)   //geometry or style has to change, otherwise we can skip expiring values.
                    {
                        SaveChanges(); //save before expiring, so the next redraw absolutely has the latest data. Can't catch it mid-command if we do this first.
                        ExpireMapTiles(entry.ElementGeometry, "mapTiles");
                        ExpireSlippyMapTiles(entry.ElementGeometry, "mapTiles");
                    }
                }
                else
                {
                    //We don't have this item, add it.
                    Places.Add(entry);
                    SaveChanges(); //again, necessary here to get tiles to draw correctly after expiring.
                    ExpireMapTiles(entry.ElementGeometry, "mapTiles");
                    ExpireSlippyMapTiles(entry.ElementGeometry, "mapTiles");
                }
            }
            SaveChanges(); //final one for anything not yet persisted.
        }
示例#4
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);
        }
示例#5
0
        /// <summary>
        /// Get the areatype (as defined by TagParser) for each OSM element in the list, along with name and client-facing ID.
        /// </summary>
        /// <param name="entriesHere">the list of elements to pull data from</param>
        /// <returns>A list of name, areatype, and elementIds for a client</returns>
        public static List <TerrainData> DetermineAreaPlaces(List <DbTables.Place> entriesHere)
        {
            //Which Place in this given Area is the one that should be displayed on the game/map as the name? picks the smallest one.
            //This one return all entries, for a game mode that might need all of them.
            var results = new List <TerrainData>(entriesHere.Count());

            foreach (var e in entriesHere)
            {
                results.Add(new TerrainData()
                {
                    Name = TagParser.GetPlaceName(e.Tags), areaType = e.GameElementName, PrivacyId = e.PrivacyId
                });
            }
            return(results);
        }
示例#6
0
        //TODO: these might be slightly faster with StringBuilder during calculations than String.Join after doing all the calculation. Test and check that assumption.
        //Default Scoring rules:
        //Each Cell10 of surface area is 1 Score (would be Points in any other game, but Points is already an overloaded term in this system).
        //OSM Areas are measured in square area, divided by Cell10 area squared. (An area that covers 25 square Cell10s is 25 Score)
        //Lines are measured in their length.  (A trail that's 25 * resolutionCell10 long is 25 Score)
        //OSM Points (single lat/lon pair) are assigned a Score of 1 as the minimum interactable size object.

        /// <summary>
        /// Get a list of element names, score, and client-facing IDs from a list of places and an area to search, cropped to that area.
        /// </summary>
        /// <param name="areaPoly">the area to search and use to determine scores of elements intersecting it  </param>
        /// <param name="places">the elements to be scored, relative to their size in the given area</param>
        /// <returns>a string of pipe-separated values (name, score, ID) split by newlines</returns>
        public static string GetScoresForArea(Geometry areaPoly, List <DbTables.Place> places)
        {
            //Determines the Scores for the Places, limited to the intersection of the current Area. 1 Cell10 = 1 Score.
            //EX: if a park overlaps 800 Cell10s, but the current area overlaps 250 of them, this returns 250 for that park.
            //Lists each Place and its corresponding Score.
            List <Tuple <string, long, Guid> > areaSizes = new List <Tuple <string, long, Guid> >(places.Count());

            foreach (var md in places)
            {
                var containedArea   = md.ElementGeometry.Intersection(areaPoly);
                var areaCell10Count = GetScoreForSinglePlace(containedArea);
                areaSizes.Add(new Tuple <string, long, Guid>(TagParser.GetPlaceName(md.Tags), areaCell10Count, md.PrivacyId));
            }
            return(string.Join("\r\n", areaSizes.Select(a => a.Item1 + "|" + a.Item2 + "|" + a.Item3)));
        }
示例#7
0
        /// <summary>
        /// Create a database StoredOsmElement from an OSMSharp Complete object.
        /// </summary>
        /// <param name="g">the CompleteOSMGeo object to prepare to save to the DB</param>
        /// <returns>the StoredOsmElement ready to save to the DB</returns>
        public static DbTables.Place ConvertOsmEntryToStoredElement(OsmSharp.Complete.ICompleteOsmGeo g)
        {
            var tags = TagParser.getFilteredTags(g.Tags);

            if (tags == null || tags.Count() == 0)
            {
                return(null); //For nodes, don't store every untagged node.
            }
            try
            {
                var geometry = featureInterpreter.Interpret(g);
                if (geometry == null)
                {
                    Log.WriteLog("Error: " + g.Type.ToString() + " " + g.Id + "-" + TagParser.GetPlaceName(g.Tags) + " didn't interpret into a Geometry object", Log.VerbosityLevels.Errors);
                    return(null);
                }
                var sw = new DbTables.Place();
                sw.SourceItemID   = g.Id;
                sw.SourceItemType = (g.Type == OsmGeoType.Relation ? 3 : g.Type == OsmGeoType.Way ? 2 : 1);
                var geo = SimplifyArea(geometry);
                if (geo == null)
                {
                    Log.WriteLog("Error: " + g.Type.ToString() + " " + g.Id + " didn't simplify for some reason.", Log.VerbosityLevels.Errors);
                    return(null);
                }
                geo.SRID           = 4326;//Required for SQL Server to accept data.
                sw.ElementGeometry = geo;
                sw.Tags            = tags;
                if (sw.ElementGeometry.GeometryType == "LinearRing" || (sw.ElementGeometry.GeometryType == "LineString" && sw.ElementGeometry.Coordinates.First() == sw.ElementGeometry.Coordinates.Last()))
                {
                    //I want to update all LinearRings to Polygons, and let the style determine if they're Filled or Stroked.
                    var poly = factory.CreatePolygon((LinearRing)sw.ElementGeometry);
                    sw.ElementGeometry = poly;
                }
                sw.AreaSize = sw.ElementGeometry.Length > 0 ? sw.ElementGeometry.Length : resolutionCell10;
                return(sw);
            }
            catch (Exception ex)
            {
                Log.WriteLog("Error: Item " + g.Id + " failed to process. " + ex.Message);
                return(null);
            }
        }
示例#8
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());
        }
示例#9
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);
        }
示例#10
0
        /// <summary>
        /// A debugging function, writres some information an element using its OSM id (or internal primary key) to load from the database.
        /// </summary>
        /// <param name="id">the PlaceId or SourceElementId of an area to load.</param>
        /// <returns>a string with some details on the area in question.</returns>
        public static string LoadDataOnPlace(long id)
        {
            //Debugging helper call. Loads up some information on an area and display it.
            //Not currently used anywhere.
            var    db      = new PraxisContext();
            var    entries = db.Places.Where(m => m.Id == id || m.SourceItemID == id).ToList();
            string results = "";

            foreach (var entry in entries)
            {
                var shape = entry.ElementGeometry;

                results += "Name: " + TagParser.GetPlaceName(entry.Tags) + Environment.NewLine;
                results += "Game Type: " + TagParser.GetAreaType(entry.Tags.ToList()) + Environment.NewLine;
                results += "Geometry Type: " + shape.GeometryType + Environment.NewLine;
                results += "OSM Type: " + entry.SourceItemType + Environment.NewLine;
                results += "Area Tags: " + String.Join(",", entry.Tags.Select(t => t.Key + ":" + t.Value));
                results += "IsValid? : " + shape.IsValid + Environment.NewLine;
                results += "Area: " + shape.Area + Environment.NewLine; //Not documented, but I believe this is the area in square degrees. Is that a real unit?
                results += "As Text: " + shape.AsText() + Environment.NewLine;
            }

            return(results);
        }
示例#11
0
 } = Guid.NewGuid();                                   //Pass this Id to clients, so we can attempt to block attaching players to locations in the DB.
 public override string ToString()
 {
     return((SourceItemType == 3 ? "Relation " : SourceItemType == 2 ? "Way " : "Node ") + SourceItemID.ToString() + TagParser.GetPlaceName(Tags));
 }