Skip to content

Conversation

@ncordon
Copy link
Contributor

@ncordon ncordon commented Nov 20, 2025

We need this to merge #136309.

Why

Our wkt representation should be locale independant.

Newer versions of jts correct at least an important bug when transforming jts's Geometry to wkt (we would have discrepancies in dots vs commas for floating part otherwise): locationtech/jts#473.

Also I've seen they solve an issue where the minus character for negative numbers was also locale dependant.

Our jts version was 8 years outdated anyway, so it's good to update it.

@ncordon ncordon added :Analytics/Geo Indexing, search aggregations of geo points and shapes >upgrade Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) labels Nov 20, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @ncordon, I've created a changelog YAML for you.

@ncordon ncordon changed the title Bumps jts version Bumps jts version to 1.20.0 Nov 20, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @ncordon, I've created a changelog YAML for you.

@ncordon ncordon marked this pull request as ready for review November 21, 2025 10:37
@ncordon ncordon requested a review from a team as a code owner November 21, 2025 10:37
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

Comment on lines -170 to 172
InvalidShapeException e = expectThrows(InvalidShapeException.class, pb::buildS4J);
assertEquals("Self-intersection at or near point (-1.0, -1.0, NaN)", e.getMessage());
assertEquals("Self-intersection at or near point (0.0, 0.0, NaN)", e.getMessage());
}
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 think this error is still correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think this is still correct, since either of the two points could be determined as the self-intersection point.

Since legacy-geo has been deprecated for a long time (6.x, I think?) a subtle change like this should be a non-issue. I think a change like this should not even matter for our main spatial module, since it does not affect any happy paths, only the possible error message for failing paths.

Copy link

@quackaplop quackaplop left a comment

Choose a reason for hiding this comment

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

Sorry for the drive-by reviewing – I just accidentally came across this one.
Two silly questions

  • How convinced are we that the behavior between 1.15 and 1.20 is not going to break much on our end? I am not super familiar with JTS, so this is mainly paranoia.
  • You did have to change once test - I think this smells like a possible breaking change? is it?

Copy link
Contributor

@craigtaverner craigtaverner left a comment

Choose a reason for hiding this comment

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

I think it is good to keep JTS up-to-date. It is nice that the only test change was in an error message, and in a case where either the old error or the new one would mean essentially the same thing.

I do think we should consider removing the old verification data.

<sha256 value="00102cde26c457b81fbb0248e4f8845884243caba0dc9b7fb42e0ea877383bc1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.locationtech.jts" name="jts-core" version="1.20.0">
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we remove the verification data for the old JTS (line 4264 above)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I still think this is a good idea, to remove the old line.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, please remove the unused line.

Comment on lines -170 to 172
InvalidShapeException e = expectThrows(InvalidShapeException.class, pb::buildS4J);
assertEquals("Self-intersection at or near point (-1.0, -1.0, NaN)", e.getMessage());
assertEquals("Self-intersection at or near point (0.0, 0.0, NaN)", e.getMessage());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think this is still correct, since either of the two points could be determined as the self-intersection point.

Since legacy-geo has been deprecated for a long time (6.x, I think?) a subtle change like this should be a non-issue. I think a change like this should not even matter for our main spatial module, since it does not affect any happy paths, only the possible error message for failing paths.

@iverase
Copy link
Contributor

iverase commented Nov 21, 2025

We need to make sure this does not break the vector tiles API. I will run some test locally.

@ncordon
Copy link
Contributor Author

ncordon commented Nov 25, 2025

How convinced are we that the behavior between 1.15 and 1.20 is not going to break much on our end? I am not super familiar with JTS, so this is mainly paranoia.

@iverase did find it introduced a bug in vector tiles that he had a fix for, I've added a test to avoid that regression happens again.

The alternative would be to stick to this version and patch the parts we need on our end (for example the WKT format seemed locale dependant in the old version for commas and dots, it has been fixed in the new ones). Given the library was highly outdated (8 years between 1.15 and 1.20) we lean heavily towards going ahead and updating it.

You did have to change once test - I think this smells like a possible breaking change? is it?

It was not breaking since the error is still correct and @craigtaverner confirmed that.

Comment on lines 307 to 309
var coordinates = mvtGeometry.getCoordinates();
var containsCenter = Arrays.stream(coordinates).anyMatch(c -> c.x == 2048 && c.y == 2048);
assertFalse(containsCenter);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could assert here on the exact contents of the vector tiles, but they are jts version dependent, and it changes from 1.15 so 1.20, so I tried to add a version independent check instead.

Copy link
Contributor

@craigtaverner craigtaverner left a comment

Choose a reason for hiding this comment

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

Great fix. I did have suggestions for a few comments and assertion messages to help make things clearer to any future developers stumbling across this code and wondering about it. The fix is quite subtle, so I think the comments are useful.

<sha256 value="00102cde26c457b81fbb0248e4f8845884243caba0dc9b7fb42e0ea877383bc1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.locationtech.jts" name="jts-core" version="1.20.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 still think this is a good idea, to remove the old line.

}

/**
* Testing purposes only
Copy link
Contributor

Choose a reason for hiding this comment

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

Could elaborate a little

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Testing purposes only
* Testing purposes only, allowing us to test the impact of the geometry clipping and simplification
without needing to parse the protobuffer results.

* Testing purposes only
*/
org.locationtech.jts.geom.Geometry getMvtGeometry(Geometry geometry) {
// Get geometry in pixel coordinates
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Get geometry in pixel coordinates
// Get clipped and simplified geometry in pixel coordinates

public void filter(CoordinateSequence seq, int i) {
seq.setOrdinate(i, 0, lon(seq.getOrdinate(i, 0)));
seq.setOrdinate(i, 1, lat(seq.getOrdinate(i, 1)));
if (i != 0 && i == seq.size() - 1 && seq.getCoordinate(0) == seq.getCoordinate(i)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a comment here might be useful. It is especially not clear why we need to do reference equality vs contents equality. We could add a comment like:

// JTS can sometimes close polygons using references to the starting coordinate, which can lead to that coordinate being converted twice.
// We need to detect if the end is the same actual Coordinate, and not do the conversion again.

var mvtGeometry = builder.getMvtGeometry(polygon);
var coordinates = mvtGeometry.getCoordinates();
var containsCenter = Arrays.stream(coordinates).anyMatch(c -> c.x == 2048 && c.y == 2048);
assertFalse(containsCenter);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assertFalse(containsCenter);
assertFalse(containsCenter, "Coordinate (2024,2024) only occurs with the double conversion bug");

var mvtGeometry = builder.getMvtGeometry(geometry);
var coordinates = mvtGeometry.getCoordinates();
var containsCenter = Arrays.stream(coordinates).anyMatch(c -> c.x == 2048 && c.y == 2048);
assertFalse(containsCenter);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assertFalse(containsCenter);
assertFalse(containsCenter, "Coordinate (2024,2024) only occurs with the double conversion bug");

var mvtGeometry = builder.getMvtGeometry(polygon);
var coordinates = mvtGeometry.getCoordinates();
var containsCenter = Arrays.stream(coordinates).anyMatch(c -> c.x == 2048 && c.y == 2048);
assertFalse(containsCenter);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assertFalse(containsCenter);
assertFalse(containsCenter, "Coordinate (2024,2024) only occurs with the double conversion bug");

Copy link
Contributor

@iverase iverase left a comment

Choose a reason for hiding this comment

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

LGTM

<sha256 value="00102cde26c457b81fbb0248e4f8845884243caba0dc9b7fb42e0ea877383bc1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.locationtech.jts" name="jts-core" version="1.20.0">
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, please remove the unused line.

@ncordon ncordon merged commit e6316ad into elastic:main Nov 25, 2025
34 checks passed
ncordon added a commit to ncordon/elasticsearch that referenced this pull request Nov 26, 2025
ncordon added a commit to ncordon/elasticsearch that referenced this pull request Nov 26, 2025
ncordon added a commit to ncordon/elasticsearch that referenced this pull request Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Analytics/Geo Indexing, search aggregations of geo points and shapes Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) >upgrade v9.3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants