diff --git a/docs/apis/Apis.md b/docs/apis/Apis.md index 48b530a57e1..d9f0467bbca 100644 --- a/docs/apis/Apis.md +++ b/docs/apis/Apis.md @@ -18,7 +18,7 @@ entities on a vector map. The [Actuator API](../sandbox/ActuatorAPI.md) provides endpoints for checking the health status of the OTP instance and reading live application metrics. -The [Geocoder API](../sandbox/GeocoderAPI.md) allows you to geocode street corners and stop names. +The [Geocoder API](../sandbox/GeocoderAPI.md) allows you to geocode stop names and codes. ## Legacy APIs (to be removed) diff --git a/docs/sandbox/GeocoderAPI.md b/docs/sandbox/GeocoderAPI.md index 0405724fff6..f7a1be3f293 100644 --- a/docs/sandbox/GeocoderAPI.md +++ b/docs/sandbox/GeocoderAPI.md @@ -26,7 +26,7 @@ To enable this you need to add the feature to `otp-config.json`. The required geocode API for Stop and From/To searches in the debug client. -Path: `/otp/routers/{routerId}/geocode` +Path: `/otp/geocode` It supports the following URL parameters: @@ -40,12 +40,12 @@ It supports the following URL parameters: #### Stop clusters A stop cluster is a deduplicated groups of stops. This means that for any stop that has a parent -station only the parent is returned and for stops that have identical names and are very close +station only the parent is returned and for stops that have _identical_ names and are very close to each other, only one is returned. -This is useful for a general purpose fuzzy "stop" search. +This is useful for a general purpose fuzzy stop search. -Path: `/otp/routers/{routerId}/geocode/stopClusters` +Path: `/otp/geocode/stopClusters` It supports the following URL parameters: diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 371bcb249b6..178b8b6fd67 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -105,6 +105,7 @@ static void setup() { .of(ALEXANDERPLATZ_STATION, BERLIN_HAUPTBAHNHOF_STATION, FIVE_POINTS_STATION) .forEach(stopModel::withStation); var transitModel = new TransitModel(stopModel.build(), new Deduplicator()); + transitModel.index(); var transitService = new DefaultTransitService(transitModel) { private final Multimap modes = ImmutableMultimap .builder() diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index 39ed6da297c..f5d1f950632 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -21,24 +21,30 @@ /** * OTP simple built-in geocoder used by the debug client. */ -@Path("/routers/{ignoreRouterId}/geocode") +@Path("/geocode") @Produces(MediaType.APPLICATION_JSON) public class GeocoderResource { private final OtpServerRequestContext serverContext; - /** - * @deprecated The support for multiple routers are removed from OTP2. See - * https://github.com/opentripplanner/OpenTripPlanner/issues/2760 - */ - @Deprecated - @PathParam("ignoreRouterId") - private String ignoreRouterId; - public GeocoderResource(@Context OtpServerRequestContext requestContext) { serverContext = requestContext; } + /** + * This class is only here for backwards-compatibility. It will be removed in the future. + */ + @Path("/routers/{ignoreRouterId}/geocode") + public static class GeocoderResourceOldPath extends GeocoderResource { + + public GeocoderResourceOldPath( + @Context OtpServerRequestContext serverContext, + @PathParam("ignoreRouterId") String ignore + ) { + super(serverContext); + } + } + /** * Geocode using data using the OTP graph for stops, clusters and street names * diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 71b0cf67b34..e390c900377 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -42,7 +42,6 @@ import org.apache.lucene.store.ByteBuffersDirectory; import org.opentripplanner.ext.geocoder.StopCluster.Coordinate; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; @@ -67,6 +66,7 @@ public class LuceneIndex implements Serializable { public LuceneIndex(TransitService transitService) { this.transitService = transitService; + var stopClusterMapper = new StopClusterMapper(transitService); this.analyzer = new PerFieldAnalyzerWrapper( @@ -80,7 +80,6 @@ public LuceneIndex(TransitService transitService) { var directory = new ByteBuffersDirectory(); - var stopClusterMapper = new StopClusterMapper(transitService); try { try ( var directoryWriter = new IndexWriter( @@ -128,7 +127,7 @@ public LuceneIndex(TransitService transitService) { directoryWriter, StopCluster.class, stopCluster.id().toString(), - new NonLocalizedString(stopCluster.name()), + I18NString.of(stopCluster.name()), stopCluster.code(), stopCluster.coordinate().lat(), stopCluster.coordinate().lon(), @@ -176,17 +175,34 @@ public Stream queryStopLocationGroups(String query, boolean * one of those is chosen at random and returned. */ public Stream queryStopClusters(String query) { - return matchingDocuments(StopCluster.class, query, false).map(LuceneIndex::toStopCluster); + return matchingDocuments(StopCluster.class, query, false).map(this::toStopCluster); } - private static StopCluster toStopCluster(Document document) { + private StopCluster toStopCluster(Document document) { var id = FeedScopedId.parse(document.get(ID)); var name = document.get(NAME); var code = document.get(CODE); var lat = document.getField(LAT).numericValue().doubleValue(); var lon = document.getField(LON).numericValue().doubleValue(); var modes = Arrays.asList(document.getValues(MODE)); - return new StopCluster(id, code, name, new Coordinate(lat, lon), modes); + var stopLocation = transitService.getStopLocation(id); + var agencies = transitService + .getAgenciesForStopLocation(stopLocation) + .stream() + .map(StopClusterMapper::toAgency) + .toList(); + var feedPublisher = StopClusterMapper.toFeedPublisher( + transitService.getFeedInfo(id.getFeedId()) + ); + return new StopCluster( + id, + code, + name, + new Coordinate(lat, lon), + modes, + agencies, + feedPublisher + ); } static IndexWriterConfig iwcWithSuggestField(Analyzer analyzer, final Set suggestFields) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java index afb60960ed4..8ffd44511fd 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.geocoder; import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -18,10 +19,22 @@ record StopCluster( @Nullable String code, String name, Coordinate coordinate, - Collection modes + Collection modes, + List agencies, + @Nullable FeedPublisher feedPublisher ) { /** * Easily serializable version of a coordinate */ public record Coordinate(double lat, double lon) {} + + /** + * Easily serializable version of an agency + */ + public record Agency(FeedScopedId id, String name) {} + + /** + * Easily serializable version of a feed publisher + */ + public record FeedPublisher(String name) {} } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index bc2f4f7022b..186d6d6b57e 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -6,6 +6,8 @@ import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.FeedInfo; +import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; import org.opentripplanner.transit.service.TransitService; @@ -62,7 +64,15 @@ StopCluster map(StopLocationsGroup g) { null, g.getName().toString(), toCoordinate(g.getCoordinate()), - modes + modes, + g + .getChildStops() + .stream() + .flatMap(sl -> transitService.getAgenciesForStopLocation(sl).stream()) + .distinct() + .map(StopClusterMapper::toAgency) + .toList(), + toFeedPublisher(transitService.getFeedInfo(g.getId().getFeedId())) ); } @@ -76,7 +86,13 @@ Optional map(StopLocation sl) { sl.getCode(), name.toString(), toCoordinate(sl.getCoordinate()), - modes + modes, + transitService + .getAgenciesForStopLocation(sl) + .stream() + .map(StopClusterMapper::toAgency) + .toList(), + toFeedPublisher(transitService.getFeedInfo(sl.getId().getFeedId())) ); }); } @@ -85,5 +101,17 @@ private static StopCluster.Coordinate toCoordinate(WgsCoordinate c) { return new StopCluster.Coordinate(c.latitude(), c.longitude()); } + static StopCluster.Agency toAgency(Agency a) { + return new StopCluster.Agency(a.getId(), a.getName()); + } + + static StopCluster.FeedPublisher toFeedPublisher(FeedInfo fi) { + if (fi == null) { + return null; + } else { + return new StopCluster.FeedPublisher(fi.getPublisherName()); + } + } + private record DeduplicationKey(I18NString name, WgsCoordinate coordinate) {} } diff --git a/src/main/java/org/opentripplanner/apis/APIEndpoints.java b/src/main/java/org/opentripplanner/apis/APIEndpoints.java index 68f6bda3d3a..555ea1659f1 100644 --- a/src/main/java/org/opentripplanner/apis/APIEndpoints.java +++ b/src/main/java/org/opentripplanner/apis/APIEndpoints.java @@ -61,6 +61,8 @@ private APIEndpoints() { addIfEnabled(SandboxAPIMapboxVectorTilesApi, VectorTilesResource.class); addIfEnabled(SandboxAPIParkAndRideApi, ParkAndRideResource.class); addIfEnabled(SandboxAPIGeocoder, GeocoderResource.class); + // scheduled to be removed and only here for backwards compatibility + addIfEnabled(SandboxAPIGeocoder, GeocoderResource.GeocoderResourceOldPath.class); addIfEnabled(SandboxAPITravelTime, TravelTimeResource.class); // scheduled to be removed diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index 0d08bcf34b2..91d129e0a0c 100644 --- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -572,6 +572,11 @@ public List getModesOfStopLocation(StopLocation stop) { return sortByOccurrenceAndReduce(getPatternModesOfStop(stop)).toList(); } + @Override + public List getAgenciesForStopLocation(StopLocation stop) { + return getRoutesForStop(stop).stream().map(Route::getAgency).distinct().toList(); + } + /** * For each pattern visiting this {@link StopLocation} return its {@link TransitMode} */ diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java index d0664aa292d..5006ed93f30 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -215,4 +215,6 @@ List stopTimesForPatternAtStop( * So, if more patterns of mode BUS than RAIL visit the stop, the result will be [BUS,RAIL]. */ List getModesOfStopLocation(StopLocation stop); + + List getAgenciesForStopLocation(StopLocation stop); }