Skip to content
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
6 changes: 6 additions & 0 deletions docs/changelog/91105.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 91105
summary: Fix handling indexed envelopes crossing the dateline in mvt API
area: Geo
type: bug
issues:
- 91060
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,61 @@ private Geometry normalize(Geometry geometry) {
}
}

public void testFetchSourceValueDateLine() throws IOException {
public void testFetchSourcePolygonDateLine() throws IOException {
assertFetchSourceGeometry(
"POLYGON((170 -10, -170 -10, -170 10, 170 10, 170 -10))",
"MULTIPOLYGON (((180.0 -10.0, 180.0 10.0, 170.0 10.0, 170.0 -10.0, 180.0 -10.0)),"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious why the polygon test has a single polygon split to a multipolygon by the function:

fetchSourceValue(mapper, sourceValue, "wkt")

But the BBOX is not split by the same function in the envelope test?

Perhaps this is not part of this PR, since this PR focuses on MVT, and the MVT call:

fetchSourceValue(mapper, sourceValue, "mvt(0/0/0@4096)")

Does seem to split both the polygon and the envelope.

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 thought about the same. The didn't like the options I had to return a split geometry. Either return a multipolygon which didn't feel right for an input envelope, or a geometry collection containing two envelopes which I did not like it either.

Therefore I decided to return it unchanged. We can visit it later if it dies not feel right.

+ "((-180.0 10.0, -180.0 -10.0, -170.0 -10.0, -170.0 10.0, -180.0 10.0)))",
Map.of(
"type",
"MultiPolygon",
"coordinates",
List.of(
List.of(
List.of(
List.of(180.0, -10.0),
List.of(180.0, 10.0),
List.of(170.0, 10.0),
List.of(170.0, -10.0),
List.of(180.0, -10.0)
)
),
List.of(
List.of(
List.of(-180.0, 10.0),
List.of(-180.0, -10.0),
List.of(-170.0, -10.0),
List.of(-170.0, 10.0),
List.of(-180.0, 10.0)
)
)
)
),
"MULTIPOLYGON (((180.0 -10.0, 180.0 10.0, 170.0 10.0, 170.0 -10.0, 180.0 -10.0)),"
+ "((-180.0 10.0, -180.0 -10.0, -170.0 -10.0, -170.0 10.0, -180.0 10.0)))"
);
}

public void testFetchSourceEnvelope() throws IOException {
assertFetchSourceGeometry(
"BBOX(-10, 10, 10, -10)",
"BBOX (-10.0, 10.0, 10.0, -10.0)",
Map.of("type", "Envelope", "coordinates", List.of(List.of(-10.0, 10.0), List.of(10.0, -10.0))),
"POLYGON ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))"
);
}

public void testFetchSourceEnvelopeDateLine() throws IOException {
assertFetchSourceGeometry(
"BBOX(10, -10, 10, -10)",
"BBOX (10.0, -10.0, 10.0, -10.0)",
Map.of("type", "Envelope", "coordinates", List.of(List.of(10.0, 10.0), List.of(-10.0, -10.0))),
"MULTIPOLYGON (((-180 -10, -10 -10, -10 10, -180 10, -180 -10)), ((10 -10, 180 -10, 180 10, 10 10, 10 -10)))"
);
}

private void assertFetchSourceGeometry(Object sourceValue, String wktValue, Map<String, Object> jsonValue, String mvtEquivalentAsWKT)
throws IOException {
final GeoFormatterFactory<Geometry> geoFormatterFactory = new GeoFormatterFactory<>(
new SpatialGeometryFormatterExtension().getGeometryFormatterFactories()
);
Expand All @@ -161,38 +215,13 @@ public void testFetchSourceValueDateLine() throws IOException {
false,
geoFormatterFactory
).build(MapperBuilderContext.ROOT).fieldType();
// Test a polygon crossing the dateline
Object sourceValue = "POLYGON((170 -10, -170 -10, -170 10, 170 10, 170 -10))";
String polygonDateLine = "MULTIPOLYGON (((180.0 -10.0, 180.0 10.0, 170.0 10.0, 170.0 -10.0, 180.0 -10.0)),"
+ "((-180.0 10.0, -180.0 -10.0, -170.0 -10.0, -170.0 10.0, -180.0 10.0)))";
Map<String, Object> jsonPolygonDateLine = Map.of(
"type",
"MultiPolygon",
"coordinates",
List.of(
List.of(
List.of(List.of(180.0, -10.0), List.of(180.0, 10.0), List.of(170.0, 10.0), List.of(170.0, -10.0), List.of(180.0, -10.0))
),
List.of(
List.of(
List.of(-180.0, 10.0),
List.of(-180.0, -10.0),
List.of(-170.0, -10.0),
List.of(-170.0, 10.0),
List.of(-180.0, 10.0)
)
)
)
);

assertEquals(List.of(jsonPolygonDateLine), fetchSourceValue(mapper, sourceValue, null));
assertEquals(List.of(polygonDateLine), fetchSourceValue(mapper, sourceValue, "wkt"));

String mvtPolygonDateLine = "MULTIPOLYGON (((180.0 -10.0, 180.0 10.0, 170.0 10.0, 170.0 -10.0, 180.0 -10.0)),"
+ "((-180.0 10.0, -180.0 -10.0, -170.0 -10.0, -170.0 10.0, -180.0 10.0)))";
assertEquals(List.of(jsonValue), fetchSourceValue(mapper, sourceValue, null));
assertEquals(List.of(wktValue), fetchSourceValue(mapper, sourceValue, "wkt"));

List<?> mvtExpected = fetchSourceValue(mapper, mvtPolygonDateLine, "mvt(0/0/0@256)");
List<?> mvt = fetchSourceValue(mapper, sourceValue, "mvt(0/0/0@256)");
final int extent = randomIntBetween(256, 4096);
List<?> mvtExpected = fetchSourceValue(mapper, mvtEquivalentAsWKT, "mvt(0/0/0@" + extent + ")");
List<?> mvt = fetchSourceValue(mapper, sourceValue, "mvt(0/0/0@" + extent + ")");
assertThat(mvt.size(), Matchers.equalTo(1));
assertThat(mvt.size(), Matchers.equalTo(mvtExpected.size()));
assertThat(mvtExpected.get(0), Matchers.instanceOf(byte[].class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,15 @@ public org.locationtech.jts.geom.Geometry visit(Rectangle rectangle) throws Runt
final double yMin = SphericalMercatorUtils.latToSphericalMercator(rectangle.getMinY());
final double xMax = SphericalMercatorUtils.lonToSphericalMercator(rectangle.getMaxX());
final double yMax = SphericalMercatorUtils.latToSphericalMercator(rectangle.getMaxY());
final Envelope envelope = new Envelope(xMin, xMax, yMin, yMax);
return geomFactory.toGeometry(envelope);
if (rectangle.getMinX() > rectangle.getMaxX()) {
// crosses dateline
final Envelope westEnvelope = new Envelope(-SphericalMercatorUtils.MERCATOR_BOUNDS, xMax, yMin, yMax);
final Envelope eastEnvelope = new Envelope(xMin, SphericalMercatorUtils.MERCATOR_BOUNDS, yMin, yMax);
return geomFactory.buildGeometry(List.of(geomFactory.toGeometry(westEnvelope), geomFactory.toGeometry(eastEnvelope)));
} else {
final Envelope envelope = new Envelope(xMin, xMax, yMin, yMax);
return geomFactory.toGeometry(envelope);
}
}
}

Expand Down