stadiamaps / ferrostar

A FOSS navigation SDK built from the ground up for the future
https://stadiamaps.github.io/ferrostar/
Other
184 stars 24 forks source link

Fixed Route Support? #366

Open bjtrounson opened 1 week ago

bjtrounson commented 1 week ago

Hi I'm wondering if this library supports fixed routes?

e.g. I have map matched some GPS coordinates in a OSRM response with Valhalla.

I'm currently using maplibre-navigation-android and I'm finding that when using a fixed route and starting in the middle of the route causes a lot of issues. I've had a look at some issues and it seems like I would have to create a lot of custom engines for it to work.

So I'm looking at migrating to this library if it is supported.

I'm also using React Native / Expo so if you need any help with creating the React Native Wrapper mentioned on this issue #116

ianthetechie commented 2 days ago

Hey @bjtrounson; apologies for the late reply here!

Hi I'm wondering if this library supports fixed routes?

e.g. I have map matched some GPS coordinates in a OSRM response with Valhalla.

Short answer: yes! Most of the core devs and many early adopters are using Valhalla, and some of the cases involve map matching of fixed routes too :)

I'm currently using maplibre-navigation-android and I'm finding that when using a fixed route and starting in the middle of the route causes a lot of issues. I've had a look at some issues and it seems like I would have to create a lot of custom engines for it to work.

This is an interesting special case. Let me describe real quick how Ferrostar currently would handle this, and then discuss some ways to make this better for your use case.

Status quo

Currently Ferrostar can load your fixed route. I'm not 100% sure whot you mean by "fixed" (is it making a trace route API call? or a pre-stored JSON from somewhere?), but in any case, the "worst" you'd have to do is to write something like the mock route adapter we use for testing that just serves up the route without a network request. Here are the iOS and Android implementations. This will handle the first step: converting your OSRM-format Valhalla route to the internal route model, no matter where it comes from.

Then, you can call the startNavigation method of FerrostarCore to get a navigation session going.... now on to the edge case. Currently, to avoid too much jank, route snapping only occurs on the current step. Most mobile OS's return location updates about once/second (this is pretty fixed on iOS; Android we specifically request 1/sec; MapLibre can get really weird if you deliver any faster; see #361). So, for the moment, it would probably advance at ~1 step per second until it hits your real location in the middle of the route. This is great if the user actually started around the start of the route, but probably not ideal for your present use case.

Ideas to improve it

The most obvious one that comes to mind is to allow jumping steps until the snapped location (which is snapping only to the current step) is within some tolerance, with some limits. Those "limits" should be tunable parameters. Probably estimated location accuracy, and the threshold at which to allow jumping multiple steps. For example:

So, if the last location update is believed to be accurate within 50m and the snapped location threshold is more than 50m from the reported location, allow snapping to any future step in the route geometry (we don't currently consider previous steps since regressions are abnormal and recalculation makes more sense at that point).

I think this would solve for your use case, but let me know your thoughts.

I'm also using React Native / Expo so if you need any help with creating the React Native Wrapper mentioned on this issue https://github.com/stadiamaps/ferrostar/issues/116

😍 That would be lovely! We have had a few potential users express interest, but I'm not aware of any work being done (though I do know some Flutter users who are just using native views). We'd love to coordinate on getting this into the main repo though! Lemme know if you're interested to set up a call or something to talk through the approach and/or join our weekly dev calls. You can find me on the Slack workspace linked in the README.

bjtrounson commented 2 days ago

Hey Ian, I appreciate the detailed response.

Here are some comments on the above.

I'm not 100% sure what you mean by "fixed" (is it making a trace route API call? or a pre-stored JSON from somewhere?),

I currently have a bunch of routes stored in a SQLite database as JSON in the OSRM format. Let me know if you need more detail than that.

My ideal route adapter would be using the stored fixed routes, but if they deviated or started away from a fixed route. It would then generate them one through Valhalla.

So, for the moment, it would probably advance at ~1 step per second until it hits your real location in the middle of the route. This is great if the user actually started around the start of the route, but probably not ideal for your present use case.

So just for clarification, would this mean if I had a route with 4 steps in it with the following step lengths: 1st: 200m 2nd: 300m 3rd: 148km 4th: 13km

It would get to the 3rd steps if the current location was within the 3rd step after 3 seconds? If so that would be acceptable for my use case, but obviously not optimal.

So, if the last location update is believed to be accurate within 50m and the snapped location threshold is more than 50m from the reported location, allow snapping to any future step in the route geometry (we don't currently consider previous steps since regressions are abnormal and recalculation makes more sense at that point). I think this would solve for your use case, but let me know your thoughts.

This seems great I think this would solve the above issue.

That would be lovely! We have had a few potential users express interest, but I'm not aware of any work being done (though I do know some Flutter users who are just using native views). We'd love to coordinate on getting this into the main repo though! Lemme know if you're interested to set up a call or something to talk through the approach and/or join our weekly dev calls. You can find me on the Slack workspace linked in the README.

Since I created this issue, I've actually created a very primitive wrapper. It's not very customizable at the moment but it works, I will look at putting it in a repo for now and then we can look merge it into this one. It still has quite a bit of work for it left to do but it's a starting point. I also have created an account on Slack and joined the ferrostar channel, I'll send you a message on there.

ianthetechie commented 2 days ago

I currently have a bunch of routes stored in a SQLite database as JSON in the OSRM format. Let me know if you need more detail than that. My ideal route adapter would be using the stored fixed routes, but if they deviated or started away from a fixed route. It would then generate them one through Valhalla.

👍 Yeah, that shouldn't be too hard to implement. Feel free to shout if you get stuck, but you can just write up the logic to query SQLite however you want and pass bytes off for deserialization (there's a top-level createOsrmResponseParser export that would let you get at this directly from the UniFFI wrappers; TBD the best approach for other wrapper approaches.)

It would get to the 3rd steps if the current location was within the 3rd step after 3 seconds? If so that would be acceptable for my use case, but obviously not optimal.

Correct. This actually depends somewhat on the StepAdvanceMode, but the one we usually recommend is RelativeLineStringDistance.

Thinking out loud, I guess you may already be able to instantly skip to step 2, so it'd be something like 1-2 seconds, but you've got the basic idea; it's roughly going to take a second to skip each step. Walking through the process:

  1. Fetch a route from SQLite and parse it into a Route object .
  2. In the existing Swift / Kotlin, you'd callstartNavigation on FerrostarCore with the parsed Route and a UserLocation. Under the hood (since we're also talking about porting this to RN), what this class does is create a new NavigationController from the route and initial user location, which is now at step 3. So the initial state... if I implemented it correctly.... might skip straight to step 2 here.
  3. Then the next GPS update comes in after ~a second and it advances to step 3.

Since I created this issue, I've actually created a very primitive wrapper. It's not very customizable at the moment but it works, I will look at putting it in a repo for now and then we can look merge it into this one. It still has quite a bit of work for it left to do but it's a starting point. I also have created an account on Slack and joined the ferrostar channel, I'll send you a message on there.

Fantastic ❤️