diff --git a/pom.xml b/pom.xml
index e4515ae4..8f401a87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -118,7 +118,7 @@
org.testcontainers
elasticsearch
- 1.20.1
+ 1.21.3
pom
import
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/CacheConfig.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/CacheConfig.java
index 250da0c5..4722b352 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/CacheConfig.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/CacheConfig.java
@@ -8,6 +8,7 @@
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
@@ -33,6 +34,7 @@ public class CacheConfig {
public static final String ALL_PARAM_VOCABS = "parameter-vocabs";
public static final String ELASTIC_SEARCH_UUID_ONLY = "elastic-search-uuid-only";
public static final String STRING_TO_GEOMETRY = "string-to-geometry";
+ public static final String STRING_TO_PREPARE_GEOMETRY = "string-to-prepared-geometry";
@Bean
public CacheNoLandGeometry createCacheNoLandGeometry() {
@@ -89,6 +91,12 @@ public JCacheCacheManager cacheManager() throws IOException {
ResourcePoolsBuilder.heap(20000)
).withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(24)))
)
+ .withCache(STRING_TO_PREPARE_GEOMETRY,
+ CacheConfigurationBuilder.newCacheConfigurationBuilder(
+ Map.class, PreparedGeometry.class,
+ ResourcePoolsBuilder.heap(20000)
+ ).withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(24)))
+ )
.withCache(GET_CAPABILITIES_WMS_LAYERS,
CacheConfigurationBuilder.newCacheConfigurationBuilder(
Object.class, Object.class,
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/mapper/Converter.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/mapper/Converter.java
index 7cc89cc7..05ee044c 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/mapper/Converter.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/mapper/Converter.java
@@ -2,7 +2,6 @@
import au.org.aodn.ogcapi.features.model.*;
import au.org.aodn.ogcapi.server.core.model.CitationModel;
-import au.org.aodn.ogcapi.server.core.model.AssetModel;
import au.org.aodn.ogcapi.server.core.model.ExtendedCollection;
import au.org.aodn.ogcapi.server.core.model.ExtendedLink;
import au.org.aodn.ogcapi.server.core.model.StacCollectionModel;
@@ -15,6 +14,7 @@
import lombok.Getter;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.opengis.filter.Filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -156,11 +156,11 @@ default Collection getCollection(D m, Filter fil
Map, ?> noLand = m.getSummaries().getGeometryNoLand();
if (noLand != null) {
// Geometry from elastic search always store in EPSG4326
- GeometryUtils.readGeometry(noLand)
+ GeometryUtils.readPreparedGeometry(noLand)
.ifPresent(input -> {
// filter have values if user CQL contains BBox, hence our centroid point needs to be
// the noland geometry intersect with BBox and centroid point will be within the BBox
- Geometry g = filter != null ? (Geometry) filter.accept(visitor, input) : input;
+ Geometry g = filter != null ? ((PreparedGeometry) filter.accept(visitor, input)).getGeometry() : input.getGeometry();
collection.getProperties().put(
CollectionProperty.centroid,
createCentroid(g)
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/parser/stac/GeometryVisitor.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/parser/stac/GeometryVisitor.java
index fd5223fe..f7ed9192 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/parser/stac/GeometryVisitor.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/parser/stac/GeometryVisitor.java
@@ -4,6 +4,7 @@
import lombok.extern.slf4j.Slf4j;
import org.geotools.filter.visitor.DefaultFilterVisitor;
import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.opengis.filter.spatial.*;
@Slf4j
@@ -116,14 +117,14 @@ public Object visit(Beyond filter, Object data) {
@Override
public Object visit(BBOX filter, Object data) {
if(filter instanceof BBoxImpl> impl) {
- if(impl.getGeometry() != null && (data instanceof Polygon || data instanceof GeometryCollection)) {
- Geometry input = (Geometry) data;
+ if(impl.getGeometry() != null && (data instanceof Polygon || data instanceof PreparedGeometry)) {
+ PreparedGeometry input = (PreparedGeometry) data;
// buffer is expensive
try {
- return impl.getGeometry().intersection(input);
+ return input.getGeometry().intersection(impl.getGeometry());
}
catch(Exception e) {
- return impl.getGeometry().intersection(input.buffer(0.0));
+ return impl.getGeometry().intersection(input.getGeometry().buffer(0.0));
}
//return impl.getGeometry().intersection(input.buffer(0.0));
}
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/util/GeometryUtils.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/util/GeometryUtils.java
index 1d629f6e..4251e401 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/util/GeometryUtils.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/util/GeometryUtils.java
@@ -9,6 +9,8 @@
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
@@ -29,6 +31,7 @@
import java.util.concurrent.*;
import static au.org.aodn.ogcapi.server.core.configuration.CacheConfig.STRING_TO_GEOMETRY;
+import static au.org.aodn.ogcapi.server.core.configuration.CacheConfig.STRING_TO_PREPARE_GEOMETRY;
public class GeometryUtils {
@@ -211,7 +214,26 @@ public static String convertToGeoJson(LiteralExpressionImpl literalExpression, C
return r;
}
}
-
+ /**
+ * Many code require static access to this function, hence we will init it somewhere as bean and then
+ * set it to the static instance for sharing
+ *
+ * @param input - A geometry text input
+ * @return - The converted geometry
+ */
+ public static Optional readPreparedGeometry(Object input) {
+ return self.readCachedPreparedGeometry(input);
+ }
+ /**
+ * Please use this function as it contains the parser with enough decimal to make it work.
+ *
+ * @param input - A Json of GeoJson
+ * @return - A prepared geometry for fast intersect calculation
+ */
+ @Cacheable(STRING_TO_PREPARE_GEOMETRY)
+ public Optional readCachedPreparedGeometry(Object input) {
+ return self.readCachedGeometry(input).map(PreparedGeometryFactory::prepare);
+ }
/**
* Many code require static access to this function, hence we will init it somewhere as bean and then
* set it to the static instance for sharing
@@ -222,7 +244,6 @@ public static String convertToGeoJson(LiteralExpressionImpl literalExpression, C
public static Optional readGeometry(Object input) {
return self.readCachedGeometry(input);
}
-
/**
* Please use this function as it contains the parser with enough decimal to make it work.
*
@@ -244,7 +265,6 @@ public Optional readCachedGeometry(Object input) {
}
return Optional.empty();
}
-
/**
* Normalize a polygon by adjusting longitudes to the range [-180, 180], and return both parts as a GeometryCollection.
*
diff --git a/server/src/test/java/au/org/aodn/ogcapi/server/core/parser/stac/ParserTest.java b/server/src/test/java/au/org/aodn/ogcapi/server/core/parser/stac/ParserTest.java
index 2a8fd6c0..4b6af837 100644
--- a/server/src/test/java/au/org/aodn/ogcapi/server/core/parser/stac/ParserTest.java
+++ b/server/src/test/java/au/org/aodn/ogcapi/server/core/parser/stac/ParserTest.java
@@ -17,8 +17,8 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.io.ParseException;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
@@ -74,7 +74,7 @@ public void verifyBBoxWorks1() throws CQLException, IOException, FactoryExceptio
StacCollectionModel model = mapper.readValue(json, StacCollectionModel.class);
Filter filter = CompilerUtil.parseFilter(Language.CQL, param.getFilter(), factory);
- Optional geo = GeometryUtils.readGeometry(model.getSummaries().getGeometryNoLand());
+ Optional geo = GeometryUtils.readPreparedGeometry(model.getSummaries().getGeometryNoLand());
Assertions.assertTrue(geo.isPresent(), "Parse no land correct");
GeometryVisitor visitor = GeometryVisitor.builder()
@@ -83,7 +83,7 @@ public void verifyBBoxWorks1() throws CQLException, IOException, FactoryExceptio
// return value are geo applied the CQL, and in this case only INTERSECTS
Geometry g = (Geometry)filter.accept(visitor, geo.get());
Assertions.assertTrue(expected.isPresent(), "Expected parse correct");
- Assertions.assertEquals(g, expected.get(), "They are equals");
+ Assertions.assertEquals(g.getGeometryN(0), expected.get(), "They are equals");
}
/**
* Test almost the same as the verifyIntersectionWorks2, since verifyIntersectionWorks1 create a polygon same as box
@@ -104,7 +104,7 @@ public void verifyBBoxWorks2() throws CQLException, IOException {
"score>=1.5 AND BBOX(geometry,-203.16603491348164,-60.248194404495756,-86.85117538227594,15.902738674628525)",
factory);
- Optional geo = GeometryUtils.readGeometry(model.getSummaries().getGeometryNoLand());
+ Optional geo = GeometryUtils.readPreparedGeometry(model.getSummaries().getGeometryNoLand());
Assertions.assertTrue(geo.isPresent(), "Parse no land correct");
GeometryVisitor visitor = GeometryVisitor.builder()
@@ -114,7 +114,7 @@ public void verifyBBoxWorks2() throws CQLException, IOException {
Geometry g = (Geometry)filter.accept(visitor, geo.get());
Assertions.assertFalse(g.isEmpty());
- Assertions.assertInstanceOf(Polygon.class, g);
+ Assertions.assertInstanceOf(GeometryCollection.class, g);
Assertions.assertEquals(168.30090846621448, g.getCentroid().getX(), 0.0000001, "getX()");
Assertions.assertEquals(-33.95984804960966, g.getCentroid().getY(), 0.0000001, "getY()");
@@ -138,7 +138,7 @@ public void verifyBBoxWorks3() throws CQLException, IOException {
"score>=1.5 AND BBOX(geometry,-209.8851491167079,-45.44715475181477,-149.06483661670887,-5.632766095762394)",
factory);
- Optional geo = GeometryUtils.readGeometry(model.getSummaries().getGeometryNoLand());
+ Optional geo = GeometryUtils.readPreparedGeometry(model.getSummaries().getGeometryNoLand());
Assertions.assertTrue(geo.isPresent(), "Parse no land correct");
GeometryVisitor visitor = GeometryVisitor.builder()
@@ -147,15 +147,15 @@ public void verifyBBoxWorks3() throws CQLException, IOException {
// return value are geo applied the CQL, and in this case only BBOX intersected
Geometry g = (Geometry)filter.accept(visitor, geo.get());
- Assertions.assertInstanceOf(MultiPolygon.class, g);
+ Assertions.assertInstanceOf(GeometryCollection.class, g);
- MultiPolygon mp = (MultiPolygon)g;
+ GeometryCollection mp = (GeometryCollection)g;
Assertions.assertEquals(2, mp.getNumGeometries(), "Geometries correct");
- Assertions.assertEquals(-159.53241830835444, mp.getGeometryN(1).getCentroid().getX(), 0.0000001, "getX() for 0");
- Assertions.assertEquals(-19.5, mp.getGeometryN(1).getCentroid().getY(), 0.0000001, "getY() for 0");
+ Assertions.assertEquals(-159.53241830835444, mp.getGeometryN(0).getCentroid().getX(), 0.0000001, "getX() for 0");
+ Assertions.assertEquals(-19.5, mp.getGeometryN(0).getCentroid().getY(), 0.0000001, "getY() for 0");
- Assertions.assertEquals(151.62121416760516, mp.getGeometryN(0).getCentroid().getX(), 0.0000001, "getX() for 1");
- Assertions.assertEquals(-18.000822620336752, mp.getGeometryN(0).getCentroid().getY(), 0.0000001, "getY() for 1");
+ Assertions.assertEquals(151.62121416760516, mp.getGeometryN(1).getCentroid().getX(), 0.0000001, "getX() for 1");
+ Assertions.assertEquals(-18.000822620336752, mp.getGeometryN(1).getCentroid().getY(), 0.0000001, "getY() for 1");
}
}