Skip to content

Commit

Permalink
Merge pull request #95 from aodn/bugs/6008-fix-missing-centroid
Browse files Browse the repository at this point in the history
Fix bug in centroid point calcuation
  • Loading branch information
vietnguyengit authored Nov 21, 2024
2 parents 4cd47fc + 5b06c14 commit adeea87
Show file tree
Hide file tree
Showing 10 changed files with 746 additions and 18 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@
<artifactId>mockserver-client-java</artifactId>
<version>5.15.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
<version>0.8</version>
</dependency>
</dependencies>
</dependencyManagement>
<distributionManagement>
Expand Down
4 changes: 4 additions & 0 deletions server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@ public RestClientTransport restClientTransport() {
public ElasticsearchClient geoNetworkElasticsearchClient(RestClientTransport transport) {
return new ElasticsearchClient(transport);
}

/**
* The elastic search client to do the query
* @param client - The elastic search client
* @param mapper - Object mapper for string to object transformation
* @param indexName - The elastic index name that store the STAC from es-indexer
* @param pageSize - Do not set this value too high, say 5000 will crash elastic search
* @param searchAsYouTypeSize - The number of search result return for search as you type
* @return
*/
@Bean
public Search createElasticSearch(ElasticsearchClient client,
ObjectMapper mapper,
@Value("${elasticsearch.index.name}") String indexName,
@Value("${elasticsearch.index.pageSize:5000}") Integer pageSize,
@Value("${elasticsearch.index.pageSize:2500}") Integer pageSize,
@Value("${elasticsearch.search_as_you_type.size:10}") Integer searchAsYouTypeSize) {

return new ElasticSearch(client, mapper, indexName, pageSize, searchAsYouTypeSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ default <D extends StacCollectionModel> Collection getCollection(D m, Filter fil
if(m.getSummaries() != null ) {
Map<?, ?> noLand = m.getSummaries().getGeometryNoLand();
if (noLand != null) {
// Geometry from elastic search always store in EPSG4326
GeometryUtils.readGeometry(noLand)
.ifPresent(input -> {
Geometry g = filter != null ? (Geometry)filter.accept(visitor, input) : input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ public static CQLCrsType convertFromUrl(String url) {

public static Geometry transformGeometry(Geometry geometry, CQLCrsType source, CQLCrsType target) throws FactoryException, TransformException {

GeometryFactory factory = JTSFactoryFinder.getGeometryFactory();

CoordinateReferenceSystem sourceCRS = CRS.decode(source.code);
CoordinateReferenceSystem targetCRS = CRS.decode(target.code);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.geotools.filter.visitor.DefaultFilterVisitor;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.*;
import org.opengis.filter.spatial.Intersects;

@Slf4j
Expand All @@ -15,13 +13,11 @@ public class GeometryVisitor extends DefaultFilterVisitor {
@Override
public Object visit(Intersects filter, Object data) {
if(filter instanceof IntersectsImpl<?> impl) {
if(impl.getPreparedGeometry().isPresent()) {
if(impl.getGeometry().isPresent()) {
if (data instanceof Polygon || data instanceof GeometryCollection) {
// To handle minor precision issues, try applying a small buffer (like 0.0) to clean up
// minor topology errors. This is a trick commonly used with JTS
return impl.getPreparedGeometry().get()
.getGeometry()
.intersection(((Geometry) data).buffer(0.0));
return impl.getGeometry().get().intersection(((Geometry) data).buffer(0.0));
}
else {
return data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
import org.geotools.filter.AttributeExpressionImpl;
import org.geotools.filter.LiteralExpressionImpl;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
import org.opengis.filter.FilterVisitor;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.spatial.Intersects;
Expand All @@ -28,7 +25,7 @@ public class IntersectsImpl<T extends Enum<T> & CQLFieldsInterface> implements I
protected Expression expression2;

@Getter
protected Optional<PreparedGeometry> preparedGeometry = Optional.empty();
protected Optional<Geometry> geometry = Optional.empty();

public IntersectsImpl(Expression expression1, Expression expression2, CQLCrsType cqlCrsType) {
if(expression1 instanceof AttributeExpressionImpl attribute && expression2 instanceof LiteralExpressionImpl literal) {
Expand All @@ -37,7 +34,9 @@ public IntersectsImpl(Expression expression1, Expression expression2, CQLCrsType

try {
String geojson = GeometryUtils.convertToGeoJson(literal, cqlCrsType);
preparedGeometry = GeometryUtils.readGeometry(geojson).map(g -> PreparedGeometryFactory.prepare(g));
geometry = GeometryUtils
.readGeometry(geojson)
.map(g -> GeometryUtils.normalizePolygon(g));
}
catch(Exception ex) {
logger.warn("Exception in parsing, query result will be wrong", ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
Expand All @@ -25,7 +27,6 @@
public class GeometryUtils {

protected static final int PRECISION = 15;

protected static GeometryFactory factory = new GeometryFactory(new PrecisionModel(), 4326);

protected static ObjectMapper mapper = new ObjectMapper();
Expand Down Expand Up @@ -208,4 +209,15 @@ public static Optional<Geometry> readGeometry(Object input) {
return Optional.empty();
}
}
/**
* Normalize a polygon by adjusting longitudes to the range [-180, 180], and return both parts as a GeometryCollection.
*
* @param polygon The input polygon.
* @return A polygon / multi-polygon unwrap at dateline.
*/
public static Geometry normalizePolygon(Geometry polygon) {
// Set dateline 180 check to true to unwrap a polygon across -180 line
JtsGeometry jtsGeometry = new JtsGeometry(polygon, JtsSpatialContext.GEO, true, false);
return jtsGeometry.getGeom();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.ParseException;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
Expand Down Expand Up @@ -54,7 +55,7 @@ public static void init() {
* @throws ParseException - Will not throw
*/
@Test
public void verifyIntersectionWorks() throws CQLException, IOException, FactoryException, TransformException, ParseException {
public void verifyIntersectionWorks1() throws CQLException, IOException, FactoryException, TransformException, ParseException {
// Assume we have the following CQL
Converter.Param param = Converter.Param.builder()
.coordinationSystem(CQLCrsType.EPSG4326)
Expand Down Expand Up @@ -85,4 +86,43 @@ public void verifyIntersectionWorks() throws CQLException, IOException, FactoryE
Assertions.assertTrue(expected.isPresent(), "Expected parse correct");
Assertions.assertEquals(g, expected.get(), "They are equals");
}

/**
* Test case where POLYGON cross the -180 line, we should be able to handle it correctly.
* the parser will split the polygon into two and then apply the intersection with the noloand in json sample
* it will result in a single polygon and therefore we can calculate the centroid
*
* @throws CQLException - Will not throw
* @throws IOException - Will not throw
* @throws FactoryException - Will not throw
* @throws TransformException - Will not throw
* @throws ParseException - Will not throw
*/
@Test
public void verifyIntersectionWorks2() throws CQLException, IOException, FactoryException, TransformException, ParseException {

// Parse the json and get the noland section
String json = BaseTestClass.readResourceFile("classpath:databag/0015db7e-e684-7548-e053-08114f8cd4ad.json");
StacCollectionModel model = mapper.readValue(json, StacCollectionModel.class);

Filter filter = CompilerUtil.parseFilter(
Language.CQL,
"score>=1.5 AND INTERSECTS(geometry,POLYGON ((-203.16603491348164 -60.248194404495756, -86.85117538227594 -60.248194404495756, -86.85117538227594 15.902738674628525, -203.16603491348164 15.902738674628525, -203.16603491348164 -60.248194404495756)))",
factory);

Optional<Geometry> geo = GeometryUtils.readGeometry(model.getSummaries().getGeometryNoLand());

Assertions.assertTrue(geo.isPresent(), "Parse no land correct");
GeometryVisitor visitor = GeometryVisitor.builder()
.build();

// return value are geo applied the CQL, and in this case only INTERSECTS
Geometry g = (Geometry)filter.accept(visitor, geo.get());

Assertions.assertFalse(g.isEmpty());
Assertions.assertTrue(g instanceof Polygon);

Assertions.assertEquals(g.getCentroid().getX(), 168.30090846621448, "getX()");
Assertions.assertEquals(g.getCentroid().getY(), -33.95984804960966, "getY()");
}
}
Loading

0 comments on commit adeea87

Please sign in to comment.