Пример #1
0
        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);
        }
Пример #2
0
        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);
        }