Commit 219d36ea authored by Luna Riegel's avatar Luna Riegel
Browse files

Merge branch 'refs/heads/dev' into dev_advanced_db_management

# Conflicts:
#	CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java
parents 1c665219 5df59c7a
...@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.18.1] (2025-12-10)
### Hotfix
This hotfix removes the AUTO_SERVER flag from the embedded database, which causes a Firewall popup when the database is
started, due to opening local ports for accessing the database while CityDoctor is running.
The Firewall request can be safely denied, as CityDoctor uses the database-file for connections to the database.
Accepting the request does not open up remote connections to the database, as H2 by default only allows connections from
the local machine.
## [3.18.0] (2025-12-05) ## [3.18.0] (2025-12-05)
**Semi-breaking change:** This MR introduces breaking changes to the CityDoctorModel and CityObject classes. Usage of the CityDoctorValidation API and CLI remain backwards compatible. **Semi-breaking change:** This MR introduces breaking changes to the CityDoctorModel and CityObject classes. Usage of the CityDoctorValidation API and CLI remain backwards compatible.
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.18.0</version> <version>3.18.1</version>
</parent> </parent>
<artifactId>CityDoctorCheckResult</artifactId> <artifactId>CityDoctorCheckResult</artifactId>
<dependencies> <dependencies>
......
...@@ -22,6 +22,7 @@ import java.io.BufferedReader; ...@@ -22,6 +22,7 @@ import java.io.BufferedReader;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -65,7 +66,11 @@ public class CheckReport { ...@@ -65,7 +66,11 @@ public class CheckReport {
private Map<String, FeatureReport> featureReports; private Map<String, FeatureReport> featureReports;
public static CheckReport load(String file) throws CheckReportParseException { public static CheckReport load(String file) throws CheckReportParseException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) { return load(Path.of(file));
}
public static CheckReport load(Path path) throws CheckReportParseException {
try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
CheckReport report = (CheckReport) getContext().createUnmarshaller().unmarshal(new InputSource(reader)); CheckReport report = (CheckReport) getContext().createUnmarshaller().unmarshal(new InputSource(reader));
enrichReportStructure(report); enrichReportStructure(report);
return report; return report;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.18.0</version> <version>3.18.1</version>
</parent> </parent>
<artifactId>CityDoctorEdge</artifactId> <artifactId>CityDoctorEdge</artifactId>
<dependencies> <dependencies>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.18.0</version> <version>3.18.1</version>
</parent> </parent>
<properties> <properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString> <versionString>${project.version}-${git.commit.id.abbrev}</versionString>
......
...@@ -40,6 +40,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError; ...@@ -40,6 +40,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError; import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError; import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError; import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
public abstract class AbstractErrorVisitor implements ErrorVisitor { public abstract class AbstractErrorVisitor implements ErrorVisitor {
...@@ -206,5 +207,9 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor { ...@@ -206,5 +207,9 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor {
@Override @Override
public void visit(SolidError err) { public void visit(SolidError err) {
} }
@Override
public void visit(XMLValidationError err) {
}
} }
...@@ -83,4 +83,8 @@ public interface CheckError extends Serializable { ...@@ -83,4 +83,8 @@ public interface CheckError extends Serializable {
*/ */
public void report(ErrorReport report); public void report(ErrorReport report);
public default boolean isParsingError() {
return false;
}
} }
...@@ -26,7 +26,7 @@ import java.util.Map; ...@@ -26,7 +26,7 @@ import java.util.Map;
import de.hft.stuttgart.citydoctor2.utils.CheckErrorFound; import de.hft.stuttgart.citydoctor2.utils.CheckErrorFound;
import de.hft.stuttgart.citydoctor2.utils.visitors.CheckableErrorCollector; import de.hft.stuttgart.citydoctor2.utils.visitors.CheckableErrorCollector;
import de.hft.stuttgart.citydoctor2.utils.visitors.ClearVisitorResultsVisitor; import de.hft.stuttgart.citydoctor2.utils.visitors.ClearResultsVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ClearMetaInformationVisitor; import de.hft.stuttgart.citydoctor2.utils.visitors.ClearMetaInformationVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsAnyErrorVisitor; import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsAnyErrorVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsErrorVisitor; import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsErrorVisitor;
...@@ -221,18 +221,23 @@ public abstract class Checkable implements Serializable { ...@@ -221,18 +221,23 @@ public abstract class Checkable implements Serializable {
} }
/** /**
* Clears the checkResults list of this checkable. * Clears the checkResults of this checkable unless the error occured during parsing.
*/ */
public void clearCheckResults() { public void clearCheckResults() {
setValidated(false); checkResults.entrySet().removeIf(e -> {
checkResults.clear(); if (e.getValue().getError() == null) {
return true;
}
return !e.getValue().getError().isParsingError();
});
setValidated(!checkResults.isEmpty());
} }
/** /**
* Clears the checkResults list of this checkable and all child objects in its datastructure. * Clears the checkResults list of this checkable and all child objects in its datastructure.
*/ */
public final void clearAllContainedCheckResults() { public final void clearAllContainedCheckResults() {
this.accept(new ClearVisitorResultsVisitor()); this.accept(new ClearResultsVisitor());
} }
/** /**
......
...@@ -66,6 +66,7 @@ public record ErrorId(String name) implements Serializable { ...@@ -66,6 +66,7 @@ public record ErrorId(String name) implements Serializable {
public static final ErrorId SE_BS_UNFRAGMENTED = new ErrorId("SE_BS_UNFRAGMENTED"); public static final ErrorId SE_BS_UNFRAGMENTED = new ErrorId("SE_BS_UNFRAGMENTED");
public static final ErrorId GE_P_DEGENERATED_RING = new ErrorId("GE_P_DEGENERATED_POLYGON"); public static final ErrorId GE_P_DEGENERATED_RING = new ErrorId("GE_P_DEGENERATED_POLYGON");
public static final ErrorId SE_POLYGON_WITHOUT_SURFACE = new ErrorId("SE_POLYGON_WITHOUT_SURFACE"); public static final ErrorId SE_POLYGON_WITHOUT_SURFACE = new ErrorId("SE_POLYGON_WITHOUT_SURFACE");
public static final ErrorId SC_SCHEMA_VALIDATION = new ErrorId("SC_SCHEMA_VALIDATION");
......
...@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError; ...@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError; import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError; import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError; import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
/** /**
* Visitor pattern interface to access errors. This is to determine the type of * Visitor pattern interface to access errors. This is to determine the type of
...@@ -149,5 +150,7 @@ public interface ErrorVisitor { ...@@ -149,5 +150,7 @@ public interface ErrorVisitor {
public void visit(CheckError err); public void visit(CheckError err);
public void visit(SolidError err); public void visit(SolidError err);
public void visit(XMLValidationError err);
} }
...@@ -51,6 +51,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError; ...@@ -51,6 +51,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError; import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError; import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError; import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
/** /**
* Visitor pattern interface used in the healing procedures. The modification * Visitor pattern interface used in the healing procedures. The modification
...@@ -199,6 +200,10 @@ public interface HealingMethod { ...@@ -199,6 +200,10 @@ public interface HealingMethod {
default boolean visit(PolygonWithoutSurfaceError err, ModificationListener l) { default boolean visit(PolygonWithoutSurfaceError err, ModificationListener l) {
return false; return false;
} }
default boolean visit(XMLValidationError err, ModificationListener l) {
return false;
}
public HealingMethod createNew(); public HealingMethod createNew();
......
...@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError; ...@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError; import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError; import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError; import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge; import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
...@@ -403,5 +404,10 @@ public class QualityAdeErrorVisitor extends AbstractErrorVisitor { ...@@ -403,5 +404,10 @@ public class QualityAdeErrorVisitor extends AbstractErrorVisitor {
public void visit(PolygonWithoutSurfaceError err) { public void visit(PolygonWithoutSurfaceError err) {
// not translated // not translated
} }
@Override
public void visit(XMLValidationError err) {
// not translated
}
} }
package de.hft.stuttgart.citydoctor2.check.error;
import java.io.Serial;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ErrorVisitor;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
public class XMLValidationError implements CheckError {
@Serial
private static final long serialVersionUID = 4002356100680460598L;
private String message;
public XMLValidationError(String message) {
this.message = message;
}
@Override
public ErrorType getType() {
return ErrorType.ERROR;
}
@Override
public ErrorId getErrorId() {
return ErrorId.SC_SCHEMA_VALIDATION;
}
@Override
public GmlElement getFeature() {
return null;
}
@Override
public void accept(ErrorVisitor errorVisitor) {
errorVisitor.visit(this);
}
@Override
public boolean accept(HealingMethod method, ModificationListener l) {
return method.visit(this, l);
}
public String getMessage() {
return message;
}
@Override
public void report(ErrorReport report) {
report.add("message", message);
}
@Override
public boolean isParsingError() {
return true;
}
}
...@@ -18,13 +18,10 @@ ...@@ -18,13 +18,10 @@
*/ */
package de.hft.stuttgart.citydoctor2.datastructure; package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import java.io.Serial;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import java.util.ArrayList;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils; import java.util.List;
import javafx.scene.paint.Color;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.model.deprecated.bridge.DeprecatedPropertiesOfBridgeConstructiveElement; import org.citygml4j.core.model.deprecated.bridge.DeprecatedPropertiesOfBridgeConstructiveElement;
import org.citygml4j.core.util.geometry.GeometryFactory; import org.citygml4j.core.util.geometry.GeometryFactory;
import org.xmlobjects.gml.model.geometry.GeometryProperty; import org.xmlobjects.gml.model.geometry.GeometryProperty;
...@@ -34,14 +31,13 @@ import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface; ...@@ -34,14 +31,13 @@ import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface;
import org.xmlobjects.gml.model.geometry.primitives.Solid; import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SolidProperty; import org.xmlobjects.gml.model.geometry.primitives.SolidProperty;
import java.io.Serial; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import java.util.ArrayList; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import java.util.List; import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import javafx.scene.paint.Color;
public class BridgeConstructiveElement extends CityObject { public class BridgeConstructiveElement extends CityObject {
private static final Logger logger = LogManager.getLogger(BridgeConstructiveElement.class);
private static final String CANNOT_ADD = "Cannot add "; private static final String CANNOT_ADD = "Cannot add ";
@Serial @Serial
...@@ -105,20 +101,6 @@ public class BridgeConstructiveElement extends CityObject { ...@@ -105,20 +101,6 @@ public class BridgeConstructiveElement extends CityObject {
} }
} }
private void reCreateBoundarySurface(GeometryFactory factory, ParserConfiguration config, BoundarySurface bs) {
if (bs.getGeometries().isEmpty()) {
for (AbstractSpaceBoundaryProperty bsp : gmlBridgeElement.getBoundaries()) {
if (bsp.getObject() != null && bsp.getObject() == bs.getGmlObject()) {
logger.warn("Found empty boundary surface: {}, removing from BridgeConstructiveElement", bs.getGmlId());
gmlBridgeElement.getBoundaries().remove(bsp);
break;
}
}
return;
}
bs.reCreateGeometries(factory, config);
}
private void setCompositeSurfaceAccordingToLod(Geometry geom, CompositeSurface cs) { private void setCompositeSurfaceAccordingToLod(Geometry geom, CompositeSurface cs) {
switch (geom.getLod()) { switch (geom.getLod()) {
case LOD1: case LOD1:
......
...@@ -23,6 +23,8 @@ import java.util.ArrayList; ...@@ -23,6 +23,8 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.database.FeatureCache; import de.hft.stuttgart.citydoctor2.database.FeatureCache;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache; import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
...@@ -172,10 +174,13 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -172,10 +174,13 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private final Path directory; private final Path directory;
private final double neighborDistance; private final double neighborDistance;
private Map<String, ConcretePolygon> polygonMap = new HashMap<>(); private Map<String, ConcretePolygon> polygonMap = new HashMap<>();
private Map<String, CompositeCollection> compositeMap = new HashMap<>(); private Map<String, CompositeCollection> compositeMap = new HashMap<>();;
private List<ResolvableReference> references = new ArrayList<>(); private List<ResolvableReference> references = new ArrayList<>();
private Map<Vertex, Vertex> vertexMap = new HashMap<>(); private Map<Vertex, Vertex> vertexMap = new HashMap<>();
private Map<String, List<GmlId>> unresolvedPolyRefIDMap = new HashMap<>();
private Map<String, GmlId> parsedPolygonsIdMap = new HashMap<>();
public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) { public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) {
this.config = config; this.config = config;
this.directory = path.getParent(); this.directory = path.getParent();
...@@ -197,6 +202,37 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -197,6 +202,37 @@ public class Citygml3FeatureMapper extends ObjectWalker {
} }
} }
public void checkUnresolvedReferences() {
if (unresolvedPolyRefIDMap.isEmpty()) {
return;
}
boolean xLinkGlobalError = false;
List<String> crossRefs = new ArrayList<>();
for (Map.Entry<String, List<GmlId>> entry : unresolvedPolyRefIDMap.entrySet()) {
String id = entry.getKey();
boolean emptyCrossFeatureReference = parsedPolygonsIdMap.containsKey(id);
xLinkGlobalError = xLinkGlobalError || emptyCrossFeatureReference;
if (emptyCrossFeatureReference) {
List<GmlId> gmlIds = entry.getValue();
StringJoiner joiner = new StringJoiner(", ");
gmlIds.forEach(gmlId -> joiner.add(gmlId.toString()));
GmlId crossFeatureId = parsedPolygonsIdMap.get(id);
//TODO: Localize String
logger.warn("Polygon {} of Feature {} is being referenced in other Features: {}", id, crossFeatureId, joiner);
crossRefs.add(id);
}
}
if (xLinkGlobalError) {
//TODO: Localize String
logger.warn("Model has Features containing cross-feature references without their own copies of the referenced polygons");
}
crossRefs.forEach(unresolvedPolyRefIDMap::remove);
if(!unresolvedPolyRefIDMap.isEmpty()) {
logger.warn("Model contains polygon references that could not be found");
}
}
@Override @Override
public void visit(AbstractSpace space) { public void visit(AbstractSpace space) {
// if we are here, an AbstractSpace thing was read that is not handled in the // if we are here, an AbstractSpace thing was read that is not handled in the
...@@ -903,7 +939,7 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -903,7 +939,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void finishCityObjectConstruction(CityObject co) { private void finishCityObjectConstruction(CityObject co) {
parseId(co.getGmlObject(), co); parseId(co.getGmlObject(), co);
co.clearGmlGeometries(); co.clearGmlGeometries();
resolveAndClearReferences(); resolveAndClearReferences(co);
updateEdgesAndVertices(co); updateEdgesAndVertices(co);
} }
...@@ -1092,7 +1128,7 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1092,7 +1128,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
return igh; return igh;
} }
private void resolveAndClearReferences() { private void resolveAndClearReferences(CityObject co) {
for (ResolvableReference ref : references) { for (ResolvableReference ref : references) {
String href = ref.href(); String href = ref.href();
if (href.startsWith("#")) { if (href.startsWith("#")) {
...@@ -1107,6 +1143,9 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1107,6 +1143,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
handlePolygonReference(href, geom); handlePolygonReference(href, geom);
} }
} }
for(String polygonId: polygonMap.keySet()){
parsedPolygonsIdMap.put(polygonId, co.getGmlId());
}
// clear storage for polygons and vertices // clear storage for polygons and vertices
references = new ArrayList<>(); references = new ArrayList<>();
...@@ -1117,9 +1156,12 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1117,9 +1156,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void handlePolygonReference(String href, Geometry geom) { private void handlePolygonReference(String href, Geometry geom) {
ConcretePolygon concPoly = polygonMap.get(href); ConcretePolygon concPoly = polygonMap.get(href);
if (concPoly == null) { if (concPoly == null) {
if (logger.isWarnEnabled()) { List<GmlId> featureList = unresolvedPolyRefIDMap.computeIfAbsent(href, k -> new ArrayList<>());
logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href); GmlId featureId = geom.getParent().getTopLevelCityObject().getGmlId();
} featureList.add(featureId);
// if (logger.isWarnEnabled()) {
// logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
// }
} else { } else {
LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom); LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom);
if (geom.getParent() instanceof BoundarySurface bs) { if (geom.getParent() instanceof BoundarySurface bs) {
......
...@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException; ...@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException;
public class GMLValidationHandler implements ErrorHandler { public class GMLValidationHandler implements ErrorHandler {
private static final int MAX_LINE_SIZE = 200;
private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class); private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class);
private final List<String> messages = new ArrayList<>(); private final List<String> messages = new ArrayList<>();
@Override @Override
public void error(SAXParseException exception) { public void error(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.error(message); logger.error(message);
messages.add(message); messages.add(message);
} }
private String constructMessage(SAXParseException exception) {
String exceptionMessage = exception.getMessage();
String message = "[line: " + exception.getLineNumber() + "] "
+ exceptionMessage.substring(0, Math.min(exceptionMessage.length(), MAX_LINE_SIZE));
if (exceptionMessage.length() > MAX_LINE_SIZE) {
message += "...";
}
return message;
}
@Override @Override
public void warning(SAXParseException exception) { public void warning(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.warn(message); logger.warn(message);
messages.add(message); messages.add(message);
} }
@Override @Override
public void fatalError(SAXParseException exception) { public void fatalError(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.fatal(message); logger.fatal(message);
messages.add(message); messages.add(message);
} }
......
...@@ -32,6 +32,5 @@ public interface CityGmlConsumer { ...@@ -32,6 +32,5 @@ public interface CityGmlConsumer {
} }
// public void endOfFile();
} }
...@@ -28,6 +28,7 @@ import java.nio.file.Path; ...@@ -28,6 +28,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -43,8 +44,6 @@ import javax.xml.validation.Schema; ...@@ -43,8 +44,6 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator; import javax.xml.validation.Validator;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
...@@ -84,8 +83,11 @@ import org.xmlobjects.schema.SchemaHandlerException; ...@@ -84,8 +83,11 @@ import org.xmlobjects.schema.SchemaHandlerException;
import org.xmlobjects.stream.XMLReader; import org.xmlobjects.stream.XMLReader;
import org.xmlobjects.stream.XMLReaderFactory; import org.xmlobjects.stream.XMLReaderFactory;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.Citygml3FeatureMapper; import de.hft.stuttgart.citydoctor2.mapper.citygml3.Citygml3FeatureMapper;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.GMLValidationHandler; import de.hft.stuttgart.citydoctor2.mapper.citygml3.GMLValidationHandler;
import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.math.Vector3d;
...@@ -100,615 +102,629 @@ import de.hft.stuttgart.quality.QualityADEModule; ...@@ -100,615 +102,629 @@ import de.hft.stuttgart.quality.QualityADEModule;
*/ */
public class CityGmlParser { public class CityGmlParser {
private static final String CITY_OBJECT_MEMBER = "cityObjectMember"; private static final String CITY_OBJECT_MEMBER = "cityObjectMember";
private static final String WGS_84 = "EPSG:4326"; private static final String WGS_84 = "EPSG:4326";
private static final Logger logger = LogManager.getLogger(CityGmlParser.class); private static final Logger logger = LogManager.getLogger(CityGmlParser.class);
private static final CRSFactory CRS_FACTORY = new CRSFactory(); private static final CRSFactory CRS_FACTORY = new CRSFactory();
// EPSG:31467 // EPSG:31467
private static final Pattern P_EPSG = Pattern.compile("^(EPSG:\\d+)$"); private static final Pattern P_EPSG = Pattern.compile("^(EPSG:\\d+)$");
// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783 // urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
// or // or
// urn:ogc:def:crs,crs:EPSG::28992 // urn:ogc:def:crs,crs:EPSG::28992
private static final Pattern P_OGC = Pattern.compile("urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"); private static final Pattern P_OGC = Pattern.compile("urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
private static final Pattern P_OGC2 = Pattern.compile("urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"); private static final Pattern P_OGC2 = Pattern.compile("urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH // urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH // urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
private static final Pattern P_URN = Pattern.compile("urn:adv:crs:([^\\*]+)"); private static final Pattern P_URN = Pattern.compile("urn:adv:crs:([^\\*]+)");
private static final SAXParserFactory FACTORY; private static final SAXParserFactory FACTORY;
private static Proj4FileReader csReader = new Proj4FileReader(); private static Proj4FileReader csReader = new Proj4FileReader();
private static final Pattern P_VUNITS = Pattern.compile("^\\+vunits=.+"); private static final Pattern P_VUNITS = Pattern.compile("^\\+vunits=.+");
private static final Pattern P_GEOIDGRID = Pattern.compile("^\\+geoidgrid=.+"); private static final Pattern P_GEOIDGRID = Pattern.compile("^\\+geoidgrid=.+");
private static CityGMLContext context; private static CityGMLContext context;
private static List<QName> chunkProperties = new ArrayList<>(); private static List<QName> chunkProperties = new ArrayList<>();
static {
static { System.setProperty("javax.xml.transform.TransformerFactory",
System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
FACTORY = SAXParserFactory.newInstance(); FACTORY = SAXParserFactory.newInstance();
try { try {
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false); FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) { } catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
logger.catching(e); logger.catching(e);
} }
chunkProperties.add(new QName(CityGMLConstants.CITYGML_1_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER)); chunkProperties.add(new QName(CityGMLConstants.CITYGML_1_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
chunkProperties.add(new QName(CityGMLConstants.CITYGML_2_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER)); chunkProperties.add(new QName(CityGMLConstants.CITYGML_2_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
chunkProperties.add(new QName(CityGMLConstants.CITYGML_3_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER)); chunkProperties.add(new QName(CityGMLConstants.CITYGML_3_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
} }
private CityGmlParser() { private CityGmlParser() {
} }
public static synchronized CityGMLContext getContext() { public static synchronized CityGMLContext setupContext() {
if (context == null) { if (context == null) {
try { try {
context = CityGMLContext.newInstance(CityGmlParser.class.getClassLoader()); context = CityGMLContext.newInstance(CityGmlParser.class.getClassLoader());
// also setup ades // also setup ades
ADERegistry adeRegistry = ADERegistry.getInstance(); ADERegistry adeRegistry = ADERegistry.getInstance();
adeRegistry.loadADE(new QualityADEContext()); adeRegistry.loadADE(new QualityADEContext());
} catch (CityGMLContextException e) { } catch (CityGMLContextException e) {
logger.fatal(Localization.getText("CityGmlParser.creating4jContextFailed"), e); logger.fatal(Localization.getText("CityGmlParser.creating4jContextFailed"), e);
throw new IllegalStateException("Unable to create citygml4j context"); throw new IllegalStateException("Unable to create citygml4j context");
} catch (ADEException e) { } catch (ADEException e) {
logger.fatal(Localization.getText("CityGmlParser.creatingAdePluginsFailed"), e); logger.fatal(Localization.getText("CityGmlParser.creatingAdePluginsFailed"), e);
throw new IllegalStateException("Unable to add ADE plugins to citygml4j"); throw new IllegalStateException("Unable to add ADE plugins to citygml4j");
} }
} }
return context; return context;
} }
/** /**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}. * Parses a given CityGML file and collects the contained CityObject Features in
* <p/> * a {@link CityDoctorModel}.
* The nested parse uses an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}, which * <p/>
* is not connected to the database. * The nested parse uses an
* <p/> * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
* Nested parsing should only be used for accessing Features in auxiliary, small CityGML files, e.g. parsing of * UnconnectedCache}, which is not connected to the database.
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject} files. * <p/>
* * Nested parsing should only be used for accessing Features in auxiliary, small
* @param filePath String path to the file * CityGML files, e.g. parsing of
* @param config Configuration of the parser * {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject
* @return A {@link CityDoctorModel} object with the parsed Features * LibraryObject} files.
* @throws CityGmlParseException if parsing of the CityGML-file failed *
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * @param filePath String path to the file
*/ * @param config Configuration of the parser
public static CityDoctorModel nestedCityGmlFileParse(String filePath, ParserConfiguration config) * @return A {@link CityDoctorModel} object with the parsed Features
throws CityGmlParseException, InvalidGmlFileException { * @throws CityGmlParseException if parsing of the CityGML-file failed
return parseCityGmlFile(filePath, config, null, null, false, true); * @throws InvalidGmlFileException if the GMLValidationHandler found an error
} */
public static CityDoctorModel nestedCityGmlFileParse(String filePath, ParserConfiguration config)
/** throws CityGmlParseException, InvalidGmlFileException {
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}. return parseCityGmlFile(filePath, config, null, null, false, true);
* <p/> }
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. /**
* * Parses a given CityGML file and collects the contained CityObject Features in
* @param filePath String path to the file * a {@link CityDoctorModel}.
* @param config Configuration of the parser * <p/>
* @return A {@link CityDoctorModel} object with the parsed Features * Features are held in a non-persistent, embedded Database with an in-memory
* @throws CityGmlParseException if parsing of the CityGML-file failed * upstream cache. Thus, strong references to {@link CityObject} are to be
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * avoided, to allow for the memory management of the cache.
*/ *
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config) * @param filePath String path to the file
throws CityGmlParseException, InvalidGmlFileException { * @param config Configuration of the parser
return parseCityGmlFile(filePath, config, null, null); * @return A {@link CityDoctorModel} object with the parsed Features
} * @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
/** public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config)
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}. throws CityGmlParseException, InvalidGmlFileException {
* <p/> return parseCityGmlFile(filePath, config, null, null);
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references }
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
* /**
* @param filePath String path to the file * Parses a given CityGML file and collects the contained CityObject Features in
* @param config Configuration of the parser * a {@link CityDoctorModel}.
* @param l Listener hook for tracking of parsing progress * <p/>
* @return A {@link CityDoctorModel} object with the parsed Features * Features are held in a non-persistent, embedded Database with an in-memory
* @throws CityGmlParseException if parsing of the CityGML-file failed * upstream cache. Thus, strong references to {@link CityObject} are to be
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * avoided, to allow for the memory management of the cache.
*/ *
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l) * @param filePath String path to the file
throws CityGmlParseException, InvalidGmlFileException { * @param config Configuration of the parser
return parseCityGmlFile(filePath, config, l, null); * @param l Listener hook for tracking of parsing progress
} * @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
/** * @throws InvalidGmlFileException if the GMLValidationHandler found an error
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}. */
* <p/> public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l)
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references throws CityGmlParseException, InvalidGmlFileException {
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. return parseCityGmlFile(filePath, config, l, null);
* }
* @param filePath String path to the file
* @param config Configuration of the parser /**
* @param l Listener hook for tracking of parsing progress * Parses a given CityGML file and collects the contained CityObject Features in
* @param handler Validation handler for GML-file validation * a {@link CityDoctorModel}.
* @return A {@link CityDoctorModel} object with the parsed Features * <p/>
* @throws CityGmlParseException if parsing of the CityGML-file failed * Features are held in a non-persistent, embedded Database with an in-memory
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * upstream cache. Thus, strong references to {@link CityObject} are to be
*/ * avoided, to allow for the memory management of the cache.
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l, *
GMLValidationHandler handler) throws CityGmlParseException, InvalidGmlFileException { * @param filePath String path to the file
return parseCityGmlFile(filePath, config, l, handler, true, false); * @param config Configuration of the parser
} * @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
/** * @return A {@link CityDoctorModel} object with the parsed Features
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}. * @throws CityGmlParseException if parsing of the CityGML-file failed
* <p/> * @throws InvalidGmlFileException if the GMLValidationHandler found an error
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references */
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
* GMLValidationHandler handler) throws CityGmlParseException, InvalidGmlFileException {
* @param filePath String path to the file return parseCityGmlFile(filePath, config, l, handler, true, false);
* @param config Configuration of the parser }
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation /**
* @param verbose If verbosity is enabled the parser will write info logs to the logger * Parses a given CityGML file and collects the contained CityObject Features in
* @param unconnected If true the parser will use an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache} * a {@link CityDoctorModel}.
* to collect the parsed CityObjects * <p/>
* @return A {@link CityDoctorModel} object with the parsed Features * Features are held in a non-persistent, embedded Database with an in-memory
* @throws CityGmlParseException if parsing of the CityGML-file failed * upstream cache. Thus, strong references to {@link CityObject} are to be
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * avoided, to allow for the memory management of the cache.
*/ *
private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l, * @param filePath String path to the file
GMLValidationHandler handler, boolean verbose, boolean unconnected) * @param config Configuration of the parser
throws CityGmlParseException, InvalidGmlFileException { * @param l Listener hook for tracking of parsing progress
CityGMLContext context = getContext(); * @param handler Validation handler for GML-file validation
Path file = Paths.get(filePath); * @param verbose If verbosity is enabled the parser will write info logs to
if (config.getValidate()) { * the logger
List<String> messages = validateFile(context, handler, file); * @param unconnected If true the parser will use an
if (!messages.isEmpty()) { * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
throw new InvalidGmlFileException("Invalid GML File. First error: \n" + messages.get(0)); * UnconnectedCache} to collect the parsed CityObjects
} * @return A {@link CityDoctorModel} object with the parsed Features
} * @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
try { */
parseEpsgCodeFromFile(file, config, verbose); private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
CityGMLInputFactory in = context.createCityGMLInputFactory() GMLValidationHandler handler, boolean verbose, boolean unconnected)
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false)); throws CityGmlParseException, InvalidGmlFileException {
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) { CityGMLContext context = setupContext();
if (l != null) { Path file = Paths.get(filePath);
ois.addListener(l::updateProgress); List<String> validationIssues = Collections.emptyList();
} if (config.getValidate()) {
return readAndKeepFeatures(config, file, in, ois, verbose, unconnected); validationIssues = validateFile(handler, file);
} }
} catch (CityGMLReadException | IOException e) { try {
throw new CityGmlParseException("Failed to read CityGML file", e); parseEpsgCodeFromFile(file, config, verbose);
} CityGMLInputFactory in = context.createCityGMLInputFactory()
} .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
if (l != null) {
ois.addListener(l::updateProgress);
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer, }
String outputFile) throws CityGmlParseException { CityDoctorModel model = readAndKeepFeatures(config, file, in, ois, verbose, unconnected);
Path f = Paths.get(file); for (String xmlError : validationIssues) {
streamCityGml(f, config, null, cityObjectConsumer, outputFile); model.addGlobalError(new XMLValidationError(xmlError));
} }
return model;
public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer, }
String outputFile) throws CityGmlParseException { } catch (CityGMLReadException | IOException e) {
streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile); throw new CityGmlParseException("Failed to read CityGML file", e);
} }
}
public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException { public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
parseEpsgCodeFromFile(file, config, true); String outputFile) throws CityGmlParseException {
startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile); Path f = Paths.get(file);
} streamCityGml(f, config, null, cityObjectConsumer, outputFile);
}
public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
try { public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer,
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory() String outputFile) throws CityGmlParseException {
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false)); streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile);
try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) { }
while (reader.hasNext()) {
AbstractFeature chunk = reader.next(); public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
if (chunk instanceof CityModel cModel) { CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
cModel.setCityObjectMembers(null); parseEpsgCodeFromFile(file, config, true);
return cModel; startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile);
} }
}
} public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
} catch (CityGMLReadException e) { try {
throw new CityGmlParseException(e); CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
} .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
throw new CityGmlParseException("Did not find any CityModel in CityGML file"); try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) {
} while (reader.hasNext()) {
AbstractFeature chunk = reader.next();
private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l, if (chunk instanceof CityModel cModel) {
CityGmlConsumer cityObjectConsumer, String outputFile) { cModel.setCityObjectMembers(null);
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) { return cModel;
if (l != null) { }
ois.addListener(l::updateProgress); }
} }
readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile); } catch (CityGMLReadException e) {
} catch (IOException | CityGMLReadException e) { throw new CityGmlParseException(e);
logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage()); }
logger.catching(Level.ERROR, e); throw new CityGmlParseException("Did not find any CityModel in CityGML file");
} }
}
private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l,
private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois, CityGmlConsumer cityObjectConsumer, String outputFile) {
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false); if (l != null) {
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile); ois.addListener(l::updateProgress);
}
} readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile);
} catch (IOException | CityGMLReadException e) {
private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois, logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { logger.catching(Level.ERROR, e);
getContext(); }
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory() }
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
CityGMLChunkWriter writer = null; private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) { CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
CityDoctorModel model = mapper.getModel(); readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile, config, file);
boolean isInitialized = false;
while (reader.hasNext()) { }
AbstractFeature chunk = reader.next();
if (writer == null) { private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
writer = createCityModelWriter(outputFile, reader); CityGmlConsumer cityObjectConsumer, String outputFile, ParserConfiguration config, Path file)
} throws CityGMLReadException {
if (!isInitialized && writer != null && reader.getParentInfo() != null setupContext();
&& reader.getParentInfo().getTypeName().getLocalPart().equals("CityModel")) { CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
FeatureInfo parentInfo = reader.getParentInfo(); .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
writer.withCityModelInfo(parentInfo); CityGMLChunkWriter writer = null;
isInitialized = true; try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
} CityDoctorModel model = mapper.getModel();
if (chunk instanceof AbstractCityObject ag) { boolean isInitialized = false;
ag.accept(mapper); while (reader.hasNext()) {
drainCityModel(model, cityObjectConsumer); AbstractFeature chunk = reader.next();
writeAbstractCityObject(writer, ag); if (writer == null) {
} else if (chunk instanceof CityModel cModel) { writer = createCityModelWriter(outputFile, reader);
cModel.setCityObjectMembers(null); }
mapper.setCityModel(cModel); if (!isInitialized && writer != null && reader.getParentInfo() != null
cityObjectConsumer.accept(cModel); && reader.getParentInfo().getTypeName().getLocalPart().equals("CityModel")) {
writeCityModel(writer, cModel); FeatureInfo parentInfo = reader.getParentInfo();
} else if (writer != null) { writer.withCityModelInfo(parentInfo);
writer.writeMember(chunk); isInitialized = true;
} }
} if (chunk instanceof AbstractCityObject ag) {
ag.accept(mapper);
// end of stream drainCityModel(model, cityObjectConsumer);
logger.debug("End of gml file stream"); writeAbstractCityObject(writer, ag);
} catch (CityGMLReadException e) { } else if (chunk instanceof CityModel cModel) {
logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e); cModel.setCityObjectMembers(null);
} catch (CityGMLWriteException e) { mapper.setCityModel(cModel);
logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e); cityObjectConsumer.accept(cModel);
} finally { writeCityModel(writer, cModel);
if (writer != null) { } else if (writer != null) {
try { writer.writeMember(chunk);
writer.close(); }
} catch (CityGMLWriteException e) { }
// ignore
} // end of stream
} logger.debug("End of gml file stream");
} } catch (CityGMLReadException e) {
} logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e);
} catch (CityGMLWriteException e) {
private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException { logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e);
if (writer != null) { } finally {
for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) { if (writer != null) {
writer.writeMember(featureProp.getObject()); try {
} writer.close();
} } catch (CityGMLWriteException e) {
} // ignore
}
private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag) }
throws CityGMLWriteException { }
if (writer != null) { }
writer.writeMember(ag);
} private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException {
} if (writer != null) {
for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) {
private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader) writer.writeMember(featureProp.getObject());
throws CityGMLWriteException { }
if (outputFile == null) { }
return null; }
}
CityGMLContext gmlContext = CityGmlParser.getContext(); private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag)
CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI()); throws CityGMLWriteException {
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version); if (writer != null) {
CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile), writer.writeMember(ag);
StandardCharsets.UTF_8.name()); }
writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI); }
writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd");
writer.withIndent(" "); private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader)
writer.withDefaultPrefixes(); throws CityGMLWriteException {
writer.withDefaultSchemaLocations(); if (outputFile == null) {
return writer; return null;
} }
CityGMLContext gmlContext = CityGmlParser.setupContext();
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file, CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
CityGMLInputFactory inputFactory, ObservedInputStream ois, CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
boolean verbose, boolean unconnected) throws CityGMLReadException { CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile),
return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose); StandardCharsets.UTF_8.name());
} writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI);
writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd");
private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory, writer.withIndent(" ");
ObservedInputStream ois, boolean verbose) throws CityGMLReadException { writer.withDefaultPrefixes();
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) { writer.withDefaultSchemaLocations();
CityGMLVersion version = null; return writer;
// model is read in chunked mode }
// object members are replaced by href in model
// need to remove the refs and re-add unparsed objects private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
List<AbstractCityObject> acos = new ArrayList<>(); CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose, boolean unconnected)
CityModel cgmlModel = null; throws CityGMLReadException {
CityDoctorModel cdModel = mapper.getModel(); return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
AutoCloseable parsingContext = cdModel.getCache().startParsingMode(); }
try (parsingContext){
List<AbstractCityObject> queuedObjects = new ArrayList<>(); private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory,
while (reader.hasNext()) { ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
AbstractFeature chunk = reader.next(); try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI()); CityGMLVersion version = null;
if (chunk instanceof CityModel cModel) { // model is read in chunked mode
cModel.setCityObjectMembers(null); // object members are replaced by href in model
mapper.setCityModel(cModel); // need to remove the refs and re-add unparsed objects
mapper.setCityGMLVersion(version); List<AbstractCityObject> acos = new ArrayList<>();
cgmlModel = cModel; CityModel cgmlModel = null;
CityDoctorModel cdModel = mapper.getModel();
if (!queuedObjects.isEmpty()) { AutoCloseable parsingContext = cdModel.getCache().startParsingMode();
for (AbstractCityObject aco : queuedObjects) { try (parsingContext) {
if (!cdModel.containsGmlId(aco.getId())) { List<AbstractCityObject> queuedObjects = new ArrayList<>();
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); while (reader.hasNext()) {
} AbstractFeature chunk = reader.next();
} version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
} if (chunk instanceof CityModel cModel) {
} else if (chunk instanceof AbstractCityObject aco) { cModel.setCityObjectMembers(null);
acos.add(aco); // getid mapper.setCityModel(cModel);
String id = aco.getId(); mapper.setCityGMLVersion(version);
aco.accept(mapper); cgmlModel = cModel;
if (cgmlModel == null) {
// Add object to queue till the CityModel has been parsed if (!queuedObjects.isEmpty()) {
queuedObjects.add(aco); for (AbstractCityObject aco : queuedObjects) {
}else if (!cdModel.containsGmlId(id)){ if (!cdModel.containsGmlId(aco.getId())) {
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
} }
} }
} }
} else if (chunk instanceof AbstractCityObject aco) {
if (mapper.getModel().getCityModel() == null) { acos.add(aco); // getid
// file does not contain a city model? String id = aco.getId();
// create it for now aco.accept(mapper);
logger.warn(Localization.getText("CityGmlParser.missingCityModel")); if (cgmlModel == null) {
mapper.setCityModel(new CityModel()); // Add object to queue till the CityModel has been parsed
for (AbstractCityObject aco : queuedObjects) { queuedObjects.add(aco);
if (!cdModel.containsGmlId(aco.getId())) { } else if (!cdModel.containsGmlId(id)) {
mapper.getModel().getCityModel().getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
} }
} }
} }
ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache()); if (mapper.getModel().getCityModel() == null) {
if (logger.isInfoEnabled() && verbose) { // file does not contain a city model?
logger.info(Localization.getText("CityGmlParser.parsedObjects"), // create it for now
mapper.getModel().getNumberOfFeatures()); logger.warn(Localization.getText("CityGmlParser.missingCityModel"));
} mapper.setCityModel(new CityModel());
mapper.setCityGMLVersion(version); for (AbstractCityObject aco : queuedObjects) {
return mapper.getModel(); if (!cdModel.containsGmlId(aco.getId())) {
} catch (Exception e) { mapper.getModel().getCityModel().getCityObjectMembers()
throw new CityGMLReadException(e); .add(new AbstractCityObjectProperty(aco));
} }
}
} }
}
ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache());
private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose) throws CityGmlParseException { if (logger.isInfoEnabled() && verbose) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) { logger.info(Localization.getText("CityGmlParser.parsedObjects"),
parseEpsgCodeFromStream(bis, config, verbose); mapper.getModel().getNumberOfFeatures());
} catch (ParserConfigurationException | SAXException | IOException e) { }
throw new CityGmlParseException("Failed to read CityGML file", e); mapper.checkUnresolvedReferences();
} mapper.setCityGMLVersion(version);
} return mapper.getModel();
} catch (Exception e) {
private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose) throw new CityGMLReadException(e);
throws ParserConfigurationException, SAXException { }
SAXParser parser = FACTORY.newSAXParser();
CityGmlHandler handler = new CityGmlHandler(); }
try { }
parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) { private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose)
try { throws CityGmlParseException {
parseCoordinateSystem(config, handler, verbose); try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
} catch (Exception e2) { parseEpsgCodeFromStream(bis, config, verbose);
logEpsgParseError(e2); } catch (ParserConfigurationException | SAXException | IOException e) {
} throw new CityGmlParseException("Failed to read CityGML file", e);
} catch (Exception e) { }
logEpsgParseError(e); }
}
if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) { private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose)
logger.info(Localization.getText("CityGmlParser.missingEPSGCode")); throws ParserConfigurationException, SAXException {
} SAXParser parser = FACTORY.newSAXParser();
CityGmlHandler handler = new CityGmlHandler();
} try {
parser.parse(new InputSource(is), handler);
private static void logEpsgParseError(Exception e) { } catch (EnvelopeFoundException e) {
logger.debug("Exception while parsing for EPSG code", e); try {
if (logger.isWarnEnabled()) { parseCoordinateSystem(config, handler, verbose);
logger.warn(Localization.getText("CityGmlParser.failedEPSGParse")); } catch (Exception e2) {
} logEpsgParseError(e2);
} }
} catch (Exception e) {
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) { logEpsgParseError(e);
if (handler.getEpsg() == null) { }
return; if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
} logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg()); }
if (crs == null) {
// could not find a coordinate system for srsName }
// assuming metric system
if (logger.isInfoEnabled() && verbose) { private static void logEpsgParseError(Exception e) {
logger.info(Localization.getText("CityGmlParser.missingEPSGCode")); logger.debug("Exception while parsing for EPSG code", e);
} if (logger.isWarnEnabled()) {
return; logger.warn(Localization.getText("CityGmlParser.failedEPSGParse"));
} }
if (crs.getProjection().getUnits() == Units.METRES) { }
// coordinate system is in meters, do not convert
if (logger.isInfoEnabled() && verbose) { private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
logger.info(Localization.getText("CityGmlParser.noConversionNeeded")); if (handler.getEpsg() == null) {
} return;
return; }
} CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
parseMeterConversion(config, crs); if (crs == null) {
Vector3d low = handler.getLowerCorner(); // could not find a coordinate system for srsName
Vector3d up = handler.getUpperCorner(); // assuming metric system
double centerLong = low.getX() + ((up.getX() - low.getX()) / 2); if (logger.isInfoEnabled() && verbose) {
double centerLat = low.getY() + ((up.getY() - low.getY()) / 2); logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
if (!crs.getName().equals(WGS_84)) { }
// need to convert coordinates first to WGS84, then find UTM Zone return;
CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84); }
ProjCoordinate p1 = new ProjCoordinate(); if (crs.getProjection().getUnits() == Units.METRES) {
p1.setValue(centerLong, centerLat); // coordinate system is in meters, do not convert
ProjCoordinate p2 = new ProjCoordinate(); if (logger.isInfoEnabled() && verbose) {
BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84); logger.info(Localization.getText("CityGmlParser.noConversionNeeded"));
bct.transform(p1, p2); }
centerLong = p2.x; return;
centerLat = p2.y; }
} parseMeterConversion(config, crs);
int zone = (int) (31 + Math.round(centerLong / 6)); Vector3d low = handler.getLowerCorner();
CoordinateReferenceSystem utm; Vector3d up = handler.getUpperCorner();
if (centerLat < 0) { double centerLong = low.getX() + ((up.getX() - low.getX()) / 2);
// south double centerLat = low.getY() + ((up.getY() - low.getY()) / 2);
if (logger.isInfoEnabled() && verbose) { if (!crs.getName().equals(WGS_84)) {
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone); // need to convert coordinates first to WGS84, then find UTM Zone
} CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south"); ProjCoordinate p1 = new ProjCoordinate();
} else { p1.setValue(centerLong, centerLat);
// north ProjCoordinate p2 = new ProjCoordinate();
if (logger.isInfoEnabled() && verbose) { BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone); bct.transform(p1, p2);
} centerLong = p2.x;
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone); centerLat = p2.y;
} }
config.setCoordinateSystem(crs, utm); int zone = (int) (31 + Math.round(centerLong / 6));
} CoordinateReferenceSystem utm;
if (centerLat < 0) {
private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) { // south
Projection projection = crs.getProjection(); if (logger.isInfoEnabled() && verbose) {
double fromMetres = projection.getFromMetres(); logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone);
if (fromMetres > 0) { }
// also transform height information utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
config.setFromMeters(fromMetres); } else {
} else { // north
config.setFromMeters(1.0); if (logger.isInfoEnabled() && verbose) {
} logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone);
} }
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
/** }
* The srsName (The name by which this reference system is identified) inside config.setCoordinateSystem(crs, utm);
* the CityGML file can have multiple formats. This method tries to parse the }
* string and detect the corresponding reference system. If it is found, it
* returns a proj4j.CoordinateReferenceSystem. It throws an private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
* IllegalArgumentException otherwise. Projection projection = crs.getProjection();
* <p> double fromMetres = projection.getFromMetres();
* This method should be able to parse any EPSG id : e.g. "EPSG:1234". German if (fromMetres > 0) {
* Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so // also transform height information
* those are also included. It isn't guaranteed that those formats are correctly config.setFromMeters(fromMetres);
* parsed, though. } else {
* <p> config.setFromMeters(1.0);
* The EPSG ids and parameters are defined in resources ('nad/epsg') inside }
* proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415 }
*
* @param srsName /**
* @return CoordinateReferenceSystem * The srsName (The name by which this reference system is identified) inside
*/ * the CityGML file can have multiple formats. This method tries to parse the
private static CoordinateReferenceSystem crsFromSrsName(String srsName) { * string and detect the corresponding reference system. If it is found, it
srsName = srsName.trim(); * returns a proj4j.CoordinateReferenceSystem. It throws an
Matcher mEPSG = P_EPSG.matcher(srsName); * IllegalArgumentException otherwise.
if (mEPSG.find()) { * <p>
if ("EPSG:4979".contentEquals(srsName)) { * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
srsName = "EPSG:4236"; * Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
} else if ("EPSG:7415".contentEquals(srsName)) { * those are also included. It isn't guaranteed that those formats are correctly
return CRS_FACTORY.createFromParameters("EPSG:7415", * parsed, though.
"+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"); * <p>
} * The EPSG ids and parameters are defined in resources ('nad/epsg') inside
return CRS_FACTORY.createFromName(srsName); * proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
} *
* @param srsName
Matcher mOGC = P_OGC.matcher(srsName); * @return CoordinateReferenceSystem
if (mOGC.find()) { */
return getCrsFromOGCSrsCode(mOGC.group(1)); private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
} srsName = srsName.trim();
Matcher mOGC2 = P_OGC2.matcher(srsName); Matcher mEPSG = P_EPSG.matcher(srsName);
if (mOGC2.find()) { if (mEPSG.find()) {
return getCrsFromOGCSrsCode(mOGC2.group(1)); if ("EPSG:4979".contentEquals(srsName)) {
srsName = "EPSG:4236";
} } else if ("EPSG:7415".contentEquals(srsName)) {
Matcher mURN = P_URN.matcher(srsName); return CRS_FACTORY.createFromParameters("EPSG:7415",
// NOTE: Could use a HashMap if the switch/case becomes too long. "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs");
if (mURN.find()) { }
return switch (mURN.group(1)) { return CRS_FACTORY.createFromName(srsName);
case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466"); }
case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468"); Matcher mOGC = P_OGC.matcher(srsName);
case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469"); if (mOGC.find()) {
case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832"); return getCrsFromOGCSrsCode(mOGC.group(1));
default -> null; }
}; Matcher mOGC2 = P_OGC2.matcher(srsName);
} if (mOGC2.find()) {
if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) { return getCrsFromOGCSrsCode(mOGC2.group(1));
return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
} }
return null; Matcher mURN = P_URN.matcher(srsName);
} // NOTE: Could use a HashMap if the switch/case becomes too long.
if (mURN.find()) {
private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) { return switch (mURN.group(1)) {
String crsName = "EPSG:" + srsCode; case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
List<String> params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList()); case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
params.removeIf(P_VUNITS.asPredicate()); case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
params.removeIf(P_GEOIDGRID.asPredicate()); case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0])); case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
} default -> null;
};
private static List<String> validateFile(CityGMLContext context, GMLValidationHandler handler, Path file) }
throws CityGmlParseException { if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) {
if (handler == null) { return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
handler = new GMLValidationHandler(); }
} return null;
try { }
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler); private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) {
Source[] schemas = schemaHandler.getSchemas(); String crsName = "EPSG:" + srsCode;
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); List<String> params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList());
schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); params.removeIf(P_VUNITS.asPredicate());
Schema schema = schemaFactory.newSchema(schemas); params.removeIf(P_GEOIDGRID.asPredicate());
Validator validator = schema.newValidator(); return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
validator.setErrorHandler(handler); }
validator.validate(new StreamSource(file.toFile()));
return handler.getMessages(); public static List<String> validateFile(GMLValidationHandler handler, Path file)
} catch (SchemaHandlerException | SAXException | IOException e) { throws CityGmlParseException {
throw new CityGmlParseException("Failed to validate CityGML file", e); if (handler == null) {
} handler = new GMLValidationHandler();
} }
setupContext();
private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler) try {
throws CityGmlParseException { SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()) readAdditionalSchemaDefinitions(context, file, schemaHandler);
.withSchemaHandler(schemaHandler) Source[] schemas = schemaHandler.getSchemas();
.createReader(file)) { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
reader.nextTag(); schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (Exception e) { Schema schema = schemaFactory.newSchema(schemas);
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e); Validator validator = schema.newValidator();
} validator.setErrorHandler(handler);
} validator.validate(new StreamSource(file.toFile()));
return handler.getMessages();
private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) { } catch (SchemaHandlerException | SAXException | IOException e) {
List<GmlId> id = new ArrayList<>(); throw new CityGmlParseException("Failed to validate CityGML file", e);
model.createFeatureStream().forEach(co -> { }
cityObjectConsumer.accept(co); }
id.add(co.getGmlId());
}); private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
id.forEach(model::removeFeature); throws CityGmlParseException {
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()).withSchemaHandler(schemaHandler)
} .createReader(file)) {
reader.nextTag();
} catch (Exception e) {
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
}
}
private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
List<GmlId> id = new ArrayList<>();
model.createFeatureStream().forEach(co -> {
cityObjectConsumer.accept(co);
id.add(co.getGmlId());
});
id.forEach(model::removeFeature);
}
} }
...@@ -41,175 +41,174 @@ import java.util.Set; ...@@ -41,175 +41,174 @@ import java.util.Set;
*/ */
public final class CityGmlUtils { public final class CityGmlUtils {
private CityGmlUtils() {
private CityGmlUtils() { // util class
// util class }
}
public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory, Polygon cdPoly, ParserConfiguration config) {
Polygon cdPoly, ParserConfiguration config) { if (cdPoly.getExteriorRing() == null) {
if (cdPoly.getExteriorRing() == null) { return null;
return null; }
} org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon(); // exterior ring
// exterior ring LinearRing extLr = cdPoly.getExteriorRing();
LinearRing extLr = cdPoly.getExteriorRing(); if (extLr.getVertices().size() < 3) {
if (extLr.getVertices().size() < 3) { // this ring does not have enough points in it
// this ring does not have enough points in it // this leads to errors when exporting therefore ignore it
// this leads to errors when exporting therefore ignore it return null;
return null; }
} org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr); gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
// interior rings
// interior rings for (LinearRing lr : cdPoly.getInnerRings()) {
for (LinearRing lr : cdPoly.getInnerRings()) { gmlLr = createGmlRing(factory, config, lr);
gmlLr = createGmlRing(factory, config, lr); if (lr.getVertices().size() < 3) {
if (lr.getVertices().size() < 3) { // this ring does not have enough points in it
// this ring does not have enough points in it // this leads to errors when exporting therefore ignore it
// this leads to errors when exporting therefore ignore it return null;
return null; }
} gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr)); }
} gmlPoly.setId(cdPoly.getGmlId().getGmlString());
gmlPoly.setId(cdPoly.getGmlId().getGmlString()); return gmlPoly;
return gmlPoly; }
}
public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp,
public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp, ParserConfiguration config) {
ParserConfiguration config) { List<SurfaceProperty> surfaces = new ArrayList<>();
List<SurfaceProperty> surfaces = new ArrayList<>(); for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) {
for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) { surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config)));
surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config))); }
} for (CompositeCollection cc : comp.getChildComposites()) {
for (CompositeCollection cc : comp.getChildComposites()) { surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config)));
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config))); }
} if (surfaces.isEmpty()) {
if (surfaces.isEmpty()) { return null;
return null; }
} return new CompositeSurface(surfaces);
return new CompositeSurface(surfaces); }
}
public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
ParserConfiguration config, LinearRing lr) {
public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
ParserConfiguration config, LinearRing lr) { ProjCoordinate p1 = new ProjCoordinate();
ProjCoordinate p2 = new ProjCoordinate();
ProjCoordinate p1 = new ProjCoordinate(); List<Double> ringValues = new ArrayList<>();
ProjCoordinate p2 = new ProjCoordinate(); BasicCoordinateTransform trans = config.getOriginalTransform();
List<Double> ringValues = new ArrayList<>(); for (Vertex v : lr.getVertices()) {
BasicCoordinateTransform trans = config.getOriginalTransform(); double x = v.getX();
for (Vertex v : lr.getVertices()) { double y = v.getY();
double x = v.getX(); double z = v.getZ();
double y = v.getY(); if (trans != null) {
double z = v.getZ(); p1.x = x;
if (trans != null) { p1.y = y;
p1.x = x; trans.transform(p1, p2);
p1.y = y; x = p2.x;
trans.transform(p1, p2); y = p2.y;
x = p2.x; z = z * config.getFromMeters();
y = p2.y; }
z = z * config.getFromMeters(); ringValues.add(x);
} ringValues.add(y);
ringValues.add(x); ringValues.add(z);
ringValues.add(y); }
ringValues.add(z);
} org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
gmlLr.setId(lr.getGmlId().getGmlString());
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3); return gmlLr;
gmlLr.setId(lr.getGmlId().getGmlString()); }
return gmlLr;
} public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
if (geom.getType() != GeometryType.SOLID) {
public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) { throw new IllegalArgumentException("Only solids are allowed");
if (geom.getType() != GeometryType.SOLID) { }
throw new IllegalArgumentException("Only solids are allowed");
} CompositeSurface comp = new CompositeSurface();
List<SurfaceProperty> surfaceMember = comp.getSurfaceMembers();
CompositeSurface comp = new CompositeSurface(); for (Polygon cdPoly : geom.getPolygons()) {
List<SurfaceProperty> surfaceMember = comp.getSurfaceMembers(); if (!cdPoly.isLink()) {
for (Polygon cdPoly : geom.getPolygons()) { org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
if (!cdPoly.isLink()) { config);
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config); if (gmlPoly != null) {
if (gmlPoly != null) { surfaceMember.add(new SurfaceProperty(gmlPoly));
surfaceMember.add(new SurfaceProperty(gmlPoly)); }
} } else {
} else { // add reference to polygon
// add reference to polygon surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString())); }
} }
}
if (surfaceMember.isEmpty()) {
if (surfaceMember.isEmpty()) { return null;
return null; }
} Solid solid = new Solid();
Solid solid = new Solid(); Shell shell = new Shell(surfaceMember);
Shell shell = new Shell(surfaceMember); solid.setExterior(new ShellProperty(shell));
solid.setExterior(new ShellProperty(shell)); solid.setId(geom.getGmlId().getGmlString());
solid.setId(geom.getGmlId().getGmlString()); return solid;
return solid; }
}
public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory, if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) {
ParserConfiguration config) { throw new IllegalArgumentException("This can only handle MultiSurfaces");
if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) { }
throw new IllegalArgumentException("This can only handle MultiSurfaces"); List<SurfaceProperty> surfaces = new ArrayList<>();
} Set<CompositeCollection> compositeCollections = new HashSet<>();
List<SurfaceProperty> surfaces = new ArrayList<>(); for (Polygon cdPoly : geom.getPolygons()) {
Set<CompositeCollection> compositeCollections = new HashSet<>(); if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) {
for (Polygon cdPoly : geom.getPolygons()) { compositeCollections.add(conc.getPartOfComposite());
if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) { } else if (!cdPoly.isLink()) {
compositeCollections.add(conc.getPartOfComposite()); // is not part of a boundary surface
} else if (!cdPoly.isLink()) { org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
// is not part of a boundary surface config);
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config); if (gmlPoly != null) {
if (gmlPoly != null) { surfaces.add(new SurfaceProperty(gmlPoly));
surfaces.add(new SurfaceProperty(gmlPoly)); }
} } else {
} else { // add reference to polygon
// add reference to polygon surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString())); }
} }
} for (CompositeCollection cdPoly : compositeCollections) {
for (CompositeCollection cdPoly : compositeCollections) { surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config)));
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config))); }
} if (surfaces.isEmpty()) {
if (surfaces.isEmpty()) { return null;
return null; }
} MultiSurface ms = new MultiSurface(surfaces);
MultiSurface ms = new MultiSurface(surfaces); ms.setId(geom.getGmlId().getGmlString());
ms.setId(geom.getGmlId().getGmlString()); return ms;
return ms; }
}
public static MultiSurface createMultiSurface(List<Polygon> polygons, GeometryFactory factory,
public static MultiSurface createMultiSurface(List<Polygon> polygons, GeometryFactory factory, ParserConfiguration config) {
ParserConfiguration config) { List<SurfaceProperty> surfaces = new ArrayList<>();
List<SurfaceProperty> surfaces = new ArrayList<>(); for (Polygon cdPoly : polygons) {
for (Polygon cdPoly : polygons) { org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config); if (gmlPoly != null) {
if (gmlPoly != null) { surfaces.add(new SurfaceProperty(gmlPoly));
surfaces.add(new SurfaceProperty(gmlPoly)); }
} }
} if (surfaces.isEmpty()) {
if (surfaces.isEmpty()) { return null;
return null; }
} return new MultiSurface(surfaces);
return new MultiSurface(surfaces); }
}
public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
ParserConfiguration config) { List<SurfaceProperty> surfaces = new ArrayList<>();
List<SurfaceProperty> surfaces = new ArrayList<>(); for (Polygon cdPoly : geom.getPolygons()) {
for (Polygon cdPoly : geom.getPolygons()) { org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config); if (gmlPoly != null) {
if (gmlPoly != null) { surfaces.add(new SurfaceProperty(gmlPoly));
surfaces.add(new SurfaceProperty(gmlPoly)); }
} }
} if (surfaces.isEmpty()) {
if (surfaces.isEmpty()) { return null;
return null; }
} return new CompositeSurface(surfaces);
return new CompositeSurface(surfaces); }
}
} }
...@@ -6,7 +6,7 @@ import de.hft.stuttgart.citydoctor2.check.Checkable; ...@@ -6,7 +6,7 @@ import de.hft.stuttgart.citydoctor2.check.Checkable;
/** /**
* This Visitor removes the check results from a CityObject and the objects in its datastructure. * This Visitor removes the check results from a CityObject and the objects in its datastructure.
*/ */
public class ClearVisitorResultsVisitor extends CheckableUtilsVisitor { public class ClearResultsVisitor extends CheckableUtilsVisitor {
@Override @Override
public void check(Checkable checkable) { public void check(Checkable checkable) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment