Skip to content

Commit

Permalink
Filter object stream by perimeter (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b authored Nov 6, 2024
2 parents 55c7fa5 + e0df263 commit 944a523
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 37 deletions.
50 changes: 50 additions & 0 deletions src/main/java/ch/geowerkstatt/lk2dxf/GeometryObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch.geowerkstatt.lk2dxf;

import ch.interlis.iom.IomObject;
import ch.interlis.iom_j.itf.impl.jtsext.geom.JtsextGeometryFactory;
import ch.interlis.iox.IoxException;
import ch.interlis.iox_j.jts.Iox2jtsext;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;

public record GeometryObject(Geometry geometry, IomObject iomObject) {
private static final String BASKET_NAME = "SIA405_LKMap_2015_LV95.SIA405_LKMap";
private static final GeometryFactory GEOMETRY_FACTORY = new JtsextGeometryFactory();

/**
* Creates a new geometry object from the given {@link IomObject}.
* @param iomObject The {@link IomObject} to create the geometry object from.
* @return A geometry object containing the {@link IomObject} and its extracted {@link Geometry}.
* @throws IllegalArgumentException If the object tag is not supported.
* @throws RuntimeException If an error occurs while extracting the geometry.
*/
public static GeometryObject create(IomObject iomObject) {
try {
Geometry geometry = switch (iomObject.getobjecttag()) {
case BASKET_NAME + ".LKPunkt" -> readPoint(iomObject, "SymbolPos");
case BASKET_NAME + ".LKLinie" -> readLine(iomObject, "Linie");
case BASKET_NAME + ".LKFlaeche" -> readSurface(iomObject, "Flaeche");
case BASKET_NAME + ".LKObjekt_Text" -> readPoint(iomObject, "TextPos");
default -> throw new IllegalArgumentException("Unsupported object tag: " + iomObject.getobjecttag());
};
return new GeometryObject(geometry, iomObject);
} catch (IoxException e) {
throw new RuntimeException("Error creating geometry for object with id \"" + iomObject.getobjectoid() + "\".", e);
}
}

private static Geometry readPoint(IomObject iomObject, String attributeName) throws IoxException {
IomObject position = iomObject.getattrobj(attributeName, 0);
return GEOMETRY_FACTORY.createPoint(Iox2jtsext.coord2JTS(position));
}

private static Geometry readLine(IomObject iomObject, String attributeName) throws IoxException {
IomObject line = iomObject.getattrobj(attributeName, 0);
return Iox2jtsext.polyline2JTS(line, false, 0.0);
}

private static Geometry readSurface(IomObject iomObject, String attributeName) throws IoxException {
IomObject surface = iomObject.getattrobj(attributeName, 0);
return Iox2jtsext.surface2JTS(surface, 0.0);
}
}
18 changes: 18 additions & 0 deletions src/main/java/ch/geowerkstatt/lk2dxf/LK2DxfOptions.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package ch.geowerkstatt.lk2dxf;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTReader;

import java.util.List;
import java.util.Optional;

Expand All @@ -9,4 +12,19 @@ public record LK2DxfOptions(
Optional<String> perimeterWkt,
Optional<String> logfile,
boolean trace) {

/**
* Parses the perimeter WKT string to a {@link Geometry}.
* @return The parsed perimeter geometry, or an empty optional if the wkt was empty.
* @throws IllegalStateException If the wkt could not be parsed.
*/
public Optional<Geometry> parsePerimeter() {
return perimeterWkt.map(wkt -> {
try {
return new WKTReader().read(wkt);
} catch (com.vividsolutions.jts.io.ParseException e) {
throw new IllegalStateException("Error parsing perimeter WKT.", e);
}
});
}
}
123 changes: 95 additions & 28 deletions src/main/java/ch/geowerkstatt/lk2dxf/LKMapXtfReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import ch.interlis.iox_j.utility.ReaderFactory;

import java.io.File;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
* A reader for LKMap INTERLIS transfer files.
Expand All @@ -24,6 +27,7 @@ public final class LKMapXtfReader implements AutoCloseable {
private static final ReaderFactory READER_FACTORY = new ReaderFactory();

private final IoxReader reader;
private LKMapXtfReaderState state = null;

/**
* Creates a new reader for LKMap INTERLIS transfer files.
Expand All @@ -37,43 +41,106 @@ public LKMapXtfReader(File xtfFile) throws IoxException {
}

/**
* Reads the objects streamed by the reader and passes them to the consumer.
* @param consumer A consumer to process the objects.
* @throws IoxException If an error occurs while reading the objects.
* @throws IllegalStateException If the transfer file is not in the expected format.
* Reads the objects as a sequential stream.
* Advancing the stream may throw an exception when reading invalid data.
* @return A stream of objects contained in the xtf file.
* @throws IllegalStateException If this method is called more than once.
*/
public void readObjects(Consumer<IomObject> consumer) throws IoxException {
IoxEvent event = reader.read();
if (!(event instanceof StartTransferEvent)) {
throw new IllegalStateException("Expected start transfer event, got: " + event);
public Stream<IomObject> readObjects() {
if (state != null) {
throw new IllegalStateException("readObjects() can only be called once");
}
state = LKMapXtfReaderState.INITIALIZED;

event = reader.read();
while (!(event instanceof EndTransferEvent)) {
if (event instanceof StartBasketEvent startBasketEvent) {
if (!BASKET_NAME.equals(startBasketEvent.getType())) {
throw new IllegalStateException("Invalid basket type: " + startBasketEvent.getType());
return StreamSupport.stream(new XtfReaderSpliterator(), false);
}

@Override
public void close() throws Exception {
reader.close();
}

private enum LKMapXtfReaderState {
INITIALIZED,
TRANSFER,
BASKET,
COMPLETED,
}

/**
* A sequential spliterator for reading objects from the surrounding {@link LKMapXtfReader}.
* Advancing the spliterator will read from the xtf reader and may throw an exception when reading invalid data.
*/
private class XtfReaderSpliterator implements Spliterator<IomObject> {
@Override
public boolean tryAdvance(Consumer<? super IomObject> action) {
try {
IoxEvent event = reader.read();
while (event != null) {
switch (event) {
case StartTransferEvent ignored -> {
if (state != LKMapXtfReaderState.INITIALIZED) {
throw new IllegalStateException("Unexpected start transfer event in state: " + state);
}
state = LKMapXtfReaderState.TRANSFER;
System.out.println("Start transfer");
}
case StartBasketEvent startBasketEvent -> {
if (state != LKMapXtfReaderState.TRANSFER) {
throw new IllegalStateException("Unexpected start basket event in state: " + state);
}
if (!BASKET_NAME.equals(startBasketEvent.getType())) {
throw new IllegalStateException("Invalid basket type: " + startBasketEvent.getType());
}
state = LKMapXtfReaderState.BASKET;
System.out.println("Start basket \"" + startBasketEvent.getBid() + "\"");
}
case ObjectEvent objectEvent -> {
if (state != LKMapXtfReaderState.BASKET) {
throw new IllegalStateException("Unexpected object event in state: " + state);
}
action.accept(objectEvent.getIomObject());
return true;
}
case EndBasketEvent ignored -> {
if (state != LKMapXtfReaderState.BASKET) {
throw new IllegalStateException("Unexpected end basket event in state: " + state);
}
state = LKMapXtfReaderState.TRANSFER;
System.out.println("End basket");
}
case EndTransferEvent ignored -> {
if (state != LKMapXtfReaderState.TRANSFER) {
throw new IllegalStateException("Unexpected end transfer event in state: " + state);
}
state = LKMapXtfReaderState.COMPLETED;
System.out.println("End transfer");
return false;
}
default -> throw new IllegalStateException("Unexpected iox event: " + event);
}
event = reader.read();
}
} else {
throw new IllegalStateException("Expected start basket event, got: " + event);
}

event = reader.read();
while (event instanceof ObjectEvent objectEvent) {
consumer.accept(objectEvent.getIomObject());
event = reader.read();
throw new IllegalStateException("Unexpected end of file");
} catch (IoxException e) {
throw new RuntimeException(e);
}
}

if (!(event instanceof EndBasketEvent)) {
throw new IllegalStateException("Expected end basket event, got: " + event);
}
@Override
public Spliterator<IomObject> trySplit() {
return null;
}

event = reader.read();
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
}

@Override
public void close() throws Exception {
reader.close();
@Override
public int characteristics() {
return IMMUTABLE | NONNULL;
}
}
}
17 changes: 14 additions & 3 deletions src/main/java/ch/geowerkstatt/lk2dxf/Main.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.geowerkstatt.lk2dxf;

import com.vividsolutions.jts.geom.Geometry;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
Expand All @@ -10,6 +11,8 @@
import java.io.File;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public final class Main {
private static final String OPTION_HELP = "help";
Expand Down Expand Up @@ -50,11 +53,19 @@ public static void main(String[] args) {
}

private static void processFiles(LK2DxfOptions options) {
Optional<Geometry> perimeter = options.parsePerimeter();

for (String xtfFile : options.xtfFiles()) {
try (LKMapXtfReader reader = new LKMapXtfReader(new File(xtfFile))) {
reader.readObjects(iomObject -> {
System.out.println(iomObject.getobjectoid());
});
AtomicInteger counter = new AtomicInteger();
Stream<GeometryObject> objects = reader.readObjects()
.map(GeometryObject::create);

if (perimeter.isPresent()) {
objects = objects.filter(o -> perimeter.get().intersects(o.geometry()));
}

objects.forEach(o -> System.out.println(counter.incrementAndGet() + ": " + o.iomObject().getobjectoid()));
} catch (Exception e) {
System.err.println("Failed to process file: " + xtfFile);
e.printStackTrace();
Expand Down
100 changes: 100 additions & 0 deletions src/test/data/LKMapXtfReaderTest/ValidMultipleBaskets.xtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version ="1.0" encoding ="UTF-8"?>
<TRANSFER xmlns="http://www.interlis.ch/INTERLIS2.3">
<HEADERSECTION VERSION="2.3" SENDER="Test">
<MODELS>
<MODEL NAME="Units" URI="http://www.interlis.ch/models" VERSION="2012-02-20" />
<MODEL NAME="Base_LV95" URI="http://www.sia.ch/405" VERSION="05.10.2018" />
<MODEL NAME="SIA405_Base_LV95" URI="http://www.sia.ch/405" VERSION="05.10.2018" />
<MODEL NAME="SIA405_LKMap_2015_LV95" URI="http://www.sia.ch/405" VERSION="27.04.2018" />
</MODELS>
<COMMENT>test data</COMMENT>
</HEADERSECTION>
<DATASECTION>
<SIA405_LKMap_2015_LV95.SIA405_LKMap BID="x1">
<SIA405_LKMap_2015_LV95.SIA405_LKMap.LKFlaeche TID="basket1object001">
<OBJ_ID>basket1object001</OBJ_ID>
<Metaattribute>
<SIA405_Base_LV95.Metaattribute>
<Datenherr>Test</Datenherr>
<Datenlieferant>Test</Datenlieferant>
<Letzte_Aenderung>20241030</Letzte_Aenderung>
</SIA405_Base_LV95.Metaattribute>
</Metaattribute>
<Eigentuemer>Test</Eigentuemer>
<Lagebestimmung>unbekannt</Lagebestimmung>
<Status>in_Betrieb</Status>
<Flaeche>
<SURFACE>
<BOUNDARY>
<POLYLINE>
<COORD>
<C1>2669992.781</C1>
<C2>1208904.666</C2>
</COORD>
<COORD>
<C1>2669971.614</C1>
<C2>1206904.416</C2>
</COORD>
<COORD>
<C1>2672924.364</C1>
<C2>1206936.166</C2>
</COORD>
<COORD>
<C1>2672818.531</C1>
<C2>1208915.250</C2>
</COORD>
<COORD>
<C1>2669992.781</C1>
<C2>1208904.666</C2>
</COORD>
</POLYLINE>
</BOUNDARY>
</SURFACE>
</Flaeche>
<Objektart>Wasser.unbekannt</Objektart>
</SIA405_LKMap_2015_LV95.SIA405_LKMap.LKFlaeche>
<SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt TID="basket1object002">
<OBJ_ID>basket1object002</OBJ_ID>
<Metaattribute>
<SIA405_Base_LV95.Metaattribute>
<Datenherr>Test</Datenherr>
<Datenlieferant>Test</Datenlieferant>
<Letzte_Aenderung>20241030</Letzte_Aenderung>
</SIA405_Base_LV95.Metaattribute>
</Metaattribute>
<Eigentuemer>Test</Eigentuemer>
<Lagebestimmung>unbekannt</Lagebestimmung>
<Status>in_Betrieb</Status>
<SymbolPos>
<COORD>
<C1>2669992.781</C1>
<C2>1208904.666</C2>
</COORD>
</SymbolPos>
<Objektart>Wasser.Absperrorgan</Objektart>
</SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt>
</SIA405_LKMap_2015_LV95.SIA405_LKMap>
<SIA405_LKMap_2015_LV95.SIA405_LKMap BID="x2">
<SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt TID="basket2object001">
<OBJ_ID>basket2object001</OBJ_ID>
<Metaattribute>
<SIA405_Base_LV95.Metaattribute>
<Datenherr>Test</Datenherr>
<Datenlieferant>Test</Datenlieferant>
<Letzte_Aenderung>20241030</Letzte_Aenderung>
</SIA405_Base_LV95.Metaattribute>
</Metaattribute>
<Eigentuemer>Test</Eigentuemer>
<Lagebestimmung>ungenau</Lagebestimmung>
<Status>in_Betrieb</Status>
<SymbolPos>
<COORD>
<C1>2669992.781</C1>
<C2>1208904.666</C2>
</COORD>
</SymbolPos>
<Objektart>Fernwaerme.unbekannt</Objektart>
</SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt>
</SIA405_LKMap_2015_LV95.SIA405_LKMap>
</DATASECTION>
</TRANSFER>
Loading

0 comments on commit 944a523

Please sign in to comment.