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

Add IsInside function #39

Merged
merged 3 commits into from
Jan 10, 2024
Merged
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 @@ -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<ValidAreaKey, Geometry> 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<IomObject> testObjects, String geometryAttribute) {
protected final Value isInsideValidArea(String usageScope, Collection<IomObject> testObjects, String geometryAttribute, Supplier<Geometry> validAreaSupplier) {
try {
Geometry validArea = VALID_AREA_CACHE.computeIfAbsent(key, this::getValidArea);
Geometry validArea = validAreaSupplier.get();

if (validArea == null) {
return Value.createUndefined();
Expand Down Expand Up @@ -75,6 +64,10 @@ protected final <T> T logExceptionAsWarning(Callable<T> 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}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public final class IsInsideExternalDatasetIoxPlugin extends BaseIsInsideFunction
private static File dataDirectory;
private static Map<String, Set<URL>> modelFilesFromDataDirectory;
private static Map<String, Set<URL>> modelFilesFromJars;
private static final Map<ValidAreaKey, Geometry> VALID_AREA_CACHE = new HashMap<>();
private static final String QUALIFIED_ILI_NAME = "GeoW_FunctionsExt.IsInsideExternalDataset";

@Override
Expand All @@ -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('.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidAreaKey, Geometry> VALID_AREA_CACHE = new HashMap<>();
private static final String QUALIFIED_ILI_NAME = "GeoW_FunctionsExt.IsInsideExternalDatasetResource";

@Override
Expand All @@ -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('.');
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IomObject, Geometry> 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<IomObject> 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);
}
}
}
6 changes: 6 additions & 0 deletions src/model/GeoW_FunctionsExt.ili
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
33 changes: 33 additions & 0 deletions src/test/data/IsInside/MandatoryConstraintThis.ili
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
INTERLIS 2.4;

MODEL TestSuite
AT "mailto:[email protected]" 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.
102 changes: 102 additions & 0 deletions src/test/data/IsInside/TestData.xtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ili:transfer xmlns:ili="http://www.interlis.ch/xtf/2.4/INTERLIS" xmlns:geom="http://www.interlis.ch/geometry/1.0"
xmlns:TestSuite="http://www.interlis.ch/xtf/2.4/TestSuite">
<ili:headersection>
<ili:models>
<ili:model>GeoW_FunctionsExt</ili:model>
<ili:model>TestSuite</ili:model>
</ili:models>
<ili:sender>ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184</ili:sender>
</ili:headersection>
<ili:datasection>
domi-b marked this conversation as resolved.
Show resolved Hide resolved
<TestSuite:FunctionTestTopic ili:bid="TestSuite.FunctionTestTopic">
<TestSuite:InvalidConstraints ili:tid="1">
<TestSuite:geometryAttribute>
<geom:coord>
<geom:c1>2533306.953</geom:c1>
<geom:c2>1184710.792</geom:c2>
</geom:coord>
</TestSuite:geometryAttribute>
<!-- Kein area-Attribut, um fehlende Fläche zu testen -->
</TestSuite:InvalidConstraints>
<TestSuite:IsInsideKantonsgrenze ili:tid="2"> <!-- Erwartet: 2D-Punkt: liegt im Kt. SO -->
<TestSuite:expected>true</TestSuite:expected>
<TestSuite:testAttributeIsInsideKantonsgrenze>
<geom:coord>
<geom:c1>2620570.500</geom:c1>
<geom:c2>1237151.400</geom:c2>
</geom:coord>
</TestSuite:testAttributeIsInsideKantonsgrenze>
<TestSuite:testAttributeKantonsgrenze>
<geom:multisurface>
<geom:surface>
<geom:exterior>
<geom:polyline>
<geom:coord>
<geom:c1>2591994.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2645900.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2645900.000</geom:c1>
<geom:c2>1211927.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2591994.000</geom:c1>
<geom:c2>1211927.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2591994.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
</geom:polyline>
</geom:exterior>
</geom:surface>
</geom:multisurface>
</TestSuite:testAttributeKantonsgrenze>
</TestSuite:IsInsideKantonsgrenze>
<TestSuite:IsInsideKantonsgrenze ili:tid="3"> <!-- Erwartet: 2D-Punkt: liegt ausserhalb, c1 der Fläche verschoben -->
<TestSuite:expected>false</TestSuite:expected>
<TestSuite:testAttributeIsInsideKantonsgrenze>
<geom:coord>
<geom:c1>2620570.500</geom:c1>
<geom:c2>1237151.400</geom:c2>
</geom:coord>
</TestSuite:testAttributeIsInsideKantonsgrenze>
<TestSuite:testAttributeKantonsgrenze>
<geom:multisurface>
<geom:surface>
<geom:exterior>
<geom:polyline>
<geom:coord>
<geom:c1>2691994.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2745900.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2745900.000</geom:c1>
<geom:c2>1211927.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2691994.000</geom:c1>
<geom:c2>1211927.000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2691994.000</geom:c1>
<geom:c2>1262353.000</geom:c2>
</geom:coord>
</geom:polyline>
</geom:exterior>
</geom:surface>
</geom:multisurface>
</TestSuite:testAttributeKantonsgrenze>
</TestSuite:IsInsideKantonsgrenze>
</TestSuite:FunctionTestTopic>
</ili:datasection>
</ili:transfer>
Original file line number Diff line number Diff line change
@@ -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.");
}
}
Loading