Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way ID in Route #309

Open
tomercagan opened this issue Apr 18, 2020 · 9 comments
Open

Way ID in Route #309

tomercagan opened this issue Apr 18, 2020 · 9 comments

Comments

@tomercagan
Copy link

Hi,

First of - thanks for this great library. It is very easy to get started and seems very robust.

I am using the library to calculate routes and then aggregate the segments on each route.

From what I have seen in the OSM pbf format specifications, there is a (unique) ID for each node/edge (way in OSM lingo, if I am not mistaken).

I have found that it is possible to preserve the way IDs when loading the RouterDb by passing in a LoadSettings with KeepWayIds = true. I even see the relevant keys when inspecting the RouterDb instance in EdgeData.

I was expecting that this attribute will "propagate" along when calculating a route and will appear as meta data of the ShapeMeta instances attributes but I don't see this happening.

Is there a way to get the IDs of the road/way/segments in the route, or otherwise look up this value and associate it with the route's segment?

Thanks!

@tomercagan
Copy link
Author

tomercagan commented May 1, 2020

Hi,
I started to look into this.

From what I gathered so far, a path is calculated and eventually there's a call to BuildRoute which in turn (in Router implementation) calls CompleteRouteBuilder.TryBuild (unless there's a custom builder defined).

Inside CompleteRouteBuilder.TryBuild it is "trivial" to add the edge's Id to the Meta attributes collection inside Add method... I don't see a "hook" that can be used in order to plug-in this sort of change.

The questions is, how to add this kind of behavior?

Creating a whole new custom route builder for such a small change does not seem to make sense.

Would adding a property to CompleteRouteBuilder make sense and be acceptable? (Would any change toward this goal would be acceptable or this is too esoteric a requirement?).

That way, it will be possible to create a new instance of CompleteRouteBuilder and assign it to the router to be used for that purpose.

I'd be happy to hear thoughts/comments :-)

BTW, a question about the implementation efficiency - it seems that along the program flow you have the full path including the edges. Then, this is redacted to a list of vertices and then, the edges are being looked up again in RouterDB. Isn't this "inefficient"?

@xivk
Copy link
Contributor

xivk commented May 18, 2020

I would make sense to add these attributes to the output routes by default, same as is happening with the other attributes.

Also, keep in mind that an OSM way can span multiple edges: https://www.openstreetmap.org/way/28859745#map=18/51.21550/3.07210 => this id is not unique per edge in the routing network.

I would accept a pull request for this, it should be fairly doable to add this feature to the route builder. Ping me if you want some pointers.

@xivk
Copy link
Contributor

xivk commented May 18, 2020

Keep in mind that if you do a pull request to keep focus on one feature and modify as little code as possible to implement it.

@tomercagan
Copy link
Author

Hi @xivk - thanks for getting back to me on this.
I actually did a change pretty much as I described above - it's actually one line of code (git diff):

 +++ b/src/Itinero/Algorithms/Routes/CompleteRouteBuilder.cs
 @@ -384,6 +384,7 @@ namespace Itinero.Algorithms.Routes
              }
              var meta = _routerDb.EdgeMeta.Get(edge.Data.MetaId);
              var attributes = new AttributeCollection(meta);
 +            attributes.AddOrReplace("wayid", edge.Id.ToString());
              if (!profile.IsTranslatedProfile()) attributes.AddOrReplace(profile);
              attributes.AddOrReplace("profile", _profile.FullName);

Does this makes sense?

I did not fully understand your comment about way IDs not being unique in the network... Is there a unique ID that identifies a "way" (road - sorry - I am new to OSM world).

Let me know how I can push this through ...

@xivk
Copy link
Contributor

xivk commented May 18, 2020

I did not fully understand your comment about way IDs not being unique in the network... Is there a unique ID that identifies a "way" (road - sorry - I am new to OSM world).

No there is no such ID. There are way IDs and node IDs in OSM for example these:

https://www.openstreetmap.org/way/28859745#map=18/51.21550/3.07210 => WAY with ID 28859745
https://www.openstreetmap.org/node/5805786783 => NODE with ID 5805786783

And then Itinero uses an internal ID scheme that uses vertex IDs and edge IDs. They are different from the above way IDs and node IDs. It would be acceptable to include the way id and node id in the output route but not the internal Itinero ID because they don't have any meaning.

You should be able to include attributes that exists on the edges (in EdgeMeta) in the route output. Then you would have the way id not the internal Itinero ID like you are doing here:

 +++ b/src/Itinero/Algorithms/Routes/CompleteRouteBuilder.cs
 @@ -384,6 +384,7 @@ namespace Itinero.Algorithms.Routes
              }
              var meta = _routerDb.EdgeMeta.Get(edge.Data.MetaId);
              var attributes = new AttributeCollection(meta);
 +            attributes.AddOrReplace("wayid", edge.Id.ToString());
              if (!profile.IsTranslatedProfile()) attributes.AddOrReplace(profile);
              attributes.AddOrReplace("profile", _profile.FullName);

@tomercagan
Copy link
Author

Hi,
Thanks for clarifying it.
So in the above code, the "wayid" attribute should actually be read from edge.DataMetaId?

@juliusfriedman
Copy link

juliusfriedman commented May 22, 2020

@xivk, would you also like the node tags to be stored?

e.g. in AddNode:

#region Node Tags
                var nodeAttributes = node.Tags.ToAttributes();
                var profileWhiteList = new Whitelist();
                if (_vehicleCache.AddToWhiteList(nodeAttributes, profileWhiteList))
                { // node has some use.
                    // build profile and meta-data.
                    var profileTags = new AttributeCollection();
                    var metaTags = new AttributeCollection();
                    foreach (var tag in node.Tags)
                    {
                        if (profileWhiteList.Contains(tag.Key))
                        {
                            profileTags.Add(tag);
                        }
                        else if (_vehicleCache.Vehicles.IsOnProfileWhiteList(tag.Key))
                        {
                            profileTags.Add(tag);
                        }
                        else if (_vehicleCache.Vehicles.IsOnMetaWhiteList(tag.Key))
                        {
                            metaTags.Add(tag);
                        }
                    }

                    if (!_vehicleCache.AnyCanTraverse(profileTags))
                    {
                        return;
                    }

                    // get profile and meta-data id's.
                    var profileCount = _profileIndex.Count;
                    var profile = _profileIndex.Add(profileTags);
                    if (profileCount != _profileIndex.Count)
                    {
                        var stringBuilder = new StringBuilder();

                        if (!this.Normalize)
                        { // no normalization, translate profiles by.
                            metaTags.AddOrReplace(profileTags);

                            // translate profile.
                            var translatedProfile = new AttributeCollection();
                            translatedProfile.AddOrReplace("translated_profile", "yes");
                            foreach (var vehicle in _vehicleCache.Vehicles)
                            {
                                foreach (var vehicleProfile in vehicle.GetProfiles())
                                {
                                    var factorAndSpeed = vehicleProfile.FactorAndSpeed(profileTags);
                                    translatedProfile.AddOrReplace($"{vehicleProfile.FullName}",
                                        $"{factorAndSpeed.Direction}|" +
                                        $"{factorAndSpeed.Value.ToInvariantString()}|" +
                                        $"{factorAndSpeed.SpeedFactor.ToInvariantString()}");
                                }
                            }

                            var translatedCount = _db.EdgeProfiles.Count;
                            var translatedProfileId = _db.EdgeProfiles.Add(translatedProfile);
                            if (translatedProfileId > Data.Edges.EdgeDataSerializer.MAX_PROFILE_COUNT)
                            {
                                throw new Exception("Maximum supported profiles exceeded, make sure only routing tags are included in the profiles.");
                            }
                            _translatedPerOriginal[profile] = (ushort)translatedProfileId;
                            profile = translatedProfileId;
                            if (translatedCount != _db.EdgeProfiles.Count)
                            {
                                stringBuilder.Clear();
                                foreach (var att in translatedProfile)
                                {
                                    stringBuilder.Append(att.Key);
                                    stringBuilder.Append('=');
                                    stringBuilder.Append(att.Value);
                                    stringBuilder.Append(' ');
                                }

                                Itinero.Logging.Logger.Log("RouterDbStreamTarget", Logging.TraceEventType.Information,
                                    "New translated profile: # {0}: {1}", _db.EdgeProfiles.Count,
                                    stringBuilder.ToInvariantString());
                            }
                        }
                        else
                        {
                            foreach (var att in profileTags)
                            {
                                stringBuilder.Append(att.Key);
                                stringBuilder.Append('=');
                                stringBuilder.Append(att.Value);
                                stringBuilder.Append(' ');
                            }
                            Itinero.Logging.Logger.Log("RouterDbStreamTarget", Logging.TraceEventType.Information,
                                "New edge profile: # profiles {0}: {1}", _profileIndex.Count, stringBuilder.ToInvariantString());

                            if (profile > Data.Edges.EdgeDataSerializer.MAX_PROFILE_COUNT)
                            {
                                throw new Exception("Maximum supported profiles exceeded, make sure only routing tags are included in the profiles.");
                            }
                        }
                    }
                    else if (!this.Normalize)
                    {
                        profile = _translatedPerOriginal[profile];

                        metaTags.AddOrReplace(profileTags);
                    }
                    var meta = _db.EdgeMeta.Add(metaTags);
                }
                #endregion

or something more primitive to only add a metaTags with a {"nodeId", node.Id}?

I think it's best to separate nodeId and wayId since both attributes are named id afaik..

E.g. the same can be done in RouterDbStreamTarget by just only adding a metaTags with a {"wayId", way.Id} when KeepWayIds is true

Otherwise I think it must also be added to either the default profile tags or the lua as 'id' which will be confusing I think...

I think the minimum way to implement this would be as follows(RouterDbStreamTarget):

For Ways:

if (KeepWayIds && way.Id.HasValue) metaTags.Add(new OsmSharp.Tags.Tag("wayId", way.Id.ToString()));

For Nodes:

if (KeepNodeIds && node.Id.HasValue) metaTags.Add(new OsmSharp.Tags.Tag("nodeId", node.Id.ToString()));

Then in CompleteRouteBuilder

 +++ b/src/Itinero/Algorithms/Routes/CompleteRouteBuilder.cs
 @@ -384,6 +384,7 @@ namespace Itinero.Algorithms.Routes
              }
              var meta = _routerDb.EdgeMeta.Get(edge.Data.MetaId);
              var attributes = new AttributeCollection(meta);
+
#region Node / WayId
            if (meta.TryGetValue("wayId", out string wayId))
            {
                attributes.AddOrReplace("wayId", wayId);
            }
            if (meta.TryGetValue("nodeId", out string nodeId))
            {
                attributes.AddOrReplace("nodeId", nodeId);
            }
            #endregion
+
              if (!profile.IsTranslatedProfile()) attributes.AddOrReplace(profile);
              attributes.AddOrReplace("profile", _profile.FullName);

@xivk , I will include this in #302 if you give some feedback there.

@bran-jnw
Copy link

bran-jnw commented Mar 2, 2022

I see that this has yet to be implemented when looking at the source, but it would be really useful in my use case where I calculate speed based on density and therefore need to know where each car is located while traversing the route.

Any chance on getting it in there in a near future?

EDIT: I actually solved my problem by creating a hash from the last coordinate of the shape as well as the second to last coordinate to identify a "road segment", using way id is not reliable since ways can cross junctions. The solution works for most cases except for when a router point starts between the last and second to last coordinate on a shape (the hash then is different due to the second to last coordinate is the router point and not a "proper" vertex/node).

@Doidel
Copy link

Doidel commented Mar 21, 2022

I would be interested in this too.
I need this because I get external input (shortcuts and restrictions) based on wayIds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants