public ActionResult GetPlaceInfo(long sourceElementId, int sourceElementType) { var db = new PraxisContext(); var area = db.Places.Include(e => e.Tags).FirstOrDefault(e => e.SourceItemID == sourceElementId && e.SourceItemType == sourceElementType); if (area == null) { return(View()); } TagParser.ApplyTags(new System.Collections.Generic.List <DbTables.Place>() { area }, "mapTiles"); ViewBag.areaname = TagParser.GetPlaceName(area.Tags); ViewBag.type = area.GameElementName; var geoarea = Converters.GeometryToGeoArea(area.ElementGeometry.Envelope); geoarea = new Google.OpenLocationCode.GeoArea(geoarea.SouthLatitude - ConstantValues.resolutionCell10, geoarea.WestLongitude - ConstantValues.resolutionCell10, geoarea.NorthLatitude + ConstantValues.resolutionCell10, geoarea.EastLongitude + ConstantValues.resolutionCell10); //add some padding to the edges. ImageStats istats = new ImageStats(geoarea, (int)(geoarea.LongitudeWidth / ConstantValues.resolutionCell11Lon), (int)(geoarea.LatitudeHeight / ConstantValues.resolutionCell11Lat)); //sanity check: we don't want to draw stuff that won't fit in memory, so check for size and cap it if needed if (istats.imageSizeX * istats.imageSizeY > 8000000) { var ratio = geoarea.LongitudeWidth / geoarea.LatitudeHeight; //W:H, var newSize = (istats.imageSizeY > 2000 ? 2000 : istats.imageSizeY); istats = new ImageStats(geoarea, (int)(newSize * ratio), newSize); } System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); //var tileSvg = MapTiles.DrawAreaAtSizeSVG(istats); ViewBag.UseSvg = true; var tile = MapTiles.DrawAreaAtSize(istats); ViewBag.UseSvg = false; sw.Stop(); ViewBag.imageString = "data:image/png;base64," + Convert.ToBase64String(tile); //ViewBag.imageString = tileSvg.Substring(39); //skip the <xml> tag ViewBag.timeToDraw = sw.Elapsed; ViewBag.placeCount = 0; ViewBag.areasByType = ""; var places = Place.GetPlaces(istats.area); ViewBag.placeCount = places.Count(); var grouped = places.GroupBy(p => p.GameElementName); string areasByType = ""; foreach (var g in grouped) { areasByType += g.Key + g.Count() + "<br />"; } ViewBag.areasByType = areasByType; return(View()); }
/// <summary> /// Convert main DB OSM elements into compact estimated offline DB place info records. /// </summary> /// <param name="allPlaces">the list of elements to copy into the standalone DB</param> /// <returns>a list of PlaceInfo items to use in the standalone DB</returns> public static List <PlaceInfo2> GetPlaceInfo(List <DbTables.Place> allPlaces) { var results = new List <PlaceInfo2>(); foreach (var place in allPlaces) //.Where(p => p.IsGameElement)) { var center = place.ElementGeometry.Centroid.Coordinate; //The less symmetrical an area's envelope is, the less accurate this guess is. But that's the tradeoff i'm making //to get this all self-contained in the least amount of space. / 4 because (/2 for average, then /2 for radius instead of diameter) //Circular radius was replaced with square envelope. it's 1 extra double to store per row to do the envelope check this way, and looks more reasonable. //var calcRadius = (place.elementGeometry.EnvelopeInternal.Width + place.elementGeometry.EnvelopeInternal.Height) / 4; var pi = new PlaceInfo2() { Name = TagParser.GetPlaceName(place.Tags), areaType = place.GameElementName, latCenter = center.Y, lonCenter = center.X, height = place.ElementGeometry.EnvelopeInternal.Height, width = place.ElementGeometry.EnvelopeInternal.Width, PlaceId = place.SourceItemID }; //Make points 1 Cell10 in size, so they're detectable. if (pi.height == 0) { pi.height = .000125; } if (pi.width == 0) { pi.width = .000125; } results.Add(pi); } return(results); }
//These functions are for taking a PraxisMapper DB and making a workable mobile version. Much less accurate, requires 0 data connectivity. //No longer a high priority but I do want to keep this code around. public static void CreateStandaloneDB(long relationID = 0, GeoArea bounds = null, bool saveToDB = false, bool saveToFolder = true) { //TODO: could rename TerrainInfo to TrailInfo, terrainDataSmall to trailData string name = ""; if (bounds != null) { name = Math.Truncate(bounds.SouthLatitude) + "_" + Math.Truncate(bounds.WestLongitude) + "_" + Math.Truncate(bounds.NorthLatitude) + "_" + Math.Truncate(bounds.EastLongitude) + ".sqlite"; } if (relationID > 0) { name = relationID.ToString() + ".sqlite"; } if (File.Exists(name)) { File.Delete(name); } var mainDb = new PraxisContext(); var sqliteDb = new StandaloneContext(relationID.ToString()); sqliteDb.ChangeTracker.AutoDetectChangesEnabled = false; sqliteDb.Database.EnsureCreated(); Log.WriteLog("Standalone DB created for relation " + relationID + " at " + DateTime.Now); GeoArea buffered; if (relationID > 0) { var fullArea = mainDb.Places.FirstOrDefault(m => m.SourceItemID == relationID && m.SourceItemType == 3); if (fullArea == null) { return; } buffered = Converters.GeometryToGeoArea(fullArea.ElementGeometry); //This should also be able to take a bounding box in addition in the future. } else { buffered = bounds; } //TODO: set a flag to allow this to pull straight from a PBF file? List <DbTables.Place> allPlaces = new List <DbTables.Place>(); var intersectCheck = Converters.GeoAreaToPolygon(buffered); bool pullFromPbf = false; //Set via arg at startup? or setting file? if (!pullFromPbf) { allPlaces = PraxisCore.Place.GetPlaces(buffered); } else { //need a file to read from. //optionally a bounding box on that file. //Starting to think i might want to track some generic parameters I refer to later. like -box|s|w|n|e or -point|lat|long or -singleFile|here.osm.pbf //allPlaces = PbfFileParser.ProcessSkipDatabase(); } Log.WriteLog("Loaded all intersecting geometry at " + DateTime.Now); string minCode = new OpenLocationCode(buffered.SouthLatitude, buffered.WestLongitude).CodeDigits; string maxCode = new OpenLocationCode(buffered.NorthLatitude, buffered.EastLongitude).CodeDigits; int removableLetters = 0; for (int i = 0; i < 10; i++) { if (minCode[i] == maxCode[i]) { removableLetters++; } else { i += 10; } } string commonStart = minCode.Substring(0, removableLetters); var wikiList = allPlaces.Where(a => a.Tags.Any(t => t.Key == "wikipedia") && TagParser.GetPlaceName(a.Tags) != "").Select(a => TagParser.GetPlaceName(a.Tags)).Distinct().ToList(); //Leaving this nearly wide open, since it's not the main driver of DB size. var basePlaces = allPlaces.Where(a => TagParser.GetPlaceName(a.Tags) != "" || a.GameElementName != "unmatched").ToList(); //.Where(a => a.name != "").ToList();// && (a.IsGameElement || wikiList.Contains(a.name))).ToList(); var distinctNames = basePlaces.Select(p => TagParser.GetPlaceName(p.Tags)).Distinct().ToList(); //This distinct might be causing things in multiple pieces to only detect one of them, not all of them? var placeInfo = PraxisCore.Standalone.Standalone.GetPlaceInfo(basePlaces); //Remove trails later. //SHORTCUT: for roads that are a straight-enough line (under 1 Cell10 in width or height) //just treat them as being 1 Cell10 in that axis, and skip tracking them by each Cell10 they cover. HashSet <long> skipEntries = new HashSet <long>(); foreach (var pi in placeInfo.Where(p => p.areaType == "road" || p.areaType == "trail")) { //If a road is nearly a straight line, treat it as though it was 1 cell10 wide, and don't index its coverage per-cell later. if (pi.height <= ConstantValues.resolutionCell10 && pi.width >= ConstantValues.resolutionCell10) { pi.height = ConstantValues.resolutionCell10; skipEntries.Add(pi.PlaceId); } else if (pi.height >= ConstantValues.resolutionCell10 && pi.width <= ConstantValues.resolutionCell10) { pi.width = ConstantValues.resolutionCell10; skipEntries.Add(pi.PlaceId); } } sqliteDb.PlaceInfo2s.AddRange(placeInfo); sqliteDb.SaveChanges(); Log.WriteLog("Processed geometry at " + DateTime.Now); var placeDictionary = placeInfo.ToDictionary(k => k.PlaceId, v => v); //to save time, i need to index which areas are in which Cell6. //So i know which entries I can skip when running. var indexCell6 = PraxisCore.Standalone.Standalone.IndexAreasPerCell6(buffered, basePlaces); var indexes = indexCell6.SelectMany(i => i.Value.Select(v => new PlaceIndex() { PlusCode = i.Key, placeInfoId = placeDictionary[v.SourceItemID].id })).ToList(); sqliteDb.PlaceIndexs.AddRange(indexes); sqliteDb.SaveChanges(); Log.WriteLog("Processed Cell6 index table at " + DateTime.Now); //trails need processed the old way, per Cell10, when they're not simply a straight-line. //Roads too. var tdSmalls = new Dictionary <string, TerrainDataSmall>(); //Possible issue: a trail and a road with the same name would only show up as whichever one got in the DB first. var toRemove = new List <PlaceInfo2>(); foreach (var trail in basePlaces.Where(p => (p.GameElementName == "trail" || p.GameElementName == "road"))) //TODO: add rivers here? { if (skipEntries.Contains(trail.SourceItemID)) { continue; //Don't per-cell index this one, we shifted it's envelope to handle it instead. } if (TagParser.GetPlaceName(trail.Tags) == "") { continue; //So sorry, but there's too damn many roads without names inflating DB size without being useful as-is. } var p = placeDictionary[trail.SourceItemID]; toRemove.Add(p); GeoArea thisPath = Converters.GeometryToGeoArea(trail.ElementGeometry); List <DbTables.Place> oneEntry = new List <DbTables.Place>(); oneEntry.Add(trail); var overlapped = AreaTypeInfo.SearchArea(ref thisPath, ref oneEntry); if (overlapped.Count() > 0) { tdSmalls.TryAdd(TagParser.GetPlaceName(trail.Tags), new TerrainDataSmall() { Name = TagParser.GetPlaceName(trail.Tags), areaType = trail.GameElementName }); } foreach (var o in overlapped) { var ti = new TerrainInfo(); ti.PlusCode = o.Key.Substring(removableLetters, 10 - removableLetters); ti.TerrainDataSmall = new List <TerrainDataSmall>(); ti.TerrainDataSmall.Add(tdSmalls[o.Value.Name]); sqliteDb.TerrainInfo.Add(ti); } sqliteDb.SaveChanges(); } foreach (var r in toRemove.Distinct()) { sqliteDb.PlaceInfo2s.Remove(r); } sqliteDb.SaveChanges(); Log.WriteLog("Trails processed at " + DateTime.Now); //make scavenger hunts var sh = PraxisCore.Standalone.Standalone.GetScavengerHunts(allPlaces); sqliteDb.ScavengerHunts.AddRange(sh); sqliteDb.SaveChanges(); Log.WriteLog("Auto-created scavenger hunt entries at " + DateTime.Now); var swCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MinY, intersectCheck.EnvelopeInternal.MinX); var neCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MaxY, intersectCheck.EnvelopeInternal.MaxX); //insert default entries for a new player. sqliteDb.PlayerStats.Add(new PlayerStats() { timePlayed = 0, distanceWalked = 0, score = 0 }); sqliteDb.Bounds.Add(new Bounds() { EastBound = neCorner.Decode().EastLongitude, NorthBound = neCorner.Decode().NorthLatitude, SouthBound = swCorner.Decode().SouthLatitude, WestBound = swCorner.Decode().WestLongitude, commonCodeLetters = commonStart, BestIdleCompletionTime = 0, LastPlayedOn = 0, StartedCurrentIdleRun = 0 }); sqliteDb.IdleStats.Add(new IdleStats() { emptySpacePerSecond = 0, emptySpaceTotal = 0, graveyardSpacePerSecond = 0, graveyardSpaceTotal = 0, natureReserveSpacePerSecond = 0, natureReserveSpaceTotal = 0, parkSpacePerSecond = 0, parkSpaceTotal = 0, touristSpacePerSecond = 0, touristSpaceTotal = 0, trailSpacePerSecond = 0, trailSpaceTotal = 0 }); sqliteDb.SaveChanges(); //now we have the list of places we need to be concerned with. System.IO.Directory.CreateDirectory(relationID + "Tiles"); PraxisCore.Standalone.Standalone.DrawMapTilesStandalone(relationID, buffered, allPlaces, saveToFolder); sqliteDb.SaveChanges(); Log.WriteLog("Maptiles drawn at " + DateTime.Now); //Copy the files as necessary to their correct location. //if (saveToFolder) //Directory.Move(relationID + "Tiles", config["Solar2dExportFolder"] + "Tiles"); //File.Copy(relationID + ".sqlite", config["Solar2dExportFolder"] + "database.sqlite"); Log.WriteLog("Standalone gameplay DB done."); }
/// <summary> /// Auto-create some scavenger hunt entries for a standalone game DB. Makes a list for entries that have a wikipedia tag, /// and then entries for all styles in the first TagParser style that have IsGameElement set to true. /// </summary> /// <param name="allPlaces">the list of OSM elements to search</param> /// <returns>the list of scavenger hunt entries</returns> public static List <ScavengerHuntStandalone> GetScavengerHunts(List <DbTables.Place> allPlaces) { //TODO: This is going to need updated for the new TagParser rules. testing at the minimum var results = new List <ScavengerHuntStandalone>(); var wikiList = allPlaces.Where(a => a.Tags.Any(t => t.Key == "wikipedia") && TagParser.GetPlaceName(a.Tags) != "").Select(a => TagParser.GetPlaceName(a.Tags)).Distinct().ToList(); //Create automatic scavenger hunt entries. Dictionary <string, List <string> > scavengerHunts = new Dictionary <string, List <string> >(); //NOTE: //If i run this by elementID, i get everything unique but several entries get duplicated becaues they're in multiple pieces. //If I run this by name, the lists are much shorter but visiting one distinct location might count for all of them (This is a bigger concern with very large areas or retail establishment) //So I'm going to run this by name for the player's sake. scavengerHunts.Add("Wikipedia Places", wikiList); Log.WriteLog(wikiList.Count() + " Wikipedia-linked items found for scavenger hunt."); //fill in gameElement lists. foreach (var gameElementTags in TagParser.allStyleGroups.First().Value.Where(s => s.Value.IsGameElement)) { var foundElements = allPlaces.Where(a => TagParser.MatchOnTags(gameElementTags.Value, a.Tags) && !string.IsNullOrWhiteSpace(TagParser.GetPlaceName(a.Tags))).Select(a => TagParser.GetPlaceName(a.Tags)).Distinct().ToList(); scavengerHunts.Add(gameElementTags.Value.Name, foundElements); Log.WriteLog(foundElements.Count() + " " + gameElementTags.Value.Name + " items found for scavenger hunt."); } foreach (var hunt in scavengerHunts) { foreach (var item in hunt.Value) { results.Add(new ScavengerHuntStandalone() { listName = hunt.Key, description = item, playerHasVisited = false }); } } return(results); }