diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseIsInsideFunction.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseIsInsideFunction.java index 82c9984..023f878 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseIsInsideFunction.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/BaseIsInsideFunction.java @@ -17,28 +17,17 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class BaseIsInsideFunction extends BaseInterlisFunction { - private static final Map VALID_AREA_CACHE = new HashMap<>(); - - /** - * Retrieve the geometries from referenced data according to the specified key. - * - * @param key The key that specifies which geometries to retrieve. - * @return The union of all found geometries. - */ - protected abstract Geometry getValidArea(ValidAreaKey key); - - protected final Value isInsideValidArea(String usageScope, ValidAreaKey key, Collection testObjects, String geometryAttribute) { + protected final Value isInsideValidArea(String usageScope, Collection testObjects, String geometryAttribute, Supplier validAreaSupplier) { try { - Geometry validArea = VALID_AREA_CACHE.computeIfAbsent(key, this::getValidArea); + Geometry validArea = validAreaSupplier.get(); if (validArea == null) { return Value.createUndefined(); @@ -75,6 +64,10 @@ protected final T logExceptionAsWarning(Callable supplier) { } } + protected final void writeLogErrorMessage(String usageScope, String message) { + logger.addEvent(logger.logErrorMsg("{0}: Unable to evaluate {1}. {2}", usageScope, this.getQualifiedIliName(), message)); + } + /** * Convert the geometry {@link IomObject} to a JTS {@link Geometry}. * diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetIoxPlugin.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetIoxPlugin.java index a5f0c7c..627410d 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetIoxPlugin.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetIoxPlugin.java @@ -26,6 +26,7 @@ public final class IsInsideExternalDatasetIoxPlugin extends BaseIsInsideFunction private static File dataDirectory; private static Map> modelFilesFromDataDirectory; private static Map> modelFilesFromJars; + private static final Map VALID_AREA_CACHE = new HashMap<>(); private static final String QUALIFIED_ILI_NAME = "GeoW_FunctionsExt.IsInsideExternalDataset"; @Override @@ -49,11 +50,16 @@ protected Value evaluateInternal(String validationKind, String usageScope, IomOb ValidAreaKey key = new ValidAreaKey(null, datasetName, transferIds); String testObjectGeometryAttribute = argTestObjectgeometry.getValue(); - return isInsideValidArea(usageScope, key, argTestObject.getComplexObjects(), testObjectGeometryAttribute); + return isInsideValidArea(usageScope, argTestObject.getComplexObjects(), testObjectGeometryAttribute, () -> VALID_AREA_CACHE.computeIfAbsent(key, this::getValidArea)); } - @Override - protected Geometry getValidArea(ValidAreaKey key) { + /** + * Retrieve the geometries from referenced data according to the specified key. + * + * @param key The key that specifies which geometries to retrieve. + * @return The union of all found geometries. + */ + private Geometry getValidArea(ValidAreaKey key) { String datasetName = key.getDatasetName(); int firstPoint = datasetName.indexOf('.'); int lastPoint = datasetName.lastIndexOf('.'); diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetResourceIoxPlugin.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetResourceIoxPlugin.java index be000a7..386fb96 100644 --- a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetResourceIoxPlugin.java +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideExternalDatasetResourceIoxPlugin.java @@ -9,9 +9,12 @@ import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; +import java.util.HashMap; import java.util.List; +import java.util.Map; public final class IsInsideExternalDatasetResourceIoxPlugin extends BaseIsInsideFunction { + private static final Map VALID_AREA_CACHE = new HashMap<>(); private static final String QUALIFIED_ILI_NAME = "GeoW_FunctionsExt.IsInsideExternalDatasetResource"; @Override @@ -37,11 +40,16 @@ protected Value evaluateInternal(String validationKind, String usageScope, IomOb ValidAreaKey key = new ValidAreaKey(transferFile, datasetName, transferIds); String testObjectGeometryAttribute = argTestObjectgeometry.getValue(); - return isInsideValidArea(usageScope, key, argTestObject.getComplexObjects(), testObjectGeometryAttribute); + return isInsideValidArea(usageScope, argTestObject.getComplexObjects(), testObjectGeometryAttribute, () -> VALID_AREA_CACHE.computeIfAbsent(key, this::getValidArea)); } - @Override - protected Geometry getValidArea(ValidAreaKey key) { + /** + * Retrieve the geometries from referenced data according to the specified key. + * + * @param key The key that specifies which geometries to retrieve. + * @return The union of all found geometries. + */ + private Geometry getValidArea(ValidAreaKey key) { String transferFile = key.getDataSource(); String datasetName = key.getDatasetName(); int lastPoint = datasetName.lastIndexOf('.'); diff --git a/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPlugin.java b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPlugin.java new file mode 100644 index 0000000..6cc71a3 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPlugin.java @@ -0,0 +1,56 @@ +package ch.geowerkstatt.ilivalidator.extensions.functions; + +import ch.ehi.basics.types.OutParam; +import ch.interlis.iom.IomObject; +import ch.interlis.iox.IoxException; +import ch.interlis.iox_j.jts.Iox2jtsext; +import ch.interlis.iox_j.validator.Value; +import com.vividsolutions.jts.geom.Geometry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public final class IsInsideIoxPlugin extends BaseIsInsideFunction { + private static final Map VALID_AREA_CACHE = new HashMap<>(); + private static final String QUALIFIED_ILI_NAME = "GeoW_FunctionsExt.IsInside"; + + @Override + public String getQualifiedIliName() { + return QUALIFIED_ILI_NAME; + } + + @Override + protected Value evaluateInternal(String validationKind, String usageScope, IomObject mainObj, Value[] arguments) { + Value argReferenceGeometry = arguments[0]; // MULTIAREA + Value argTestObject = arguments[1]; // OBJECT OF ANYCLASS + Value argTestObjectgeometry = arguments[2]; // TEXT + + if (argTestObject.isUndefined() || argTestObjectgeometry.isUndefined()) { + return Value.createSkipEvaluation(); + } + if (argReferenceGeometry.isUndefined()) { + writeLogErrorMessage(usageScope, "Missing reference geometry."); + return Value.createUndefined(); + } + + Collection referenceGeometryObjects = argReferenceGeometry.getComplexObjects(); + if (referenceGeometryObjects.size() != 1) { + writeLogErrorMessage(usageScope, "Expected exactly one reference geometry."); + return Value.createUndefined(); + } + + IomObject referenceGeometry = referenceGeometryObjects.iterator().next(); + String testObjectGeometryAttribute = argTestObjectgeometry.getValue(); + + return isInsideValidArea(usageScope, argTestObject.getComplexObjects(), testObjectGeometryAttribute, () -> VALID_AREA_CACHE.computeIfAbsent(referenceGeometry, this::getValidArea)); + } + + private Geometry getValidArea(IomObject referenceGeometry) { + try { + return Iox2jtsext.multisurface2JTS(referenceGeometry, 0.0, new OutParam<>(), logger, 0.0, "warning"); + } catch (IoxException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/model/GeoW_FunctionsExt.ili b/src/model/GeoW_FunctionsExt.ili index be366e0..f09a34e 100644 --- a/src/model/GeoW_FunctionsExt.ili +++ b/src/model/GeoW_FunctionsExt.ili @@ -40,6 +40,12 @@ MODEL GeoW_FunctionsExt !!@ fn.since = "2023-12-20"; FUNCTION IsInsideExternalDatasetResource (TransferFile: TEXT; DatasetName: TEXT; Objects: TEXT; TestObject: OBJECT OF ANYCLASS; TestObjectgeometry: TEXT): BOOLEAN; + !!@ fn.description = "Prüft, ob ein Objekt innerhalb der Geometrie eines anderen Objektes liegt."; + !!@ fn.param = "ReferenceGeometry: Referenzgeometrie, innerhalb welcher das TestObject liegen muss. TestObject: Objekt, welches zu prüfen ist. TestObjectgeometry: Geometriefeld, bezogen auf das unter Testobject übergebene Objekt"; + !!@ fn.return = "Boolean"; + !!@ fn.since = "2023-12-21"; + FUNCTION IsInside (ReferenceGeometry: MULTIAREA; 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"; diff --git a/src/test/data/IsInside/MandatoryConstraintThis.ili b/src/test/data/IsInside/MandatoryConstraintThis.ili new file mode 100644 index 0000000..6d14622 --- /dev/null +++ b/src/test/data/IsInside/MandatoryConstraintThis.ili @@ -0,0 +1,33 @@ +INTERLIS 2.4; + +MODEL TestSuite + AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-21" = + IMPORTS GeoW_FunctionsExt; + + DOMAIN + !!@CRS=EPSG:2056 + CHKoord = COORD 2460000.000 .. 2870000.000 [INTERLIS.m], + 1045000.000 .. 1310000.000 [INTERLIS.m], + ROTATION 2 -> 1; + CHMultiArea = MULTIAREA WITH (STRAIGHTS) VERTEX CHKoord WITHOUT OVERLAPS > 0.001; + + TOPIC FunctionTestTopic = + CLASS ConstraintTestClass (ABSTRACT) = + expected : MANDATORY BOOLEAN; + END ConstraintTestClass; + + CLASS IsInsideKantonsgrenze EXTENDS ConstraintTestClass = + testAttributeIsInsideKantonsgrenze : CHKoord; + testAttributeKantonsgrenze : CHMultiArea; + MANDATORY CONSTRAINT IsInsideKantonsgrenze: GeoW_FunctionsExt.IsInside(THIS->testAttributeKantonsgrenze, THIS, "testAttributeIsInsideKantonsgrenze") == expected; + END IsInsideKantonsgrenze; + + CLASS InvalidConstraints = + geometryAttribute : CHKoord; + area : CHMultiArea; + MANDATORY CONSTRAINT IsInsideMissingArea: GeoW_FunctionsExt.IsInside(THIS->area, THIS, "geometryAttribute"); + END InvalidConstraints; + + END FunctionTestTopic; + +END TestSuite. diff --git a/src/test/data/IsInside/TestData.xtf b/src/test/data/IsInside/TestData.xtf new file mode 100644 index 0000000..b99a21b --- /dev/null +++ b/src/test/data/IsInside/TestData.xtf @@ -0,0 +1,102 @@ + + + + + GeoW_FunctionsExt + TestSuite + + ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184 + + + + + + + 2533306.953 + 1184710.792 + + + + + + true + + + 2620570.500 + 1237151.400 + + + + + + + + + 2591994.000 + 1262353.000 + + + 2645900.000 + 1262353.000 + + + 2645900.000 + 1211927.000 + + + 2591994.000 + 1211927.000 + + + 2591994.000 + 1262353.000 + + + + + + + + + false + + + 2620570.500 + 1237151.400 + + + + + + + + + 2691994.000 + 1262353.000 + + + 2745900.000 + 1262353.000 + + + 2745900.000 + 1211927.000 + + + 2691994.000 + 1211927.000 + + + 2691994.000 + 1262353.000 + + + + + + + + + + diff --git a/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPluginTest.java b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPluginTest.java new file mode 100644 index 0000000..d7dd1b9 --- /dev/null +++ b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/IsInsideIoxPluginTest.java @@ -0,0 +1,27 @@ +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 IsInsideIoxPluginTest { + private static final String TEST_DATA = "IsInside/TestData.xtf"; + private ValidationTestHelper vh; + + @BeforeEach + void setUp() { + vh = new ValidationTestHelper(); + vh.addFunction(new IsInsideIoxPlugin()); + } + + @Test + void isInside() throws Ili2cFailure, IoxException { + vh.runValidation(new String[]{TEST_DATA}, new String[]{"IsInside/MandatoryConstraintThis.ili"}); + + Assert.equals(2, vh.getErrs().size()); + AssertionHelper.assertLogEventsContainMessage(vh.getErrs(), "TestSuite.FunctionTestTopic.InvalidConstraints.IsInsideMissingArea: Unable to evaluate GeoW_FunctionsExt.IsInside. Missing reference geometry."); + AssertionHelper.assertLogEventsContainMessage(vh.getErrs(), "Mandatory Constraint TestSuite.FunctionTestTopic.InvalidConstraints.IsInsideMissingArea is not true."); + } +}