/// <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. }
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); }
/// <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); } }
/// <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); }