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