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

add valhalla's time options #1

Open
wants to merge 19 commits into
base: departure-arrival-time
Choose a base branch
from

Conversation

nilsnolde
Copy link

Sorry, I got carried away a little bit. Seems I gave bit of a lousy review when adding OTP, there's been a few inconsistencies with other routers, which I patched here as well. Ideally I'd have separated that entirely in a different PR, sorry for a bit of a mess.. Maybe I should've also waited until we merged your PR, then PR valhalla separately. Anyways, let me know if you strongly prefer another way and you can just cherry-pick what you want from here for your PR and I'll deal with valhalla after we merged gis-ops#116 for OTP & Google only.

I mainly wanted to make the review back and forth a bit easier and have smth tangible to show what I mean.Let me know smth looks weird or is not clear, I'm not 100% I didn't mess anything up.

@@ -25,6 +25,7 @@ contextily = {version = "^1.1.0", optional = true}
geopandas = {version = "^0.8.2", optional = true}
descartes = {version = "^1.0.0", optional = true}
pytz = "^2023.3"
timezonefinder = "^6.2.0"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for valhalla, which (unfortunately) doesn't return a tz aware date_time string

@@ -189,8 +187,8 @@ def directions( # noqa: C901
language: Optional[str] = None,
region: Optional[str] = None,
units: Optional[str] = None,
arrival_time: Optional[int] = None,
departure_time: Optional[int] = None,
date_time: Optional[datetime.datetime] = datetime.datetime.now(datetime.timezone.utc),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made them all more consistent in terms of time arguments:

  • date_time is everywhere supposed to be a tz-aware date_time object, defaulting to UTC
  • date_time_type is either depart_at or arrive_by

I put those arguments wherever time is needed, but sometimes they're not doing anything, e.g. OTP isochrones only support depart_at scenarios

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could've done a enum or so, but I thought with a docstring it's ok, and it's one less thing to import for others

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I'd planned to tackle that later too! However, I would have preferred a "departure_datetime" and an "arrival_datetime". I find it more concise and more explicit for the user.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to that. As we're breaking stuff right now, we're flexible. And I kinda agree.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change it here before touching the tests.

routingpy/routers/google.py Outdated Show resolved Hide resolved
@@ -135,13 +105,13 @@ def directions(
query = f"""
{{
plan(
date: "{ date.strftime("%Y-%m-%d") }"
time: "{ time.strftime("%H:%M:%S") }"
date: "{ date_time.date().strftime("%Y-%m-%d") }"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should do the same as before, just omitting the timezone

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we must validate this in the unit tests.

arrival_datetime = self._timestamp_to_utc_datetime(itinerary["endTime"])
distance, geometry = OpenTripPlannerV2._parse_legs(itinerary["legs"])
departure_datetime = convert.timestamp_to_tz_datetime(itinerary["startTime"], "UTC")
arrival_datetime = convert.timestamp_to_tz_datetime(itinerary["endTime"], "UTC")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this I don't really understand @khamaileon : does that mean that in the OTP result there's no info which timezone the destination (or departure) is referring to? Is there no way to get the timezone info back from OTP? If not, I'd suggest to find the timezone similar to valhalla with timezonefinder, otherwise anyone using this has the burden to look up the timezone to get the local time.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can consider the input timezone as the output timezone. That's why I insisted on a datetime aware input.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how does OTP expect it? It expects a UTC date & time in the request right? Then these outputs here by OTP is UTC as well? If we can't find out the timezone, we shouldn't assume the same as the input, it might've changed from origin to destination. I couldn't find the docs for OTP request parameters though..

Copy link
Owner

@khamaileon khamaileon Aug 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It expects a local date & time in the request. But we ask the user for an aware datetime so we can use the same timezone to transform the output. Maybe I'm wrong, but I find it hard to imagine a user requesting an itinerary on one timezone with a result on another timezone.

BTW, there is a way to retrieve the timezone for transit legs via agency/timezone.

Copy link
Author

@nilsnolde nilsnolde Aug 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it hard to imagine a user requesting an itinerary on one timezone with a result on another timezone.

That's actually quite common for car or long-distance transit (e.g. Amtrak crossing the US).

BTW, there is a way to retrieve the timezone for transit legs via agency/timezone.

I think that's not granular enough. We'd need the stops.txt/stop_timezone (if OTP exposes that) and I wouldn't be sure that's consistently handled by all operators who indeed have stops in different timezones.

FWIW, I asked a OTP core dev offline (Cc @leonardehrenfried), maybe he has some more insight/recommendations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha he sent me your matrix questions and his answers. So it's the same situation for OTP as we have in Valhalla. It's a rare situation that one operator has timezone-crossing stops, but still it's common to cross timezone in one itinerary. I'd use the same approach for now as in Valhalla and look it up dynamically by its location with timezonefinder. Apparently they want to change it in OTP similar to what I'd like in Valhalla, so this would hopefully only be a temp solution. Ok for you?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually quite common for car or long-distance transit (e.g. Amtrak crossing the US).

Yeah, I see. I think I was too focused on public transport where you only have one timezone per network.

I think that's not granular enough. We'd need the stops.txt/stop_timezone (if OTP exposes that) and I wouldn't be sure that's consistently handled by all operators who indeed have stops in different timezones.

OTP exposes timezone a the stop level, but maybe that's too much information to bring up.

FWIW, I asked a OTP core dev offline (Cc @leonardehrenfried), maybe he has some more insight/recommendations.

Lol I had asked the question on the OTP chat and it was him who answered me!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I'm ok with it! Don't forget timezonefinder has a cost (near 1s / 10000 query)

Copy link
Owner

@khamaileon khamaileon Aug 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Come to think of it, now that we won't be using the input timezone for output, do we still need an input datetime aware? And what happens if we enter a departure timezone that isn't the one at the point of origin? Sorry, I can make the changes if we rollback again.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah right, it’s a bit of cost, but I guess it’s drowning well in the OTP/Valhalla response time:)

God I hate datetime stuff! It always the head quite a bit.. I’m not sure about OTP‘s isochrones, but I’m now fairly sure that expects a local time as well (timezone would be meaningless). If that’s the case, all routers only need the local time and we can ditch tz aware input? The output is probably the only thing that should have timezone awareness. Google does it right, OTP & Valhalla hopefully soon too.

Comment on lines +181 to +198
profile: str,
intervals: List[int],
interval_type: Optional[str] = None,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here: I changed those to be consistent with other routers

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was planning to do that in the 2nd phase. I wanted the 1st version to reflect the router's API as closely as possible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, that'd have been fine too, as long as we don't release, which we have already. It's not that bad though, the next release will break the date_time stuff anyways, so we'll need to increment major version.

Comment on lines 289 to 300
# TODO: maybe we can get rid of that in the future, in case we'll have timezone aware strings in the response:
# https://github.com/valhalla/valhalla/issues/4241
if origin.get("date_time"):
departure_time = timestamp_to_tz_datetime(
origin["date_time"], lonlat_to_timezone(origin["lon"], origin["lat"])
)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valhalla doesn't return the timezone either. it could do that in the date_time string and I think we should. it won't change any public facing API if we change it in the future, so we can start with this

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. I'd take the input timezone.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above:) destination can have a different timezone

@nilsnolde
Copy link
Author

nilsnolde commented Aug 6, 2023

And last question: do you want to be invited as a maintainer to routingpy? No pressure, I don't mind either way. It just makes collaboration a bit easier, at the least the Github goodies like assigning reviewers (not possible across forks).

@nilsnolde
Copy link
Author

And I should mention this is missing unit tests for valhalla (I didn't do changes to OTP/Google tests either yet).

@@ -189,8 +187,8 @@ def directions( # noqa: C901
language: Optional[str] = None,
region: Optional[str] = None,
units: Optional[str] = None,
arrival_time: Optional[int] = None,
departure_time: Optional[int] = None,
date_time: Optional[datetime.datetime] = datetime.datetime.now(datetime.timezone.utc),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I'd planned to tackle that later too! However, I would have preferred a "departure_datetime" and an "arrival_datetime". I find it more concise and more explicit for the user.

@@ -378,7 +349,7 @@ def _parse_direction_json(self, response, alternatives):

directions = []
for route in response["routes"]:
duration, distance, geometry, departure_datetime, arrival_datetime = self._parse_legs(
duration, distance, geometry, departure_datetime, arrival_datetime = Google.parse_legs(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's just me, but it seems a bit odd. But I can live with it and we stay consistent with the rest of the code, which is the most important thing.

arrival_time: Optional[int] = None,
departure_time: Optional[int] = None,
date_time: Optional[datetime.datetime] = datetime.datetime.now(datetime.timezone.utc),
date_time_type: Optional[str] = None,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

time: Optional[datetime.time] = datetime.datetime.now().time(),
arrive_by: Optional[bool] = False,
profile: Optional[str],
date_time: Optional[datetime.datetime] = datetime.datetime.now(datetime.timezone.utc),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

@@ -135,13 +105,13 @@ def directions(
query = f"""
{{
plan(
date: "{ date.strftime("%Y-%m-%d") }"
time: "{ time.strftime("%H:%M:%S") }"
date: "{ date_time.date().strftime("%Y-%m-%d") }"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we must validate this in the unit tests.

arrival_datetime = self._timestamp_to_utc_datetime(itinerary["endTime"])
distance, geometry = OpenTripPlannerV2._parse_legs(itinerary["legs"])
departure_datetime = convert.timestamp_to_tz_datetime(itinerary["startTime"], "UTC")
arrival_datetime = convert.timestamp_to_tz_datetime(itinerary["endTime"], "UTC")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can consider the input timezone as the output timezone. That's why I insisted on a datetime aware input.

Comment on lines +181 to +198
profile: str,
intervals: List[int],
interval_type: Optional[str] = None,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was planning to do that in the 2nd phase. I wanted the 1st version to reflect the router's API as closely as possible.

routingpy/routers/opentripplanner_v2.py Outdated Show resolved Hide resolved
Comment on lines 289 to 300
# TODO: maybe we can get rid of that in the future, in case we'll have timezone aware strings in the response:
# https://github.com/valhalla/valhalla/issues/4241
if origin.get("date_time"):
departure_time = timestamp_to_tz_datetime(
origin["date_time"], lonlat_to_timezone(origin["lon"], origin["lat"])
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. I'd take the input timezone.

@khamaileon
Copy link
Owner

I'm fine with doing it this way. Apart from the comments I've already made, the tests on Valhalla are missing and the results of the dates also need to be tested. Tell me how you want us to proceed, I should have 1 or 2 hours available today. And I'd be delighted to become a contributor. I had already done a code of this type privately a few years ago. It's unmaintainable on its own. Long live FOSS!

@nilsnolde
Copy link
Author

Ok cool I'll add you to the repo then, thanks!

I'd do the changes to this PR:

  • add tests for valhalla
  • adapt tests for google/otp
  • move to arrival_datetime & destination_datetime, both being mutually exclusive

Just needs clarification on the OTP /route endpoint and how it handles timezones. Is there a sane doc for OTP API parameters? I struggled to find one.

Sounds ok for you? Next collab will happen with a bit more thought on my side:)

@khamaileon
Copy link
Owner

Ok cool I'll add you to the repo then, thanks!

I'd do the changes to this PR:

* add tests for valhalla

* adapt tests for google/otp

* move to `arrival_datetime` & `destination_datetime`, both being mutually exclusive

Great, let me know if you need help.

Just needs clarification on the OTP /route endpoint and how it handles timezones. Is there a sane doc for OTP API parameters? I struggled to find one.

You can look at the GraphQL doc on a local instance: http://localhost:8081/graphiql

Sounds ok for you? Next collab will happen with a bit more thought on my side:)

No worries, it's nice to share it with you already :)

@nilsnolde
Copy link
Author

sorry for the long wait. I finally found the time to take care of this. let me know if it's fine for you.

@khamaileon
Copy link
Owner

Hi @nilsnolde, happy new year :)
Sorry end of the year was intensive for me too. I'll have a look ASAP!

@leonardehrenfried
Copy link

BTW, times which include the offset (~= time zone) will be coming to OTP: opentripplanner/OpenTripPlanner#5660

@nilsnolde
Copy link
Author

Great:) forgot to update that we also added it a few weeks ago: valhalla/valhalla#4491. returning both tz string and offset

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