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

New vectortile layer for Digitransit realtime stops #5743

Merged

Conversation

sharhio
Copy link
Contributor

@sharhio sharhio commented Mar 12, 2024

Summary

A new Digitransit vectortile layer for stops which also includes realtime alert information.

Issue

The new layer allows showing information related to alerts affecting stops on the map. Alert information is only fetched when the map is zoomed in, hence the requirement for a new layer. The implementation follows the same structure as VehicleRental layers. The realtime stop mapper uses the code from the previously existing stop layer for the parts that are identical between the two layers.

Unit tests

Unit test for alerts added to StopsLayerTest.
Manual testing was also performed.

@sharhio sharhio requested a review from a team as a code owner March 12, 2024 11:01
Copy link

codecov bot commented Mar 12, 2024

Codecov Report

Attention: Patch coverage is 80.00000% with 9 lines in your changes are missing coverage. Please review.

Project coverage is 67.78%. Comparing base (51a381e) to head (e6c31e6).
Report is 114 commits behind head on dev-2.x.

Files Patch % Lines
...xt/vectortiles/layers/stops/StopsLayerBuilder.java 0.00% 5 Missing ⚠️
...entripplanner/routing/alertpatch/TransitAlert.java 25.00% 0 Missing and 3 partials ⚠️
...es/layers/stops/DigitransitStopPropertyMapper.java 95.65% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             dev-2.x    #5743    +/-   ##
===========================================
  Coverage      67.78%   67.78%            
- Complexity     16478    16527    +49     
===========================================
  Files           1901     1905     +4     
  Lines          72132    72293   +161     
  Branches        7430     7441    +11     
===========================================
+ Hits           48892    49007   +115     
- Misses         20727    20765    +38     
- Partials        2513     2521     +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


@BeforeEach
public void setUp() {
public final void setUp() {
Copy link
Member

Choose a reason for hiding this comment

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

It's not really necessary to mark methods as final especially if you don't plan to extend the class.

Comment on lines 27 to 32
protected static DigitransitRealtimeStopPropertyMapper create(
TransitService transitService,
Locale locale
) {
return new DigitransitRealtimeStopPropertyMapper(transitService, locale);
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this or could you just use the normal constructor in tests as well?

new KeyValue("desc", i18NStringMapper.mapToApi(stop.getDescription())),
new KeyValue(
"parentStation",
stop.getParentStation() != null ? stop.getParentStation().getId() : "null"
Copy link
Member

Choose a reason for hiding this comment

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

I think this "null" is a mistake we made in the old layer, you can just return null

import org.opentripplanner.inspector.vector.LayerParameters;
import org.opentripplanner.transit.service.TransitService;

public class StopsBaseLayerBuilder extends StopsLayerBuilder {
Copy link
Member

Choose a reason for hiding this comment

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

Is this class necessary? Could we just edit the existing StopsLayerBuilder since this is not really overriding anything.

@t2gran t2gran added this to the 2.5 (next release) milestone Mar 12, 2024
Comment on lines 36 to 41
alert.effect().equals(AlertEffect.NO_SERVICE) &&
(
alert.getEffectiveStartDate() != null &&
alert.getEffectiveStartDate().isBefore(currentTime)
) &&
(alert.getEffectiveEndDate() != null && alert.getEffectiveEndDate().isAfter(currentTime))
Copy link
Member

Choose a reason for hiding this comment

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

Can you move this into a method on TransitAlert? Maybe call it noServiceOn(instant).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also fixed the logic a little, as I realised that the end time doesn't need to be defined for the alert to be effective, it could be open ended.


@Override
protected Collection<KeyValue> map(RegularStop stop) {
Instant currentTime = ZonedDateTime.now(transitService.getTimeZone()).toInstant();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Instant currentTime = ZonedDateTime.now(transitService.getTimeZone()).toInstant();
Instant currentTime = Instant.now();

No need for the detour via the ZonedDateTime.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Really? I thought the time zone might be important in this context to ensure the alert is actually valid according to local time.

Copy link
Member

Choose a reason for hiding this comment

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

If you call toInstant all timezone information is lost again. An instant is just a number of seconds since 1970-01-01 UTC.

Copy link
Member

Choose a reason for hiding this comment

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

This instant can be converted to many time zones but it remains the same number of seconds.

Copy link
Member

Choose a reason for hiding this comment

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

And since we only want to give an answer at the time of someone asking and not the future/past the time zone doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, you're right. Somehow tricked myself into thinking the instant is different in different places. 😂 Thanks!

Comment on lines 45 to 49
new KeyValue("gtfsId", stop.getId().toString()),
new KeyValue("name", i18NStringMapper.mapNonnullToApi(stop.getName())),
new KeyValue("code", stop.getCode()),
new KeyValue("platform", stop.getPlatformCode()),
new KeyValue("desc", i18NStringMapper.mapToApi(stop.getDescription())),
Copy link
Member

Choose a reason for hiding this comment

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

Can you extract a method for these KeyValue instances that are shared between the two mappers?

You can define two lists and use ListUtils.combine to combine the two lists.

),
new KeyValue("type", getType(transitService, stop)),
new KeyValue("routes", getRoutes(transitService, stop)),
new KeyValue("noServiceAlert", noServiceAlert)
Copy link
Member

Choose a reason for hiding this comment

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

If you want to keep the boolean, you could use a property name like "closedByServiceAlert".

* @param instant
* @return
*/
public boolean noServiceOn(Instant instant) {
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I think in English "no service at instant" is more correct. Can you rename the method?

Comment on lines 39 to 40
i18NStringMapper.mapNonnullToApi(stop.getName()),
i18NStringMapper.mapToApi(stop.getDescription()),
Copy link
Member

Choose a reason for hiding this comment

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

These two calls can move to the method by passing the i18nstring mapper.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, passed the transitService as well.

Copy link
Member

@leonardehrenfried leonardehrenfried left a comment

Choose a reason for hiding this comment

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

Only tiny requests left.

…DigitransitRealtimeStopPropertyMapper.java

Co-authored-by: Leonard Ehrenfried <mail@leonard.io>
@leonardehrenfried
Copy link
Member

One thing I forgot: you need to add documentation.

@leonardehrenfried
Copy link
Member

You need to modify the md file in doc-templates and then run mvn test so produce the documentation.

@sharhio
Copy link
Contributor Author

sharhio commented Mar 19, 2024

You need to modify the md file in doc-templates and then run mvn test so produce the documentation.

Sorry about the confusion with the files, can't believe I forgot about the templates. It should be ok now.

"mapper": "DigitransitRealtime",
"maxZoom": 20,
"minZoom": 14,
"cacheMaxSeconds": 600
Copy link
Member

Choose a reason for hiding this comment

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

For the other realtime layers, we have suggested 60 second cache time. Not sure what is actually optimal here but we could use the same.

new KeyValue("desc", i18NStringMapper.mapToApi(stop.getDescription())),
new KeyValue(
"parentStation",
stop.getParentStation() != null ? stop.getParentStation().getId() : null
Copy link
Member

Choose a reason for hiding this comment

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

This is a breaking change. This shouldn't be shared between the mappers as the old one always returned a string.

Copy link
Member

Choose a reason for hiding this comment

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

This has been regenerated (although there wasn't really a need since the graphql schema has not been updated. There are just some duplicate imports here.

Copy link
Member

Choose a reason for hiding this comment

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

Same as above.

optionsome
optionsome previously approved these changes Mar 21, 2024
@optionsome
Copy link
Member

@leonardehrenfried seemingly our UI at least doesn't rely on the fact that the parentStation id would always be a string. Should we just return null instead of "null" for the old layer?

@leonardehrenfried
Copy link
Member

Yes, you can. I'm not aware of anyone using this field. I think the null as string came from a time when we used Map.of or something similar which didn't allow nulls.

@optionsome optionsome merged commit 4d7f4dd into opentripplanner:dev-2.x Mar 25, 2024
5 checks passed
@optionsome optionsome deleted the new-vectortile-realtime-stop-layer branch March 25, 2024 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants