Skip to content

Commit

Permalink
Merge pull request #35 Add Union function
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b authored Dec 18, 2023
2 parents 5c88416 + 27796ad commit 72187ba
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 19 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,4 @@ public static Double sum(Collection<IomObject> items, Function<IomObject, Double
.map(func)
.reduce(0.0, Double::sum);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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.Iox2jtsException;
import ch.interlis.iox_j.jts.Iox2jtsext;
import ch.interlis.iox_j.jts.Jts2iox;
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<UnionSurfaceKey, IomObject> 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<IomObject> 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<IomObject> 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<IomObject> surfaces;

private UnionSurfaceKey(Collection<IomObject> 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);
}
}
}
6 changes: 6 additions & 0 deletions src/model/GeoW_FunctionsExt.ili
Original file line number Diff line number Diff line change
Expand Up @@ -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.
15 changes: 0 additions & 15 deletions src/test/data/GetInnerRingsCount/TestData.xtf
Original file line number Diff line number Diff line change
Expand Up @@ -154,27 +154,22 @@
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530010</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530010</geom:c1>
<geom:c2>1150010</geom:c2>
<geom:c3>469</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150010</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
</geom:polyline>
</geom:exterior>
Expand All @@ -183,27 +178,22 @@
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150003</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530006</geom:c1>
<geom:c2>1150003</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530006</geom:c1>
<geom:c2>1150006</geom:c2>
<geom:c3>469</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150006</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150003</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
</geom:polyline>
</geom:interior>
Expand All @@ -214,27 +204,22 @@
<geom:coord>
<geom:c1>2540000</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2540010</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2540010</geom:c1>
<geom:c2>1150010</geom:c2>
<geom:c3>469</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2540000</geom:c1>
<geom:c2>1150010</geom:c2>
<geom:c3>468</geom:c3>
</geom:coord>
<geom:coord>
<geom:c1>2540000</geom:c1>
<geom:c2>1150000</geom:c2>
<geom:c3>467</geom:c3>
</geom:coord>
</geom:polyline>
</geom:exterior>
Expand Down
31 changes: 31 additions & 0 deletions src/test/data/Union/MandatoryConstraintMergedInnerRings.ili
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
INTERLIS 2.4;

MODEL TestSuite
AT "mailto:[email protected]" 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.
30 changes: 30 additions & 0 deletions src/test/data/Union/MandatoryConstraintThis.ili
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
INTERLIS 2.4;

MODEL TestSuite
AT "mailto:[email protected]" 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.
101 changes: 101 additions & 0 deletions src/test/data/Union/TestData.xtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:GeoW_FunctionsExt="http://www.interlis.ch/xtf/2.4/GeoW_FunctionsExt"
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>
<TestSuite:FunctionTestTopic ili:bid="TestSuite.FunctionTestTopic">
<TestSuite:BaseClass ili:tid="0">
<TestSuite:surfaceAttribute> <!-- Surface with 1 inner ring -->
<geom:surface>
<geom:exterior>
<geom:polyline>
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530010</geom:c1>
<geom:c2>1150000</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530010</geom:c1>
<geom:c2>1150010</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150010</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530000</geom:c1>
<geom:c2>1150000</geom:c2>
</geom:coord>
</geom:polyline>
</geom:exterior>
<geom:interior>
<geom:polyline>
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150003</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530006</geom:c1>
<geom:c2>1150003</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530006</geom:c1>
<geom:c2>1150006</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150006</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2530003</geom:c1>
<geom:c2>1150003</geom:c2>
</geom:coord>
</geom:polyline>
</geom:interior>
</geom:surface>
</TestSuite:surfaceAttribute>
</TestSuite:BaseClass>
<TestSuite:BaseClass ili:tid="1">
<TestSuite:surfaceAttribute> <!-- Surface without inner ring -->
<geom:surface>
<geom:exterior>
<geom:polyline>
<geom:coord>
<geom:c1>2646349.508</geom:c1>
<geom:c2>1249022.931</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2646353.508</geom:c1>
<geom:c2>1249022.926</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2646353.503</geom:c1>
<geom:c2>1249018.926</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2646349.503</geom:c1>
<geom:c2>1249018.931</geom:c2>
</geom:coord>
<geom:coord>
<geom:c1>2646349.508</geom:c1>
<geom:c2>1249022.931</geom:c2>
</geom:coord>
</geom:polyline>
</geom:exterior>
</geom:surface>
</TestSuite:surfaceAttribute>
</TestSuite:BaseClass>
</TestSuite:FunctionTestTopic>
</ili:datasection>
</ili:transfer>
Loading

0 comments on commit 72187ba

Please sign in to comment.