From b432e6bb9502af2aff973c9bd31d030623a97066 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 13 Dec 2023 16:18:58 +0100 Subject: [PATCH] Add Union function --- .../functions/EvaluationHelper.java | 37 ++++++ .../extensions/functions/UnionIoxPlugin.java | 122 ++++++++++++++++++ src/model/GeoW_FunctionsExt.ili | 6 + 3 files changed, 165 insertions(+) create mode 100644 src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPlugin.java diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java index 30331d3..4f2cf2d 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java @@ -8,12 +8,18 @@ import ch.interlis.ili2c.metamodel.TransferDescription; import ch.interlis.ili2c.metamodel.Viewable; import ch.interlis.iom.IomObject; +import ch.interlis.iom_j.Iom_jObject; +import ch.interlis.iox_j.jts.Iox2jtsException; +import ch.interlis.iox_j.jts.Jtsext2iox; import ch.interlis.iox_j.validator.Validator; import ch.interlis.iox_j.validator.Value; +import com.vividsolutions.jts.geom.MultiPolygon; +import com.vividsolutions.jts.geom.Polygon; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.function.Function; public final class EvaluationHelper { @@ -88,4 +94,35 @@ public static Double sum(Collection items, Function polygons = splitMultiPolygon(multiPolygon); + + IomObject unionSurface = new Iom_jObject(Iom_jObject.MULTISURFACE, null); + for (Polygon polygon : polygons) { + IomObject multiSurfaceObject = Jtsext2iox.JTS2surface(polygon); + + int surfaceCount = multiSurfaceObject.getattrvaluecount(Iom_jObject.MULTISURFACE_SURFACE); + for (int i = 0; i < surfaceCount; i++) { + IomObject surfaceObject = multiSurfaceObject.getattrobj(Iom_jObject.MULTISURFACE_SURFACE, i); + unionSurface.addattrobj(Iom_jObject.MULTISURFACE_SURFACE, surfaceObject); + } + } + return unionSurface; + } + + /** + * Splits the {@link MultiPolygon} into a list of {@link Polygon}s. + */ + public static List splitMultiPolygon(MultiPolygon multiPolygon) { + int polygonCount = multiPolygon.getNumGeometries(); + List polygons = new ArrayList<>(polygonCount); + + for (int i = 0; i < polygonCount; i++) { + polygons.add((Polygon) multiPolygon.getGeometryN(i)); + } + return polygons; + } } diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPlugin.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPlugin.java new file mode 100644 index 0000000..8f7643b --- /dev/null +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPlugin.java @@ -0,0 +1,122 @@ +package ch.geowerkstatt.ilivalidator.extensions.functions; + +import ch.ehi.basics.types.OutParam; +import ch.interlis.ili2c.metamodel.PathEl; +import ch.interlis.ili2c.metamodel.Viewable; +import ch.interlis.iom.IomObject; +import ch.interlis.iox.IoxException; +import ch.interlis.iox_j.jts.Iox2jtsException; +import ch.interlis.iox_j.jts.Iox2jtsext; +import ch.interlis.iox_j.jts.Jtsext2iox; +import ch.interlis.iox_j.validator.Value; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.MultiPolygon; +import com.vividsolutions.jts.geom.Polygon; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public final class UnionIoxPlugin extends BaseInterlisFunction { + private static final Map UNION_SURFACE_CACHE = new HashMap<>(); + + @Override + public String getQualifiedIliName() { + return "GeoW_FunctionsExt.Union"; + } + + @Override + protected Value evaluateInternal(String validationKind, String usageScope, IomObject contextObject, Value[] actualArguments) { + Value argObjects = actualArguments[0]; // OBJECTS OF ANYCLASS + Value argPath = actualArguments[1]; // TEXT + + if (argObjects.isUndefined()) { + return Value.createSkipEvaluation(); + } + if (argObjects.getComplexObjects() == null) { + return Value.createUndefined(); + } + + Collection surfaces; + + if (argPath.isUndefined()) { + surfaces = argObjects.getComplexObjects(); + } else { + Viewable contextClass = EvaluationHelper.getContextClass(td, contextObject, argObjects); + if (contextClass == null) { + throw new IllegalStateException("unknown class in " + usageScope); + } + + PathEl[] attributePath = EvaluationHelper.getAttributePathEl(validator, contextClass, argPath); + surfaces = EvaluationHelper.evaluateAttributes(validator, argObjects, attributePath); + } + + UnionSurfaceKey key = new UnionSurfaceKey(surfaces); + IomObject unionSurface = UNION_SURFACE_CACHE.computeIfAbsent(key, this::union); + if (unionSurface == null) { + return Value.createUndefined(); + } + return new Value(Collections.singletonList(unionSurface)); + } + + private IomObject union(UnionSurfaceKey key) { + Collection surfaces = key.surfaces; + + MultiPolygon[] polygons = surfaces.stream() + .map(surface -> { + try { + return Iox2jtsext.multisurface2JTS(surface, 0, new OutParam<>(), logger, 0, "warning"); + } catch (IoxException e) { + logger.addEvent(logger.logErrorMsg("Could not convert surface to JTS")); + return null; + } + }) + .filter(Objects::nonNull) + .toArray(MultiPolygon[]::new); + + Geometry geometryCollection = new GeometryFactory().createGeometryCollection(polygons); + Geometry unionGeometry = geometryCollection.union(); + + try { + if (unionGeometry instanceof Polygon) { + return Jtsext2iox.JTS2surface((Polygon) unionGeometry); + } else if (unionGeometry instanceof MultiPolygon) { + return EvaluationHelper.jts2multiSurface((MultiPolygon) unionGeometry); + } else { + logger.addEvent(logger.logErrorMsg("Expected {0} or {1} but was {2}", Polygon.class.toString(), MultiPolygon.class.toString(), unionGeometry.getClass().toString())); + return null; + } + } catch (Iox2jtsException e) { + logger.addEvent(logger.logErrorMsg("Could not calculate {0}", this.getQualifiedIliName())); + return null; + } + } + + private static final class UnionSurfaceKey { + private final Collection surfaces; + + private UnionSurfaceKey(Collection surfaces) { + this.surfaces = surfaces; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UnionSurfaceKey)) { + return false; + } + UnionSurfaceKey that = (UnionSurfaceKey) o; + return Objects.equals(surfaces, that.surfaces); + } + + @Override + public int hashCode() { + return Objects.hash(surfaces); + } + } +} diff --git a/src/model/GeoW_FunctionsExt.ili b/src/model/GeoW_FunctionsExt.ili index dd90e8e..5cc1eee 100644 --- a/src/model/GeoW_FunctionsExt.ili +++ b/src/model/GeoW_FunctionsExt.ili @@ -33,4 +33,10 @@ MODEL GeoW_FunctionsExt !!@ fn.return = "Boolean"; !!@ fn.since = "2022-12-05"; FUNCTION IsInsideExternalDataset (DatasetName: TEXT; Objects: TEXT; TestObject: OBJECT OF ANYCLASS; TestObjectgeometry: TEXT): BOOLEAN; + + !!@ fn.description = "Fasst die Flächen-Geometrien aus der Eingabemenge zu einer Flächen-Geometrie zusammen. Für 'Objects' können Objekte oder Geometrien angegeben werden. Für 'AreaAttr' soll der Pfad zur Flächen-Geometrie in INTERLIS 2 Syntax angegeben werden. Falls 'Objects' bereits die Geometrien enthält, soll für 'AreaAttr' 'UNDEFINED' übergeben werden."; + !!@ fn.param = "Objects: Ausgangsobjekte oder Geometrien. AreaAttr: Pfad zum Geometrieattribut oder UNDEFINED"; + !!@ fn.return = "Zusammengefasste Flächen-Geometrie"; + !!@ fn.since = "2023-12-13"; + FUNCTION Union (Objects: OBJECTS OF ANYCLASS; AreaAttr: TEXT): OBJECT OF ANYCLASS; END GeoW_FunctionsExt. \ No newline at end of file