public static string AgentPath(MapAgentPath path, bool addBoundingBoxex = true) { var result = new GeoJsonMultiLine(); for(int i = 0; i < path.Points.Count - 2; i++) { var p0 = path.Points[i]; var p1 = path.Points[i + 1]; var segment = new List<List<double>> { new List<double> { p0.Lon, p0.Lat }, new List<double> { p1.Lon, p1.Lat } }; result.Coordinates.Add(segment); } if (addBoundingBoxex) { foreach (var tileId in path.TileIds) { var bbox = new List<List<double>>(); var (lon0, lat0, lon1, lat1) = TileMath.GetTileBounds(tileId, WorldGraph.Zoom); bbox.Add(new List<double> { lon0, lat0 }); bbox.Add(new List<double> { lon1, lat0 }); bbox.Add(new List<double> { lon1, lat1 }); bbox.Add(new List<double> { lon0, lat1 }); bbox.Add(new List<double> { lon0, lat0 }); result.Coordinates.Add(bbox); } } return JsonSerializer.Serialize(result, SerializerOptions); }
void AddTile(WayTile tile) { if (_tiles.Any(t => t.Id == tile.Id)) { return; } _tiles.Add(tile); var(lon0, lat0, lon1, lat1) = TileMath.GetTileBounds(tile.Id, Zoom); foreach (var node in tile.Nodes) { node.TileId = tile.Id; node.VisitCount = 0; if (_nodeLut.TryGetValue(node.Id, out WayTileNode existingNode)) { // It is possible that both nodes are inside their own tile if they are exactly on the border. Which one wins doesn't matter. var insideNode = existingNode.Inside.HasValue ? existingNode : node; var outsideNode = existingNode.Inside.HasValue ? node : existingNode; // Node id already exists. This is a link between two tiles. The existing node should be outside its tile // and the new one should be inside. Otherwise something is wrong. // 1) Let the node inside its own tile inherit the connections of the other var tileLinkConnections = outsideNode.Conn.Where(id => insideNode.Conn.Any(idOther => idOther == id)).ToList(); insideNode.Conn.AddRange(tileLinkConnections); // 2) Overwrite the old one with the new one in the LUT. _nodeLut[node.Id] = insideNode; } else { _nodeLut[node.Id] = node; } } var outside = _nodeLut.Values.Where(v => !v.Inside.HasValue).ToList(); int loaded = 0; int notLoaded = 0; foreach (var n in outside) { if (!n.Inside.HasValue) { long tileId = TileMath.GetTileId(n.Point.Lon, n.Point.Lat, Zoom); if (_tiles.Any(t => t.Id == tileId)) { loaded++; } else { notLoaded++; } } // all outside nodes should be in tiles NOT loaded. } }
public async Task <WayTile> GetTile(long tileId, int zoom) { var bbox = TileMath.GetTileBounds(tileId, zoom); string dbgMsg = $"TileId {tileId}, zoom: {zoom}, bbox: ({bbox.lon0}, {bbox.lat0}, {bbox.lon1}, {bbox.lat1})"; _logger.LogInformation($"Loading: {dbgMsg}"); // https://wiki.openstreetmap.org/wiki/Bounding_Box // The order of values in the bounding box used by Overpass API is (South, West, North, East): // minimum latitude, minimum longitude, maximum latitude, maximum longitude string cmd = $"[out: json];way(if:is_tag(\"highway\") || is_tag(\"route\"))({NumberStr(bbox.lat1)}, {NumberStr(bbox.lon0)}, {NumberStr(bbox.lat0)}, {NumberStr(bbox.lon1)});out qt;node(w);out skel qt;"; var client = new HttpClient(); var content = new StringContent(cmd); OsmResponse osmResponse = null; string body = ""; HttpResponseMessage httpRes = null; int attempt = 0; while (true) { try { var sw = Stopwatch.StartNew(); httpRes = await client.PostAsync(_currentUrl, content); body = await httpRes.Content.ReadAsStringAsync(); long elapsed = sw.ElapsedMilliseconds; osmResponse = JsonSerializer.Deserialize <OsmResponse>(body, SerializerOptions); _logger.LogInformation($"Loaded (ms: {elapsed}, chars: {body.Length}, elements: {osmResponse.Elements.Count}): {dbgMsg}, url: {_currentUrl}"); break; } catch (Exception e) { if (++attempt > 3) { _logger.LogWarning($"Giving up getting tile: {dbgMsg}, exception: {e}"); return(null); } // Try switching URL on errors _currentUrl = _currentUrl == UrlPrimary ? UrlSecondary : UrlPrimary; _logger.LogInformation($"Retrying tile: {dbgMsg}, url: {_currentUrl}, exception: {e}"); await Task.Delay(200); } } var osmNodes = osmResponse.Elements.Where(e => e.Type == "node").ToList(); var osmWays = osmResponse.Elements.Where(e => e.Type == "way").ToList(); var result = new WayTile(); result.Id = tileId; var nodeLut = new Dictionary <long, WayTileNode>(); // First create all nodes... foreach (var osmNode in osmNodes) { var node = new WayTileNode(); node.Id = osmNode.Id; node.Point = new GeoCoord(osmNode.Lon, osmNode.Lat); node.Inside = TileMath.IsInsideBounds(osmNode.Lon, osmNode.Lat, bbox) ? (byte?)1 : null; nodeLut[osmNode.Id] = node; result.Nodes.Add(node); } // ...then add connections between them foreach (var osmWay in osmWays) { int nodeCount = osmWay.Nodes.Count; for (int i = 0; i < nodeCount - 1; ++i) { var wayPointA = osmWay.Nodes[i]; var wayPointB = osmWay.Nodes[i + 1]; nodeLut.TryGetValue(wayPointA, out WayTileNode nodeA); nodeLut.TryGetValue(wayPointB, out WayTileNode nodeB); if (nodeA != null && nodeB != null) { // Skip connection if both nodes are out of bounds bool withinBoundsA = TileMath.IsInsideBounds(nodeA.Point.Lon, nodeA.Point.Lat, bbox); bool withinBoundsB = TileMath.IsInsideBounds(nodeB.Point.Lon, nodeB.Point.Lat, bbox); if (withinBoundsA || withinBoundsB) { nodeA.Conn.Add(wayPointB); nodeB.Conn.Add(wayPointA); } } } } // Remove orphaned nodes outside of bounds result.Nodes = result.Nodes.Where(n => n.Conn.Count > 0).ToList(); _logger.LogInformation($"Parsed (nodes: {result.Nodes.Count}): {dbgMsg}"); return(result); }