Skip to content

Commit

Permalink
Extend IsInsideAreaByCode for numeric codes (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b authored Feb 26, 2024
2 parents e66f460 + 27c7928 commit 0f3b8eb
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ch.ehi.basics.types.OutParam;
import ch.interlis.ili2c.metamodel.EnumerationType;
import ch.interlis.ili2c.metamodel.NumericType;
import ch.interlis.ili2c.metamodel.PathEl;
import ch.interlis.ili2c.metamodel.Type;
import ch.interlis.ili2c.metamodel.Viewable;
Expand All @@ -20,21 +21,21 @@
import java.util.Objects;
import java.util.stream.Collectors;

public final class IsInsideAreaByCodeEnumIoxPlugin extends BaseInterlisFunction {
public final class IsInsideAreaByCodeIoxPlugin extends BaseInterlisFunction {
private static final Map<InsideAreaKey, Value> OBJECTS_CACHE = new HashMap<>();

@Override
public String getQualifiedIliName() {
return "NGK_SO_FunctionsExt.IsInsideAreaByCodeEnum";
return "NGK_SO_FunctionsExt.IsInsideAreaByCode";
}

@Override
protected Value evaluateInternal(String validationKind, String usageScope, IomObject contextObject, Value[] arguments) {
Value argObjects = arguments[0];
Value argGeometryPath = arguments[1];
Value argEnumPath = arguments[2];
Value argCodePath = arguments[2];

if (argObjects.isUndefined() || argGeometryPath.isUndefined() || argEnumPath.isUndefined()) {
if (argObjects.isUndefined() || argGeometryPath.isUndefined() || argCodePath.isUndefined()) {
return Value.createSkipEvaluation();
}

Expand All @@ -45,43 +46,48 @@ protected Value evaluateInternal(String validationKind, String usageScope, IomOb

List<String> objectIds = objects.stream().map(IomObject::getobjectoid).collect(Collectors.toList());
String geometryAttribute = argGeometryPath.getValue();
String enumAttribute = argEnumPath.getValue();
String codeAttribute = argCodePath.getValue();

InsideAreaKey key = new InsideAreaKey(objectIds, geometryAttribute, enumAttribute);
InsideAreaKey key = new InsideAreaKey(objectIds, geometryAttribute, codeAttribute);
return OBJECTS_CACHE.computeIfAbsent(key, k -> {
Viewable contextClass = EvaluationHelper.getContextClass(td, contextObject, argObjects);
if (contextClass == null) {
throw new IllegalStateException("unknown class in " + usageScope);
}

PathEl[] geometryPath = EvaluationHelper.getAttributePathEl(validator, contextClass, argGeometryPath);
PathEl[] enumPath = EvaluationHelper.getAttributePathEl(validator, contextClass, argEnumPath);
PathEl[] codePath = EvaluationHelper.getAttributePathEl(validator, contextClass, argCodePath);

return isInsideArea(usageScope, objects, geometryPath, enumPath);
return isInsideArea(usageScope, objects, geometryPath, codePath);
});
}

private Value isInsideArea(String usageScope, Collection<IomObject> objects, PathEl[] geometryPath, PathEl[] enumPath) {
Map<ValueKey, Geometry> geometriesByEnumValue = objects.stream()
private Value isInsideArea(String usageScope, Collection<IomObject> objects, PathEl[] geometryPath, PathEl[] codePath) {
Map<ValueKey, Geometry> geometriesByCodeValue = objects.stream()
.collect(Collectors.toMap(
o -> getEnumValue(o, enumPath),
o -> getCodeValue(o, codePath),
o -> getGeometryValue(o, geometryPath),
Geometry::union
));

ValueKey firstKey = geometriesByEnumValue.keySet().iterator().next();
List<Geometry> sortedGeometries;
ValueKey firstKey = geometriesByCodeValue.keySet().iterator().next();
Type keyType = firstKey.getType();
if (!(keyType instanceof EnumerationType)) {
logger.addEvent(logger.logErrorMsg("{0}: Enumeration type expected.", usageScope));
return Value.createSkipEvaluation();
}
EnumerationType enumType = (EnumerationType) keyType;
if (!enumType.isOrdered()) {
logger.addEvent(logger.logErrorMsg("{0}: Enumeration type must be ordered.", usageScope));

if (keyType instanceof EnumerationType) {
EnumerationType enumType = (EnumerationType) keyType;
if (!enumType.isOrdered()) {
logger.addEvent(logger.logErrorMsg("{0}: Enumeration type must be ordered.", usageScope));
return Value.createSkipEvaluation();
}
sortedGeometries = sortByEnumValues(geometriesByCodeValue, enumType);
} else if (keyType instanceof NumericType) {
sortedGeometries = sortByNumericValues(geometriesByCodeValue);
} else {
logger.addEvent(logger.logErrorMsg("{0}: Unsupported type {1} for {2}.", usageScope, keyType.toString(), getQualifiedIliName()));
return Value.createSkipEvaluation();
}

List<Geometry> sortedGeometries = sortByEnumValues(geometriesByEnumValue, enumType);
for (int i = 0; i < sortedGeometries.size() - 1; i++) {
Geometry current = sortedGeometries.get(i);
Geometry next = sortedGeometries.get(i + 1);
Expand All @@ -104,7 +110,15 @@ private List<Geometry> sortByEnumValues(Map<ValueKey, Geometry> map, Enumeration
.collect(Collectors.toList());
}

private ValueKey getEnumValue(IomObject object, PathEl[] enumPath) {
private List<Geometry> sortByNumericValues(Map<ValueKey, Geometry> map) {
return map.entrySet()
.stream()
.sorted(Comparator.comparingDouble(entry -> entry.getKey().getNumericValue()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

private ValueKey getCodeValue(IomObject object, PathEl[] enumPath) {
Value value = validator.getValueFromObjectPath(null, object, enumPath, null);
return new ValueKey(value);
}
Expand Down Expand Up @@ -147,6 +161,10 @@ public String getStringValue() {
return value.getValue();
}

public double getNumericValue() {
return value.getNumeric();
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -169,12 +187,12 @@ public int hashCode() {
private static final class InsideAreaKey {
private final List<String> objectIds;
private final String geometryAttribute;
private final String enumAttribute;
private final String codeAttribute;

InsideAreaKey(List<String> objectIds, String geometryAttribute, String enumAttribute) {
InsideAreaKey(List<String> objectIds, String geometryAttribute, String codeAttribute) {
this.objectIds = objectIds;
this.geometryAttribute = geometryAttribute;
this.enumAttribute = enumAttribute;
this.codeAttribute = codeAttribute;
}

@Override
Expand All @@ -188,12 +206,12 @@ public boolean equals(Object o) {
InsideAreaKey that = (InsideAreaKey) o;
return objectIds.equals(that.objectIds)
&& geometryAttribute.equals(that.geometryAttribute)
&& enumAttribute.equals(that.enumAttribute);
&& codeAttribute.equals(that.codeAttribute);
}

@Override
public int hashCode() {
return Objects.hash(objectIds, geometryAttribute, enumAttribute);
return Objects.hash(objectIds, geometryAttribute, codeAttribute);
}
}
}
6 changes: 3 additions & 3 deletions src/model/NGK_SO_FunctionsExt.ili
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
INTERLIS 2.4;
MODEL NGK_SO_FunctionsExt
AT "mailto:[email protected]" VERSION "2024-01-23" =
!!@ fn.description = "Prüft bei der Objektmenge, dass die gemäss dem geordneten Enum jeweils kleineren Flächen innerhalb der grösseren Flächen liegen. Die Sortierung des Enums muss von der kleinsten zur grössten Fläche erfolgen.";
!!@ fn.param = "Objects: Zu prüfende Objektmenge. GeometryAttr: Pfad zur Geometrie. CodeAttr: Pfad zum Enum";
!!@ fn.description = "Prüft bei der Objektmenge, dass die gemäss dem geordneten Enum oder Zahlen-Code jeweils kleineren Flächen innerhalb der grösseren Flächen liegen. Die Sortierung des Codes muss von der kleinsten zur grössten Fläche erfolgen.";
!!@ fn.param = "Objects: Zu prüfende Objektmenge. GeometryAttr: Pfad zur Geometrie. CodeAttr: Pfad zum geordneten Enum oder Zahlen-Code";
!!@ fn.return = "Boolean";
!!@ fn.since = "2024-01-23";
FUNCTION IsInsideAreaByCodeEnum (Objects: OBJECTS OF ANYCLASS; GeometryAttr: TEXT; CodeAttr: TEXT): BOOLEAN;
FUNCTION IsInsideAreaByCode (Objects: OBJECTS OF ANYCLASS; GeometryAttr: TEXT; CodeAttr: TEXT): BOOLEAN;
END NGK_SO_FunctionsExt.
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,26 @@ MODEL TestSuite
TOPIC FunctionTestTopic =

DOMAIN
Code = (
CodeEnum = (
code_1,
code_2,
code_3,
code_4
) ORDERED;
CodeNumeric = 0 .. 999;

!!@CRS=EPSG:2056
CHKoord = COORD 2460000.000 .. 2870000.000 [INTERLIS.m],
1045000.000 .. 1310000.000 [INTERLIS.m],
ROTATION 2 -> 1;

CLASS BaseClass =
code : Code;
codeEnum : CodeEnum;
codeNumeric : CodeNumeric;
surface : SURFACE WITH (STRAIGHTS) VERTEX CHKoord WITHOUT OVERLAPS > 0.001;

SET CONSTRAINT insideAreaConstraint: NGK_SO_FunctionsExt.IsInsideAreaByCodeEnum(ALL, "surface", "code");
SET CONSTRAINT insideAreaConstraintEnum: NGK_SO_FunctionsExt.IsInsideAreaByCode(ALL, "surface", "codeEnum");
SET CONSTRAINT insideAreaConstraintNumeric: NGK_SO_FunctionsExt.IsInsideAreaByCode(ALL, "surface", "codeNumeric");
END BaseClass;

END FunctionTestTopic;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<ili:datasection>
<TestSuite:FunctionTestTopic ili:bid="TestSuite.FunctionTestTopic">
<TestSuite:BaseClass ili:tid="1">
<TestSuite:code>code_1</TestSuite:code>
<TestSuite:codeEnum>code_1</TestSuite:codeEnum>
<TestSuite:codeNumeric>11</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down Expand Up @@ -134,7 +135,8 @@
</TestSuite:surface>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="2">
<TestSuite:code>code_2</TestSuite:code>
<TestSuite:codeEnum>code_2</TestSuite:codeEnum>
<TestSuite:codeNumeric>22</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down Expand Up @@ -289,7 +291,8 @@
</TestSuite:surface>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="3">
<TestSuite:code>code_3</TestSuite:code>
<TestSuite:codeEnum>code_3</TestSuite:codeEnum>
<TestSuite:codeNumeric>33</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down Expand Up @@ -420,7 +423,8 @@
</TestSuite:surface>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="4">
<TestSuite:code>code_4</TestSuite:code>
<TestSuite:codeEnum>code_4</TestSuite:codeEnum>
<TestSuite:codeNumeric>44</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
</ili:headersection>
<ili:datasection>
<TestSuite:FunctionTestTopic ili:bid="TestSuite.FunctionTestTopic">
<TestSuite:BaseClass ili:tid="a"><!-- Code ordered 2, 1, 4 to test sorting and gap in codes -->
<TestSuite:code>code_2</TestSuite:code>
<TestSuite:BaseClass ili:tid="a"><!-- Code enum ordered 2, 1, 4 and numeric 1, 0, 440 to test sorting and gap in codes -->
<TestSuite:codeEnum>code_2</TestSuite:codeEnum>
<TestSuite:codeNumeric>1</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down Expand Up @@ -74,7 +75,8 @@
</TestSuite:surface>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="b">
<TestSuite:code>code_1</TestSuite:code>
<TestSuite:codeEnum>code_1</TestSuite:codeEnum>
<TestSuite:codeNumeric>0</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down Expand Up @@ -149,7 +151,8 @@
</TestSuite:surface>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="c">
<TestSuite:code>code_4</TestSuite:code>
<TestSuite:codeEnum>code_4</TestSuite:codeEnum>
<TestSuite:codeNumeric>440</TestSuite:codeNumeric>
<TestSuite:surface>
<geom:surface>
<geom:exterior>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,31 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public final class IsInsideAreaByCodeEnumIoxPluginTest {
private static final String ILI_FILE = "IsInsideAreaByCodeEnum/SetConstraints.ili";
private static final String TEST_DATA_OK = "IsInsideAreaByCodeEnum/TestData_Ok.xtf";
private static final String TEST_DATA_FAIL = "IsInsideAreaByCodeEnum/TestData_Fail.xtf";
public final class IsInsideAreaByCodeIoxPluginTest {
private static final String ILI_FILE = "IsInsideAreaByCode/SetConstraints.ili";
private static final String TEST_DATA_OK = "IsInsideAreaByCode/TestData_Ok.xtf";
private static final String TEST_DATA_FAIL = "IsInsideAreaByCode/TestData_Fail.xtf";
private ValidationTestHelper vh = null;

@BeforeEach
public void setUp() {
vh = new ValidationTestHelper();
vh.addFunction(new IsInsideAreaByCodeEnumIoxPlugin());
vh.addFunction(new IsInsideAreaByCodeIoxPlugin());
}

@Test
public void setConstraintOk() throws Ili2cFailure, IoxException {
vh.runValidation(new String[]{TEST_DATA_OK}, new String[]{ILI_FILE});
Assert.equals(0, vh.getErrs().size());
AssertionHelper.assertNoConstraintError(vh, "insideAreaConstraint");
AssertionHelper.assertNoConstraintError(vh, "insideAreaConstraintEnum");
AssertionHelper.assertNoConstraintError(vh, "insideAreaConstraintNumeric");
}

@Test
public void setConstraintFail() throws Ili2cFailure, IoxException {
vh.runValidation(new String[]{TEST_DATA_FAIL}, new String[]{ILI_FILE});
Assert.equals(1, vh.getErrs().size());
AssertionHelper.assertConstraintErrors(vh, 1, "insideAreaConstraint");
Assert.equals(2, vh.getErrs().size());
AssertionHelper.assertConstraintErrors(vh, 1, "insideAreaConstraintEnum");
AssertionHelper.assertConstraintErrors(vh, 1, "insideAreaConstraintNumeric");
}
}

0 comments on commit 0f3b8eb

Please sign in to comment.