private OsmWorldData ToWorld(OsmResponse response, float s, float w, float n, float e) { var nodes = new List <OsmNode>(); var ways = new List <OsmWay>(); var others = new List <OsmElement>(); foreach (var element in response.elements) { switch (element.type) { case "way": ways.Add(OsmWay.FromElement(element)); break; case "node": nodes.Add(OsmNode.FromElement(element)); break; default: others.Add(element); break; } } return(new OsmWorldData(response.version, response.generator, new OsmRect(s, w, n, e), response.osm3s, ways, nodes, others)); }
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); }