diff --git a/build.gradle b/build.gradle index e91cf5d..a2c1361 100644 --- a/build.gradle +++ b/build.gradle @@ -19,9 +19,9 @@ repositories { dependencies { implementation group: 'ch.interlis', name: 'iox-api', version: '1.0.4' - implementation group: 'ch.interlis', name: 'iox-ili', version: '1.21.8' - implementation group: 'ch.interlis', name: 'ili2c-tool', version: "5.2.3" - implementation group: 'ch.interlis', name: 'ili2c-core', version: "5.2.3" + implementation group: 'ch.interlis', name: 'iox-ili', version: '1.22.0' + implementation group: 'ch.interlis', name: 'ili2c-tool', version: "5.4.0" + implementation group: 'ch.interlis', name: 'ili2c-core', version: "5.4.0" implementation group: 'com.vividsolutions', name: 'jts-core', version: '1.14.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 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..10864f7 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/EvaluationHelper.java @@ -87,5 +87,4 @@ public static Double sum(Collection items, Function 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 argGeometries = actualArguments[0]; + + if (argGeometries.isUndefined()) { + return Value.createSkipEvaluation(); + } + + Collection surfaces = argGeometries.getComplexObjects(); + if (surfaces == null) { + return Value.createUndefined(); + } + + 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 Jts2iox.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..b2104f9 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 'Geometries' können nur Geometrien angegeben werden."; + !!@ fn.param = "Geometries: Geometrien, die zusammengefasst werden sollen"; + !!@ fn.return = "Zusammengefasste Flächen-Geometrie"; + !!@ fn.since = "2023-12-13"; + FUNCTION Union (Geometries: ANYSTRUCTURE): MULTIAREA; END GeoW_FunctionsExt. \ No newline at end of file diff --git a/src/test/data/GetInnerRingsCount/TestData.xtf b/src/test/data/GetInnerRingsCount/TestData.xtf index 06c6c5d..e927a4f 100644 --- a/src/test/data/GetInnerRingsCount/TestData.xtf +++ b/src/test/data/GetInnerRingsCount/TestData.xtf @@ -154,27 +154,22 @@ 2530000 1150000 - 467 2530010 1150000 - 468 2530010 1150010 - 469 2530000 1150010 - 468 2530000 1150000 - 467 @@ -183,27 +178,22 @@ 2530003 1150003 - 467 2530006 1150003 - 468 2530006 1150006 - 469 2530003 1150006 - 468 2530003 1150003 - 467 @@ -214,27 +204,22 @@ 2540000 1150000 - 467 2540010 1150000 - 468 2540010 1150010 - 469 2540000 1150010 - 468 2540000 1150000 - 467 diff --git a/src/test/data/Union/MandatoryConstraintMergedInnerRings.ili b/src/test/data/Union/MandatoryConstraintMergedInnerRings.ili new file mode 100644 index 0000000..efd9df3 --- /dev/null +++ b/src/test/data/Union/MandatoryConstraintMergedInnerRings.ili @@ -0,0 +1,31 @@ +INTERLIS 2.4; + +MODEL TestSuite + AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-13" = + IMPORTS GeoW_FunctionsExt; + + DOMAIN + Coord = COORD 0 .. 10 [INTERLIS.m], + 0 .. 10 [INTERLIS.m], + ROTATION 2 -> 1; + + Surface = SURFACE WITH (STRAIGHTS) VERTEX Coord WITHOUT OVERLAPS > 0.1; + + TOPIC FunctionTestTopic = + + CLASS BaseClass = + surfaceAttribute : BAG OF Surface; + + !! Surfaces evaluated separately + MANDATORY CONSTRAINT innerRings: + GeoW_FunctionsExt.GetInnerRingsCount(THIS, "surfaceAttribute") == 2; + + !! Surfaces evaluated as union (inner rings overlap in test data) + MANDATORY CONSTRAINT innerRingsUnion: + GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(THIS->surfaceAttribute), UNDEFINED) == 1; + + END BaseClass; + + END FunctionTestTopic; + +END TestSuite. diff --git a/src/test/data/Union/MandatoryConstraintThis.ili b/src/test/data/Union/MandatoryConstraintThis.ili new file mode 100644 index 0000000..e2828f4 --- /dev/null +++ b/src/test/data/Union/MandatoryConstraintThis.ili @@ -0,0 +1,30 @@ +INTERLIS 2.4; + +MODEL TestSuite + AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-13" = + IMPORTS GeoW_FunctionsExt; + + DOMAIN + !!@CRS=EPSG:2056 + CHKoord = COORD 2460000.000 .. 2870000.000 [INTERLIS.m], + 1045000.000 .. 1310000.000 [INTERLIS.m], + ROTATION 2 -> 1; + + Surface = SURFACE WITH (STRAIGHTS) VERTEX CHKoord WITHOUT OVERLAPS > 0.1; + + TOPIC FunctionTestTopic = + + CLASS BaseClass = + surfaceAttribute : BAG OF Surface; + + MANDATORY CONSTRAINT falseConstraint: + GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(THIS->surfaceAttribute), UNDEFINED) > 3; + + MANDATORY CONSTRAINT oneInnerRingConstraint: + GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(THIS->surfaceAttribute), UNDEFINED) == 1; + + END BaseClass; + + END FunctionTestTopic; + +END TestSuite. diff --git a/src/test/data/Union/TestData.xtf b/src/test/data/Union/TestData.xtf new file mode 100644 index 0000000..1c659aa --- /dev/null +++ b/src/test/data/Union/TestData.xtf @@ -0,0 +1,101 @@ + + + + + GeoW_FunctionsExt + TestSuite + + ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184 + + + + + + + + + + 2530000 + 1150000 + + + 2530010 + 1150000 + + + 2530010 + 1150010 + + + 2530000 + 1150010 + + + 2530000 + 1150000 + + + + + + + 2530003 + 1150003 + + + 2530006 + 1150003 + + + 2530006 + 1150006 + + + 2530003 + 1150006 + + + 2530003 + 1150003 + + + + + + + + + + + + + 2646349.508 + 1249022.931 + + + 2646353.508 + 1249022.926 + + + 2646353.503 + 1249018.926 + + + 2646349.503 + 1249018.931 + + + 2646349.508 + 1249022.931 + + + + + + + + + \ No newline at end of file diff --git a/src/test/data/Union/TestDataMergedInnerRings.xtf b/src/test/data/Union/TestDataMergedInnerRings.xtf new file mode 100644 index 0000000..f163531 --- /dev/null +++ b/src/test/data/Union/TestDataMergedInnerRings.xtf @@ -0,0 +1,123 @@ + + + + + GeoW_FunctionsExt + TestSuite + + ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184 + + + + + + + + + + 0 + 0 + + + 0 + 10 + + + 10 + 10 + + + 10 + 0 + + + 0 + 0 + + + + + + + 1 + 1 + + + 1 + 2 + + + 2 + 2 + + + 2 + 1 + + + 1 + 1 + + + + + + + + + + + 0 + 0 + + + 0 + 10 + + + 10 + 10 + + + 10 + 0 + + + 0 + 0 + + + + + + + 1 + 1 + + + 1 + 4 + + + 4 + 4 + + + 4 + 1 + + + 1 + 1 + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java new file mode 100644 index 0000000..1d5bec7 --- /dev/null +++ b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java @@ -0,0 +1,37 @@ +package ch.geowerkstatt.ilivalidator.extensions.functions; + +import ch.interlis.ili2c.Ili2cFailure; +import ch.interlis.iox.IoxException; +import com.vividsolutions.jts.util.Assert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public final class UnionIoxPluginTest { + private static final String TEST_DATA = "Union/TestData.xtf"; + private static final String TEST_DATA_MERGED_INNER_RINGS = "Union/TestDataMergedInnerRings.xtf"; + private ValidationTestHelper vh; + + @BeforeEach + void setUp() { + vh = new ValidationTestHelper(); + vh.addFunction(new GetInnerRingsCountIoxPlugin()); + vh.addFunction(new UnionIoxPlugin()); + } + + @Test + void mandatoryConstraintOnThis() throws Ili2cFailure, IoxException { + vh.runValidation(new String[]{TEST_DATA}, new String[]{"Union/MandatoryConstraintThis.ili"}); + Assert.equals(3, vh.getErrs().size()); + AssertionHelper.assertConstraintErrors(vh, 2, "falseConstraint"); + AssertionHelper.assertConstraintErrors(vh, 0, "0", "oneInnerRingConstraint"); + AssertionHelper.assertConstraintErrors(vh, 1, "1", "oneInnerRingConstraint"); + } + + @Test + void mandatoryConstraintMergedInnerRings() throws Ili2cFailure, IoxException { + vh.runValidation(new String[]{TEST_DATA_MERGED_INNER_RINGS}, new String[]{"Union/MandatoryConstraintMergedInnerRings.ili"}); + Assert.equals(0, vh.getErrs().size()); + AssertionHelper.assertNoConstraintError(vh, "innerRings"); + AssertionHelper.assertNoConstraintError(vh, "innerRingsUnion"); + } +}