8000 Validate flex stop areas by testower · Pull Request #702 · entur/uttu · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Validate flex stop areas #702

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.

Al 8000 ready on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
Expand All @@ -16,8 +17,14 @@
import no.entur.uttu.netex.NetexUnmarshaller;
import no.entur.uttu.netex.NetexUnmarshallerUnmarshalFromSourceException;
import no.entur.uttu.stopplace.filter.StopPlacesFilter;
import no.entur.uttu.stopplace.filter.params.BoundingBoxFilterParams;
import no.entur.uttu.stopplace.filter.params.StopPlaceFilterParams;
import no.entur.uttu.stopplace.spi.StopPlaceRegistry;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.index.strtree.STRtree;
import org.rutebanken.netex.model.PublicationDeliveryStructure;
import org.rutebanken.netex.model.Quay;
import org.rutebanken.netex.model.SiteFrame;
Expand Down Expand Up @@ -50,6 +57,9 @@

private final List<StopPlace> allStopPlacesIndex = new ArrayList<>();

private final STRtree spatialIndex = new STRtree();
private final GeometryFactory geometryFactory = new GeometryFactory();

@Value("${uttu.stopplace.netex-file-uri}")
String netexFileUri;

Expand All @@ -66,6 +76,7 @@
PublicationDeliveryStructure publicationDeliveryStructure =
netexUnmarshaller.unmarshalFromSource(new StreamSource(netexFileInputStream));
extractStopPlaceData(publicationDeliveryStructure);
buildSpatialIndex();

Check warning on line 79 in src/main/java/no/entur/uttu/stopplace/NetexPublicationDeliveryFileStopPlaceRegistry.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/no/entur/uttu/stopplace/NetexPublicationDeliveryFileStopPlaceRegistry.java#L79

Added line #L79 was not covered by tests
} catch (IOException ioException) {
// probably not a zip file
logger.info("Not a zip file", ioException);
Expand All @@ -74,6 +85,7 @@
PublicationDeliveryStructure publicationDeliveryStructure =
netexUnmarshaller.unmarshalFromSource(new StreamSource(new File(netexFileUri)));
extractStopPlaceData(publicationDeliveryStructure);
buildSpatialIndex();
} catch (
NetexUnmarshallerUnmarshalFromSourceException unmarshalFromSourceException
) {
Expand Down Expand Up @@ -131,13 +143,121 @@

@Override
public List<StopPlace> getStopPlaces(List<StopPlaceFilterParams> filters) {
return filters.isEmpty()
? allStopPlacesIndex
: stopPlacesFilter.filter(allStopPlacesIndex, stopPlaceByQuayRefIndex, filters);
if (filters.isEmpty()) {
return allStopPlacesIndex;
}

// Check if we can optimize with spatial pre-filtering
Optional<BoundingBoxFilterParams> boundingBoxFilter = filters
.stream()
.filter(BoundingBoxFilterParams.class::isInstance)
.map(BoundingBoxFilterParams.class::cast)
.findFirst();

if (boundingBoxFilter.isPresent()) {
return getStopPlacesOptimized(filters, boundingBoxFilter.get());
}

return stopPlacesFilter.filter(allStopPlacesIndex, stopPlaceByQuayRefIndex, filters);

Check warning on line 161 in src/main/java/no/entur/uttu/stopplace/NetexPublicationDeliveryFileStopPlaceRegistry.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/no/entur/uttu/stopplace/NetexPublicationDeliveryFileStopPlaceRegistry.java#L161

Added line #L161 was not covered by tests
}

/**
* Optimized stop place filtering that uses spatial index for bounding box pre-filtering
*/
private List<StopPlace> getStopPlacesOptimized(
List<StopPlaceFilterParams> filters,
BoundingBoxFilterParams boundingBoxFilter
) {
// Step 1: Use spatial index to pre-filter by bounding box
Polygon boundingBoxPolygon = createPolygonFromBoundingBox(boundingBoxFilter);
List<StopPlace> spatiallyFilteredStops = getStopPlacesWithinPolygon(
boundingBoxPolygon
);

logger.debug(
"Spatial pre-filtering reduced stop places from {} to {}",
allStopPlacesIndex.size(),
spatiallyFilteredStops.size()
);

// Step 2: Apply remaining filters to the spatially pre-filtered set
return stopPlacesFilter.filter(
spatiallyFilteredStops,
stopPlaceByQuayRefIndex,
filters
);
10000 }

/**
* Convert BoundingBoxFilterParams to JTS Polygon for spatial querying
*/
private Polygon createPolygonFromBoundingBox(BoundingBoxFilterParams boundingBox) {
double swLat = boundingBox.southWestLat().doubleValue();
double swLng = boundingBox.southWestLng().doubleValue();
double neLat = boundingBox.northEastLat().doubleValue();
double neLng = boundingBox.northEastLng().doubleValue();

Coordinate[] coords = {
new Coordinate(swLng, swLat), // SW
new Coordinate(neLng, swLat), // SE
new Coordinate(neLng, neLat), // NE
new Coordinate(swLng, neLat), // NW
new Coordinate(swLng, swLat), // Close the ring
};

return geometryFactory.createPolygon(coords);
}

@Override
public Optional<Quay> getQuayById(String id) {
return Optional.ofNullable(quayByQuayRefIndex.get(id));
}

@Override
public List<StopPlace> getStopPlacesWithinPolygon(Polygon polygon) {
if (polygon == null) {
return new ArrayList<>();
}

// Step 1: Fast spatial query using bounding box
@SuppressWarnings("unchecked")
List<StopPlace> candidates = spatialIndex.query(polygon.getEnvelopeInternal());

// Step 2: Precise polygon containment check
return candidates
.stream()
.filter(stopPlace -> {
Point point = createPointFromStopPlace(stopPlace);
return point != null && polygon.contains(point);
})
.toList();
}

private void buildSpatialIndex() {
logger.info("Building spatial index with {} stop places", allStopPlacesIndex.size());

for (StopPlace stopPlace : allStopPlacesIndex) {
Point point = createPointFromStopPlace(stopPlace);
if (point != null) {
spatialIndex.insert(point.getEnvelopeInternal(), stopPlace);
}
}

spatialIndex.build();
logger.info("Spatial index built successfully");
}

private Point createPointFromStopPlace(StopPlace stopPlace) {
if (
stopPlace.getCentroid() == null || stopPlace.getCentroid().getLocation() == null
) {
return null;
}

var centroid = stopPlace.getCentroid().getLocation();
double longitude = centroid.getLongitude().doubleValue();
double latitude = centroid.getLatitude().doubleValue();

return geometryFactory.createPoint(new Coordinate(longitude, latitude));
}
}
43 changes: 2 additions & 41 deletions src/main/java/no/entur/uttu/stopplace/filter/StopPlacesFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static no.entur.uttu.error.codes.ErrorCodeEnumeration.INVALID_STOP_PLACE_FILTER;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -110,14 +109,8 @@ private boolean includeStopPlace(
for (StopPlaceFilterParams f : filters) {
switch (f) {
case BoundingBoxFilterParams boundingBoxFilterParams -> {
boolean isInsideBoundingBox = isStopPlaceWithinBoundingBox(
boundingBoxFilterParams,
stopPlace,
quays
);
if (!isInsideBoundingBox) {
return false;
}
// BoundingBox filtering is now handled by spatial pre-filtering in the registry
// Skip this filter as it's already been applied
}
case TransportModeStopPlaceFilterParams transportModeFilterParams -> {
boolean isOfTransportMode =
Expand Down Expand Up @@ -158,38 +151,6 @@ private boolean foundMatchForSearchText(
);
}

private boolean isStopPlaceWithinBoundingBox(
BoundingBoxFilterParams boundingBoxFilterParams,
StopPlace stopPlace,
List<Quay> quays
) {
BigDecimal lat = Optional
.ofNullable(stopPlace.getCentroid())
.map(centroid -> centroid.getLocation().getLatitude())
.orElse(null);
BigDecimal lng = Optional
.ofNullable(stopPlace.getCentroid())
.map(centroid -> centroid.getLocation().getLongitude())
.orElse(null);

if (!quays.isEmpty() && (lat == null || lng == null)) {
Quay firstQuay = quays.getFirst();
lat = firstQuay.getCentroid().getLocation().getLatitude();
lng = firstQuay.getCentroid().getLocation().getLongitude();
}
if (lat == null || lng == null) {
// oh well, we tried
return false;
}

return (
lat.compareTo(boundingBoxFilterParams.northEastLat()) < 0 &&
lng.compareTo(boundingBoxFilterParams.northEastLng()) < 0 &&
lat.compareTo(boundingBoxFilterParams.southWestLat()) > 0 &&
lng.compareTo(boundingBoxFilterParams.southWestLng()) > 0
);
}

private List<StopPlace> getStopPlacesByQuayIds(
QuayIdFilterParams quayIdFilterParams,
Map<String, StopPlace> stopPlaceByQuayRefIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import java.util.Optional;
import no.entur.uttu.stopplace.filter.params.StopPlaceFilterParams;
import org.locationtech.jts.geom.Polygon;

/**
* Represents a stop place registry used to lookup stop places from quay refs
Expand Down Expand Up @@ -46,4 +47,11 @@ List<org.rutebanken.netex.model.StopPlace> getStopPlaces(
* @return The quay entity
*/
Optional<org.rutebanken.netex.model.Quay> getQuayById(String id);

/**
* Find stop places within a given polygon area
* @param polygon The polygon area to search within
* @return List of stop places whose centroids are within the polygon
*/
List<org.rutebanken.netex.model.StopPlace> getStopPlacesWithinPolygon(Polygon polygon);
}
Loading
0