Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Geometry rather than MultiPolygon for indexing records #5615

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import java.util.List;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Polygon;

public interface ErrorHandler {
void handleParseException(Exception e, String gml);

void handleBuildException(Exception e, List<Polygon> polygons);
void handleBuildException(Exception e, List<Geometry> geometries);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.xsd.Parser;
import org.jdom.Element;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.util.GeometryTransformer;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.xml.sax.SAXException;
Expand All @@ -28,16 +26,20 @@
import java.util.List;
import java.util.logging.Level;

/**
* Utility class to help with geometry and gml.
*/
public class GeomUtils {
public static MultiPolygon getSpatialExtent(Path schemaDir, Element metadata, ErrorHandler errorHandler) throws Exception {

public static Geometry getSpatialExtent(Path schemaDir, Element metadata, ErrorHandler errorHandler) throws Exception {
org.geotools.util.logging.Logging.getLogger("org.geotools.xml")
.setLevel(Level.SEVERE);
Path sSheet = schemaDir.resolve("extract-gml.xsl").toAbsolutePath();
Element transform = Xml.transform(metadata, sSheet);
if (transform.getChildren().size() == 0) {
return null;
}
List<Polygon> allPolygons = new ArrayList<Polygon>();
List<Geometry> allGeometry = new ArrayList<Geometry>();
for (Element geom : (List<Element>) transform.getChildren()) {
Parser parser = GMLParsers.create(geom);
String srs = geom.getAttributeValue("srsName");
Expand All @@ -46,91 +48,265 @@ public static MultiPolygon getSpatialExtent(Path schemaDir, Element metadata, Er

try {
if (srs != null && !(srs.equals(""))) sourceCRS = CRS.decode(srs);
MultiPolygon jts = parseGml(parser, gml);
Geometry geometry = parseGml(parser, gml);

// if we have an srs and its not WGS84 then transform to WGS84
if (!CRS.equalsIgnoreMetadata(sourceCRS, DefaultGeographicCRS.WGS84)) {
MathTransform tform = CRS.findMathTransform(sourceCRS, DefaultGeographicCRS.WGS84);
jts = (MultiPolygon) JTS.transform(jts, tform);
}

for (int i = 0; i < jts.getNumGeometries(); i++) {
allPolygons.add((Polygon) jts.getGeometryN(i));
geometry = JTS.transform(geometry, tform);
}
allGeometry.add(geometry);
} catch (Exception e) {
errorHandler.handleParseException(e, gml);
// continue
}
}

if (allPolygons.isEmpty()) {
try {
return toGeometry(allGeometry);
} catch (Exception e) {
errorHandler.handleBuildException(e, allGeometry);
return null; // continue
}
}

/**
* Process list into a single geometry, choosing the most appropriate geometry collection.
*
* <ul>
* <li>f the list is empty geometry is {@code null}</li>
* <li>If list contains a single geometry it is returned</li>
* <li>If the list contains all Points, a MultiPoint will be created</li>
* <li>If the list contains all LineStrings a MultiLineString will be created</li>
* <li>If the list contains all Polygons a MultiPolygon is created</li>
* <li>If the list contains mixed contents a GeometryCollection is created</li>
* </ul>
*
* @param geoms list of geometry to process
* @return geometry, or geometry collection as required, or null if list is empty
*/
protected static Geometry toGeometry(List<Geometry> geoms){
if( geoms == null || geoms.isEmpty()){
return null;
} else {
try {
Polygon[] array = new Polygon[allPolygons.size()];
GeometryFactory geometryFactory = allPolygons.get(0).getFactory();
return geometryFactory.createMultiPolygon(allPolygons.toArray(array));
}
if( geoms.size()==1 ){
return geoms.get(0);
}

GeometryFactory factory = geoms.get(0).getFactory();

} catch (Exception e) {
errorHandler.handleBuildException(e, allPolygons);
// continue
return null;
// determine geometry collection to create
int dimension = -2; // mixed content geometry collection
for( Geometry geom : geoms){
if( geom instanceof GeometryCollection){
dimension = -1;
break; // mixed content geometry collection
}

if( dimension == -2 ) {
dimension = geom.getDimension();
}
else if (dimension != geom.getDimension()){
dimension = -1;
break; // mixed content geometry collection
}
}

// process list into geometry collection
switch (dimension){
case 0:
return factory.createMultiPoint( geoms.toArray(new Point[geoms.size()]));
case 1:
return factory.createMultiLineString( geoms.toArray(new LineString[geoms.size()]) );
case 2:
return factory.createMultiPolygon( geoms.toArray(new Polygon[geoms.size()]) );
default:
return factory.createGeometryCollection( geoms.toArray(new Geometry[geoms.size()]) );
}
}

public static MultiPolygon parseGml(Parser parser, String gml) throws IOException, SAXException,
/**
* Parse GML into a geometry: polygons, linestring, point or appropriate geometry collection as required.
*
* The resulting geometry may be clipped or split to accommodate spatial reference system bounds. Points are not buffered
* in any way (so the bounds of a point will have width and height zero).
*
* @param parser
* @param gml
* @return geometry, or null if not provided.
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public static Geometry parseGml(Parser parser, String gml) throws IOException, SAXException,
ParserConfigurationException {
Object value = parser.parse(new StringReader(gml));
if (value instanceof HashMap) {

if( value == null ){
return null;
}
else if (value instanceof HashMap) {
@SuppressWarnings("rawtypes")
HashMap map = (HashMap) value;
List<MultiPolygon> geoms = new ArrayList<MultiPolygon>();
List<Geometry> geoms = new ArrayList<Geometry>();
for (Object entry : map.values()) {
addToList(geoms, entry);
}
if (geoms.isEmpty()) {
return null;
} else if (geoms.size() > 1) {
GeometryFactory factory = geoms.get(0).getFactory();
return factory.createMultiPolygon(geoms.toArray(new Polygon[0]));
} else {
return toMultiPolygon(geoms.get(0));
}

} else if (value == null) {
return toGeometry( geoms );
} else if (value instanceof Geometry ){
return (Geometry) value;
}
else {
return null;
} else {
return toMultiPolygon((Geometry) value);
}
}

/**
* Produces a multipolygon that covers the provided geometry.
*
* @param geometry
* @return covering multipolygon
*/
public static MultiPolygon toMultiPolygon(Geometry geometry) {
if (geometry instanceof Polygon) {
Polygon polygon = (Polygon) geometry;

return geometry.getFactory().createMultiPolygon(
new Polygon[]{polygon});
} else if (geometry instanceof MultiPolygon) {
if (geometry == null) return null;
if (geometry instanceof MultiPolygon) {
return (MultiPolygon) geometry;
}
String message = geometry.getClass() + " cannot be converted to a polygon. Check metadata";
Log.error(Geonet.INDEX_ENGINE, message);
throw new IllegalArgumentException(message);
final double DISTANCE = 0.01;
GeometryTransformer transform = new CoveredByTransformer(DISTANCE);

Geometry transformed = transform.transform(geometry);
if( transformed == null ){
String message = geometry.getClass() + " cannot be converted to a polygon. Check metadata";
Log.error(Geonet.INDEX_ENGINE, message);
throw new IllegalArgumentException(message);
}
transformed.setSRID(geometry.getSRID());
transformed.setUserData(geometry.getUserData());

if( transformed instanceof MultiPolygon) {
return (MultiPolygon) transformed;
}
else {
return null;
}
}

public static void addToList(List<MultiPolygon> geoms, Object entry) {
if (entry instanceof Polygon) {
geoms.add(toMultiPolygon((Polygon) entry));
} else if (entry instanceof MultiPolygon) {
geoms.add((MultiPolygon) entry);
/**
* Process entry and add any geometries to list.
*
* The contents of any GeometryCollection (such as MultiPolygon) are added one-by-one
* by one to the list.
*/
protected static final void addToList(List<Geometry> geoms, Object entry) {
if (entry instanceof Geometry) {
Geometry geom = (Geometry) entry;
if( geom instanceof GeometryCollection){
GeometryCollection collection = (GeometryCollection) geom;
for( int i=0; i<collection.getNumGeometries();i++){
addToList( geoms, collection.getGeometryN(i));
}
}
else {
geoms.add(geom);
}
} else if (entry instanceof Collection) {
@SuppressWarnings("rawtypes")
Collection collection = (Collection) entry;
for (Object object : collection) {
geoms.add(toMultiPolygon((Polygon) object));
addToList(geoms,object);
}
}
}

/**
* GeometryTransformer using buffer and bbox to return a MultiPolygon
* covering the provided geometry.
*
* The generated geometry satisfies a {@link Geometry#coveredBy(Geometry)} relationship,
* and can acts as a "bounding polygon" to index the provided geometry.
*/
private static class CoveredByTransformer extends GeometryTransformer {

private final double DISTANCE;

public CoveredByTransformer(double DISTANCE) {
this.DISTANCE = DISTANCE;
}

protected Geometry checkChildPolygon(Polygon polygon, Geometry parent ){
if( parent instanceof GeometryCollection){
// MultiLineString, MultiPoint, MultiPolygon or GeometryCollection ..
return polygon;
}
else {
return factory.createMultiPolygon(new Polygon[]{polygon});
}
}

@Override
protected Geometry transformLinearRing(LinearRing linearRing, Geometry parent) {
if(parent instanceof Polygon) {
// used as exterior or interior ring forming a polygon
return super.transformLinearRing(linearRing, parent);
}

Polygon polygon = factory.createPolygon(linearRing);
Geometry bounds = polygon.buffer(DISTANCE);

if( bounds instanceof Polygon) {
return checkChildPolygon((Polygon) polygon, parent);
}
return null;
}

@Override
protected Geometry transformLineString(LineString lineString, Geometry parent) {
Geometry bounds = lineString.buffer(DISTANCE);
if( bounds instanceof Polygon) {
return checkChildPolygon((Polygon) bounds, parent);
}
return null;
}

@Override
protected Geometry transformPolygon(Polygon polygon, Geometry parent) {
return checkChildPolygon( polygon, parent );
}

protected Geometry transformPoint(Point point, Geometry parent) {
if (point.isEmpty()){
return null; // skip
}
Geometry bounds = point.buffer(DISTANCE);

if( bounds instanceof Polygon) {
return checkChildPolygon( (Polygon) bounds, parent );
}
return null;
}

protected Geometry transformGeometryCollection(GeometryCollection geom, Geometry parent) {
List<Polygon> transGeomList = new ArrayList<>();
for (int i = 0; i < geom.getNumGeometries(); i++) {
Geometry transformGeom = transform(geom.getGeometryN(i));
if (transformGeom == null) continue;
if (transformGeom.isEmpty()) continue;

if( transformGeom instanceof MultiPolygon) {
MultiPolygon transformedMultiPolygon = (MultiPolygon) transformGeom;
for (int j = 0; j < transformedMultiPolygon.getNumGeometries(); j++) {
Polygon polygon = (Polygon) transformedMultiPolygon.getGeometryN(j);
if (polygon == null) continue;
if (polygon.isEmpty()) continue;
transGeomList.add(polygon);
}
}
else if (transformGeom instanceof Polygon){
transGeomList.add((Polygon)transformGeom);
}
}
return factory.createMultiPolygon(GeometryFactory.toPolygonArray(transGeomList));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ private void loadSpatialExtent(List<Region> regions, Id id) throws Exception {
Element metadata = findMetadata(id, false);
if (metadata != null) {
Path schemaDir = getSchemaDir(id);
MultiPolygon geom = GeomUtils.getSpatialExtent(schemaDir, metadata,
Geometry geom = GeomUtils.getSpatialExtent(schemaDir, metadata,
new SpatialExtentErrorHandler());
MetadataRegion region = new MetadataRegion(id, null, geom);
regions.add(region);
Expand Down Expand Up @@ -370,8 +370,8 @@ public void handleParseException(Exception e, String gml) {
}

@Override
public void handleBuildException(Exception e, List<Polygon> polygons) {
Log.error(Geonet.SPATIAL, "Failed to create a MultiPolygon from: " + polygons, e);
public void handleBuildException(Exception e, List<Geometry> geoms) {
Log.error(Geonet.SPATIAL, "Failed to create a geometry from: " + geoms, e);
}
}
}
Loading