1919import org .locationtech .jts .geom .Coordinate ;
2020import org .locationtech .jts .geom .Geometry ;
2121import org .locationtech .jts .geom .GeometryFactory ;
22- import org .locationtech .jts .geom .Point ;
2322import org .locationtech .jts .geom .PrecisionModel ;
2423import org .locationtech .jts .io .ParseException ;
2524import org .locationtech .jts .io .WKBWriter ;
25+ import org .locationtech .jts .io .WKTReader ;
2626import org .locationtech .jts .io .WKTWriter ;
27+ import org .locationtech .spatial4j .exception .InvalidShapeException ;
2728import org .locationtech .spatial4j .shape .Shape ;
2829import org .locationtech .spatial4j .shape .jts .JtsGeometry ;
2930import org .locationtech .spatial4j .shape .jts .JtsPoint ;
@@ -50,6 +51,10 @@ public class GeoExtensionProcessor extends AbstractProcessor {
5051 private final String bboxField ;
5152 private final String centroidField ;
5253
54+ private final GeometryFactory geomFactory ;
55+ private final WKTWriter wktWriter ;
56+ private final WKTReader wktReader ;
57+
5358 private GeoExtensionProcessor (String tag , String description , String field , String path , Boolean keepShape , String shapeField ,
5459 String fixedField , String wkbField , String hashField , String typeField ,
5560 String areaField , String bboxField , String centroidField ) {
@@ -65,6 +70,12 @@ private GeoExtensionProcessor(String tag, String description, String field, Stri
6570 this .areaField = areaField ;
6671 this .bboxField = bboxField ;
6772 this .centroidField = centroidField ;
73+
74+ PrecisionModel precisionModel = new PrecisionModel (PrecisionModel .FLOATING );
75+ this .geomFactory = new GeometryFactory (precisionModel , 0 );
76+
77+ this .wktWriter = new WKTWriter ();
78+ this .wktReader = new WKTReader ();
6879 }
6980
7081 @ SuppressWarnings ("unchecked" )
@@ -102,6 +113,72 @@ private List<String> getGeoShapeFieldsFromDoc(IngestDocument ingestDocument) {
102113 return ShapeParser .parse (parser );
103114 }
104115
116+ private class GeometryWithWkt {
117+ public Geometry geometry ;
118+ public String wkt ;
119+ }
120+
121+ private GeometryWithWkt s4jToJts (Shape shape ) {
122+ // Convert a Spatial4J shape into a JTSGeometry
123+ // A Spatial4J shape is basically either:
124+ // - a JtsPoint
125+ // - a JtsGeometry
126+ // - an XShapeCollection (multi-* and GeometryCollection)
127+ // There is a special case for MultiPoint since the WKTWriter of JTS
128+ // is not compliant with the wkt reader of ES.
129+
130+ GeometryWithWkt geomWithWkt = new GeometryWithWkt ();
131+ Geometry geom = null ;
132+
133+ String altWKT = null ;
134+ if (shape instanceof JtsPoint ) {
135+ geom = ((JtsPoint ) shape ).getGeom ();
136+ } else if (shape instanceof JtsGeometry ) {
137+ geom = ((JtsGeometry ) shape ).getGeom ();
138+ } else if (shape instanceof XShapeCollection ) {
139+ XShapeCollection collection = (XShapeCollection ) shape ;
140+ ArrayList <Geometry > geoms = new ArrayList <>(collection .size ());
141+ ArrayList <String > wkts = new ArrayList <>(collection .size ());
142+
143+ boolean hasWkt = false ;
144+ for (int i = 0 ; i < collection .size (); i ++) {
145+ GeometryWithWkt g = s4jToJts (collection .get (i ));
146+ geoms .add (g .geometry );
147+ wkts .add (g .wkt );
148+ if (g .wkt != null ) {
149+ hasWkt = true ;
150+ }
151+ }
152+
153+ if (hasWkt ) {
154+ // if it has a WKT, it means it is a geometrycolleciton
155+ // containing a multipoint
156+ altWKT = "GEOMETRYCOLLECTION(" ;
157+ for (int i = 0 ; i < collection .size (); i ++) {
158+ if (wkts .get (i ) != null ) {
159+ altWKT += wkts .get (i );
160+ } else {
161+ altWKT += wktWriter .write (geoms .get (i ));
162+ }
163+ if (i < collection .size () - 1 ) {
164+ altWKT += "," ;
165+ }
166+ }
167+ altWKT += ")" ;
168+ }
169+ geom = geomFactory .buildGeometry (geoms );
170+
171+ if (geom .getGeometryType () == "MultiPoint" ) {
172+ // special case of multipoint where the WKT must be corrected
173+ // ES wants multipoint without extra parenthesis between points
174+ altWKT = wktWriter .write (geom ).replace ("((" , "(" ).replace ("))" , ")" ).replace ("), (" , ", " );
175+ }
176+ }
177+ geomWithWkt .geometry = geom ;
178+ geomWithWkt .wkt = altWKT ;
179+ return geomWithWkt ;
180+ }
181+
105182 @ SuppressWarnings ("unchecked" )
106183 @ Override
107184 public IngestDocument execute (IngestDocument ingestDocument ) throws IOException , ParseException {
@@ -114,47 +191,50 @@ public IngestDocument execute(IngestDocument ingestDocument) throws IOException,
114191 continue ;
115192 }
116193
194+ // From an input Object that represents a GeoJSON we need to:
195+ // - store a WKT version of the geometry, that will also be indexed as a geometry by ES
196+ // - make sure this WKT does not have duplicated points, since ES refuse them
197+ // - make sure this WKT geometry is equal (or close enough) to the geometry indexed by ES (why ?).
198+ // It means if ES splits the geometry around the dateline, we want to do the same
199+ // - compute its area
200+ // - compute its WKB representation
201+ // - compute its centroid
202+ //
203+ // We currently do this by:
204+ // 1/ creating a Spatial4J shape from an Object (thanks to legacygeo.ShapeBuilder)
205+ // 2/ converting this Spatial4J shape to a JTSGeometry
206+ // 3/ call area(), wkbwriter(), centroid() and wktwriter on the JTSGeometry
207+ //
208+ // 2/ could be avoided but the S4J WKTWriter is of poor quality (e.g. a multipoint can be represented as a geometrycollection of points)
209+ // 1/ could be avoided if we find how to create a JTSGeometry from an Object
210+
117211 ShapeBuilder <?,?, ?> shapeBuilder = getShapeBuilderFromObject (geoShapeObject );
118212
119213 // buildS4J() will try to clean up and fix the shape. If it fails, an exception is raised
120214 // Included fixes:
121- // - point deduplication
122215 // - dateline warping (enforce lon in [-180,180])
123- Shape shape = shapeBuilder .buildS4J ();
124-
125- Geometry geom = null ;
126- PrecisionModel precisionModel = new PrecisionModel (PrecisionModel .FLOATING );
127- GeometryFactory geomFactory = new GeometryFactory (precisionModel , 0 );
128- String altWKT = null ;
129- if (shape instanceof JtsPoint ) {
130- geom = ((JtsPoint )shape ).getGeom ();
131- }
132- else if (shape instanceof JtsGeometry ) {
133- geom = ((JtsGeometry ) shape ).getGeom ();
134- }
135- else if (shape instanceof XShapeCollection ) {
136- List <?> shapes = ((XShapeCollection ) shape ).getShapes ();
137- switch (shapeBuilder .type ()) {
138- case MULTIPOINT :
139- Point [] points = new Point [shapes .size ()];
140- for (int i = 0 ; i < shapes .size (); i ++) {
141- points [i ] = (Point )((JtsPoint )(shapes .get (i ))).getGeom ();
142- }
143- geom = geomFactory .createMultiPoint (points );
144- // ES wants multipoint without extra parenthesis between points
145- altWKT = new WKTWriter ().write (geom ).replace ("((" ,"(" ).replace ("))" ,")" ).replace ("), (" ,", " );
146- break ;
147- case MULTILINESTRING :
148- case MULTIPOLYGON :
149- case GEOMETRYCOLLECTION :
150- ArrayList <Geometry > geoms = new ArrayList <>(shapes .size ());
151- for (int i = 0 ; i < shapes .size (); i ++) {
152- geoms .add (((JtsGeometry )(shapes .get (i ))).getGeom ());
153- }
154- geom = geomFactory .buildGeometry (geoms );
155- break ;
216+ Shape shape = null ;
217+ try {
218+ try {
219+ shape = shapeBuilder .buildS4J ();
220+ } catch (InvalidShapeException e ) {
221+ // buildS4J does not always deduplicate points
222+ shapeBuilder = GeoUtils .removeDuplicateCoordinates (shapeBuilder );
223+ shape = shapeBuilder .buildS4J ();
156224 }
157225 }
226+ catch (Throwable e ) {
227+ // sometimes it is still not enough
228+ // e.g. when non-EPSG:4326 coordinates are input
229+ // buildS4J will try to warp date lines and may generate lots of small components
230+ // which could yield a GeometryCollection, which raises an AssertionError
231+ // So, we catch here both Error (AssertionError) and Exceptions
232+ throw new IllegalArgumentException ("Unable to parse shape [" + shapeBuilder .toWKT () + "]: " + e .getMessage ());
233+ }
234+
235+ GeometryWithWkt geometryWithWkt = s4jToJts (shape );
236+ Geometry geom = geometryWithWkt .geometry ;
237+ String altWKT = geometryWithWkt .wkt ;
158238
159239 if (geom == null ) {
160240 throw new IllegalArgumentException ("Unable to parse shape [" + shapeBuilder .toWKT () + "]" );
@@ -168,7 +248,7 @@ else if (shape instanceof XShapeCollection) {
168248
169249 if (fixedField != null ) {
170250 ingestDocument .setFieldValue (geoShapeField + "." + fixedField ,
171- altWKT != null ? altWKT : new WKTWriter () .write (geom ));
251+ altWKT != null ? altWKT : wktWriter .write (geom ));
172252 }
173253
174254 // compute and add extra geo sub-fields
0 commit comments