/// <summary>
        /// Get a link to a Place's wikipedia page, if tagged with a wiki tag.
        /// </summary>
        /// <param name="element">the Place with tags to check</param>
        /// <returns>a link to the relevant Wikipedia page for an element, or an empty string if the element has no such tag.</returns>
        public static string GetWikipediaLink(DbTables.Place element)
        {
            var wikiTag = element.Tags.FirstOrDefault(t => t.Key == "wikipedia");

            if (wikiTag == null)
            {
                return("");
            }

            string[] splitValue = wikiTag.Value.Split(":");
            return("https://" + splitValue[0] + ".wikipedia.org/wiki/" + splitValue[1]); //TODO: check if special characters need replaced or encoded on this.
        }
Exemple #2
0
        public static DbTables.Place ConvertSingleTsvPlace(string sw)
        {
            var source = sw.AsSpan();

            DbTables.Place entry = new DbTables.Place();
            entry.SourceItemID    = source.SplitNext('\t').ToLong();
            entry.SourceItemType  = source.SplitNext('\t').ToInt();
            entry.ElementGeometry = GeometryFromWKT(source.SplitNext('\t').ToString());
            entry.AreaSize        = source.SplitNext('\t').ToDouble();
            entry.PrivacyId       = Guid.Parse(source);

            return(entry);
        }
Exemple #3
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);
            }
        }
Exemple #4
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);
        }