Commit 1fbe16e1 authored by Matthias Betz's avatar Matthias Betz
Browse files

add xml validation to streaming processing

parent 82888d4b
Pipeline #12286 passed with stage
in 2 minutes and 15 seconds
...@@ -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;
......
...@@ -32,6 +32,5 @@ public interface CityGmlConsumer { ...@@ -32,6 +32,5 @@ public interface CityGmlConsumer {
} }
// public void endOfFile();
} }
...@@ -102,618 +102,629 @@ import de.hft.stuttgart.quality.QualityADEModule; ...@@ -102,618 +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
List<String> validationIssues = Collections.emptyList(); * the logger
if (config.getValidate()) { * @param unconnected If true the parser will use an
validationIssues = validateFile(context, handler, file); * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
} * UnconnectedCache} to collect the parsed CityObjects
* @return A {@link CityDoctorModel} object with the parsed Features
try { * @throws CityGmlParseException if parsing of the CityGML-file failed
parseEpsgCodeFromFile(file, config, verbose); * @throws InvalidGmlFileException if the GMLValidationHandler found an error
CityGMLInputFactory in = context.createCityGMLInputFactory() */
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false)); private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) { GMLValidationHandler handler, boolean verbose, boolean unconnected)
if (l != null) { throws CityGmlParseException, InvalidGmlFileException {
ois.addListener(l::updateProgress); CityGMLContext context = setupContext();
} Path file = Paths.get(filePath);
CityDoctorModel model = readAndKeepFeatures(config, file, in, ois, verbose, unconnected); List<String> validationIssues = Collections.emptyList();
for (String xmlError : validationIssues) { if (config.getValidate()) {
model.addGlobalError(new XMLValidationError(xmlError)); validationIssues = validateFile(handler, file);
} }
return model; try {
} parseEpsgCodeFromFile(file, config, verbose);
} catch (CityGMLReadException | IOException e) { CityGMLInputFactory in = context.createCityGMLInputFactory()
throw new CityGmlParseException("Failed to read CityGML file", e); .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
} try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
} if (l != null) {
ois.addListener(l::updateProgress);
}
CityDoctorModel model = readAndKeepFeatures(config, file, in, ois, verbose, unconnected);
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer, for (String xmlError : validationIssues) {
String outputFile) throws CityGmlParseException { model.addGlobalError(new XMLValidationError(xmlError));
Path f = Paths.get(file); }
streamCityGml(f, config, null, cityObjectConsumer, outputFile); return model;
} }
} catch (CityGMLReadException | IOException e) {
public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer, throw new CityGmlParseException("Failed to read CityGML file", e);
String outputFile) throws CityGmlParseException { }
streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile); }
}
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l, String outputFile) throws CityGmlParseException {
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException { Path f = Paths.get(file);
parseEpsgCodeFromFile(file, config, true); streamCityGml(f, config, null, cityObjectConsumer, outputFile);
startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile); }
}
public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer,
public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException { String outputFile) throws CityGmlParseException {
try { streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile);
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory() }
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) { public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
while (reader.hasNext()) { CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
AbstractFeature chunk = reader.next(); parseEpsgCodeFromFile(file, config, true);
if (chunk instanceof CityModel cModel) { startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile);
cModel.setCityObjectMembers(null); }
return cModel;
} public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
} try {
} CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
} catch (CityGMLReadException e) { .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
throw new CityGmlParseException(e); try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) {
} while (reader.hasNext()) {
throw new CityGmlParseException("Did not find any CityModel in CityGML file"); AbstractFeature chunk = reader.next();
} if (chunk instanceof CityModel cModel) {
cModel.setCityObjectMembers(null);
private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l, return cModel;
CityGmlConsumer cityObjectConsumer, String outputFile) { }
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) { }
if (l != null) { }
ois.addListener(l::updateProgress); } catch (CityGMLReadException e) {
} throw new CityGmlParseException(e);
readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile); }
} catch (IOException | CityGMLReadException e) { throw new CityGmlParseException("Did not find any CityModel in CityGML file");
logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage()); }
logger.catching(Level.ERROR, e);
} private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l,
} CityGmlConsumer cityObjectConsumer, String outputFile) {
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois, if (l != null) {
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { ois.addListener(l::updateProgress);
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false); }
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile); readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile);
} catch (IOException | CityGMLReadException e) {
} logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
logger.catching(Level.ERROR, e);
private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois, }
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { }
getContext();
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory() private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false)); CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
CityGMLChunkWriter writer = null; Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) { readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile, config, file);
CityDoctorModel model = mapper.getModel(); }
boolean isInitialized = false;
while (reader.hasNext()) { private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
AbstractFeature chunk = reader.next(); CityGmlConsumer cityObjectConsumer, String outputFile, ParserConfiguration config, Path file)
if (writer == null) { throws CityGMLReadException {
writer = createCityModelWriter(outputFile, reader); setupContext();
} CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
if (!isInitialized && writer != null && reader.getParentInfo() != null .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
&& reader.getParentInfo().getTypeName().getLocalPart().equals("CityModel")) { CityGMLChunkWriter writer = null;
FeatureInfo parentInfo = reader.getParentInfo(); try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
writer.withCityModelInfo(parentInfo); CityDoctorModel model = mapper.getModel();
isInitialized = true; boolean isInitialized = false;
} while (reader.hasNext()) {
if (chunk instanceof AbstractCityObject ag) { AbstractFeature chunk = reader.next();
ag.accept(mapper); if (writer == null) {
drainCityModel(model, cityObjectConsumer); writer = createCityModelWriter(outputFile, reader);
writeAbstractCityObject(writer, ag); }
} else if (chunk instanceof CityModel cModel) { if (!isInitialized && writer != null && reader.getParentInfo() != null
cModel.setCityObjectMembers(null); && reader.getParentInfo().getTypeName().getLocalPart().equals("CityModel")) {
mapper.setCityModel(cModel); FeatureInfo parentInfo = reader.getParentInfo();
cityObjectConsumer.accept(cModel); writer.withCityModelInfo(parentInfo);
writeCityModel(writer, cModel); isInitialized = true;
} else if (writer != null) { }
writer.writeMember(chunk); if (chunk instanceof AbstractCityObject ag) {
} ag.accept(mapper);
} drainCityModel(model, cityObjectConsumer);
writeAbstractCityObject(writer, ag);
// end of stream } else if (chunk instanceof CityModel cModel) {
logger.debug("End of gml file stream"); cModel.setCityObjectMembers(null);
} catch (CityGMLReadException e) { mapper.setCityModel(cModel);
logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e); cityObjectConsumer.accept(cModel);
} catch (CityGMLWriteException e) { writeCityModel(writer, cModel);
logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e); } else if (writer != null) {
} finally { writer.writeMember(chunk);
if (writer != null) { }
try { }
writer.close();
} catch (CityGMLWriteException e) { // end of stream
// ignore logger.debug("End of gml file stream");
} } catch (CityGMLReadException e) {
} logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e);
} } catch (CityGMLWriteException e) {
} logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e);
} finally {
private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException { if (writer != null) {
if (writer != null) { try {
for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) { writer.close();
writer.writeMember(featureProp.getObject()); } catch (CityGMLWriteException e) {
} // ignore
} }
} }
}
private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag) }
throws CityGMLWriteException {
if (writer != null) { private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException {
writer.writeMember(ag); if (writer != null) {
} for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) {
} writer.writeMember(featureProp.getObject());
}
private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader) }
throws CityGMLWriteException { }
if (outputFile == null) {
return null; private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag)
} throws CityGMLWriteException {
CityGMLContext gmlContext = CityGmlParser.getContext(); if (writer != null) {
CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI()); writer.writeMember(ag);
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version); }
CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile), }
StandardCharsets.UTF_8.name());
writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI); private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader)
writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd"); throws CityGMLWriteException {
writer.withIndent(" "); if (outputFile == null) {
writer.withDefaultPrefixes(); return null;
writer.withDefaultSchemaLocations(); }
return writer; CityGMLContext gmlContext = CityGmlParser.setupContext();
} CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file, CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile),
CityGMLInputFactory inputFactory, ObservedInputStream ois, StandardCharsets.UTF_8.name());
boolean verbose, boolean unconnected) throws CityGMLReadException { writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI);
return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose); writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd");
} writer.withIndent(" ");
writer.withDefaultPrefixes();
private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory, writer.withDefaultSchemaLocations();
ObservedInputStream ois, boolean verbose) throws CityGMLReadException { return writer;
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) { }
CityGMLVersion version = null;
// model is read in chunked mode private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
// object members are replaced by href in model CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose, boolean unconnected)
// need to remove the refs and re-add unparsed objects throws CityGMLReadException {
List<AbstractCityObject> acos = new ArrayList<>(); return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
CityModel cgmlModel = null; }
CityDoctorModel cdModel = mapper.getModel();
AutoCloseable parsingContext = cdModel.getCache().startParsingMode(); private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory,
try (parsingContext){ ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
List<AbstractCityObject> queuedObjects = new ArrayList<>(); try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
while (reader.hasNext()) { CityGMLVersion version = null;
AbstractFeature chunk = reader.next(); // model is read in chunked mode
version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI()); // object members are replaced by href in model
if (chunk instanceof CityModel cModel) { // need to remove the refs and re-add unparsed objects
cModel.setCityObjectMembers(null); List<AbstractCityObject> acos = new ArrayList<>();
mapper.setCityModel(cModel); CityModel cgmlModel = null;
mapper.setCityGMLVersion(version); CityDoctorModel cdModel = mapper.getModel();
cgmlModel = cModel; AutoCloseable parsingContext = cdModel.getCache().startParsingMode();
try (parsingContext) {
if (!queuedObjects.isEmpty()) { List<AbstractCityObject> queuedObjects = new ArrayList<>();
for (AbstractCityObject aco : queuedObjects) { while (reader.hasNext()) {
if (!cdModel.containsGmlId(aco.getId())) { AbstractFeature chunk = reader.next();
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
} if (chunk instanceof CityModel cModel) {
} cModel.setCityObjectMembers(null);
} mapper.setCityModel(cModel);
} else if (chunk instanceof AbstractCityObject aco) { mapper.setCityGMLVersion(version);
acos.add(aco); // getid cgmlModel = cModel;
String id = aco.getId();
aco.accept(mapper); if (!queuedObjects.isEmpty()) {
if (cgmlModel == null) { for (AbstractCityObject aco : queuedObjects) {
// Add object to queue till the CityModel has been parsed if (!cdModel.containsGmlId(aco.getId())) {
queuedObjects.add(aco); cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
}else if (!cdModel.containsGmlId(id)){ }
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); }
} }
} } else if (chunk instanceof AbstractCityObject aco) {
} acos.add(aco); // getid
String id = aco.getId();
if (mapper.getModel().getCityModel() == null) { aco.accept(mapper);
// file does not contain a city model? if (cgmlModel == null) {
// create it for now // Add object to queue till the CityModel has been parsed
logger.warn(Localization.getText("CityGmlParser.missingCityModel")); queuedObjects.add(aco);
mapper.setCityModel(new CityModel()); } else if (!cdModel.containsGmlId(id)) {
for (AbstractCityObject aco : queuedObjects) { cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
if (!cdModel.containsGmlId(aco.getId())) { }
mapper.getModel().getCityModel().getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); }
} }
}
} if (mapper.getModel().getCityModel() == null) {
// file does not contain a city model?
ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache()); // create it for now
if (logger.isInfoEnabled() && verbose) { logger.warn(Localization.getText("CityGmlParser.missingCityModel"));
logger.info(Localization.getText("CityGmlParser.parsedObjects"), mapper.setCityModel(new CityModel());
mapper.getModel().getNumberOfFeatures()); for (AbstractCityObject aco : queuedObjects) {
} if (!cdModel.containsGmlId(aco.getId())) {
mapper.checkUnresolvedReferences(); mapper.getModel().getCityModel().getCityObjectMembers()
mapper.setCityGMLVersion(version); .add(new AbstractCityObjectProperty(aco));
return mapper.getModel(); }
} catch (Exception e) { }
throw new CityGMLReadException(e); }
}
ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache());
} if (logger.isInfoEnabled() && verbose) {
} logger.info(Localization.getText("CityGmlParser.parsedObjects"),
mapper.getModel().getNumberOfFeatures());
private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose) throws CityGmlParseException { }
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) { mapper.checkUnresolvedReferences();
parseEpsgCodeFromStream(bis, config, verbose); mapper.setCityGMLVersion(version);
} catch (ParserConfigurationException | SAXException | IOException e) { return mapper.getModel();
throw new CityGmlParseException("Failed to read CityGML file", e); } catch (Exception e) {
} throw new CityGMLReadException(e);
} }
private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose) }
throws ParserConfigurationException, SAXException { }
SAXParser parser = FACTORY.newSAXParser();
CityGmlHandler handler = new CityGmlHandler(); private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose)
try { throws CityGmlParseException {
parser.parse(new InputSource(is), handler); try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
} catch (EnvelopeFoundException e) { parseEpsgCodeFromStream(bis, config, verbose);
try { } catch (ParserConfigurationException | SAXException | IOException e) {
parseCoordinateSystem(config, handler, verbose); throw new CityGmlParseException("Failed to read CityGML file", e);
} catch (Exception e2) { }
logEpsgParseError(e2); }
}
} catch (Exception e) { private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose)
logEpsgParseError(e); throws ParserConfigurationException, SAXException {
} SAXParser parser = FACTORY.newSAXParser();
if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) { CityGmlHandler handler = new CityGmlHandler();
logger.info(Localization.getText("CityGmlParser.missingEPSGCode")); try {
} parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) {
} try {
parseCoordinateSystem(config, handler, verbose);
private static void logEpsgParseError(Exception e) { } catch (Exception e2) {
logger.debug("Exception while parsing for EPSG code", e); logEpsgParseError(e2);
if (logger.isWarnEnabled()) { }
logger.warn(Localization.getText("CityGmlParser.failedEPSGParse")); } catch (Exception e) {
} logEpsgParseError(e);
} }
if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) { logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
if (handler.getEpsg() == null) { }
return;
} }
CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
if (crs == null) { private static void logEpsgParseError(Exception e) {
// could not find a coordinate system for srsName logger.debug("Exception while parsing for EPSG code", e);
// assuming metric system if (logger.isWarnEnabled()) {
if (logger.isInfoEnabled() && verbose) { logger.warn(Localization.getText("CityGmlParser.failedEPSGParse"));
logger.info(Localization.getText("CityGmlParser.missingEPSGCode")); }
} }
return;
} private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
if (crs.getProjection().getUnits() == Units.METRES) { if (handler.getEpsg() == null) {
// coordinate system is in meters, do not convert return;
if (logger.isInfoEnabled() && verbose) { }
logger.info(Localization.getText("CityGmlParser.noConversionNeeded")); CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
} if (crs == null) {
return; // could not find a coordinate system for srsName
} // assuming metric system
parseMeterConversion(config, crs); if (logger.isInfoEnabled() && verbose) {
Vector3d low = handler.getLowerCorner(); logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
Vector3d up = handler.getUpperCorner(); }
double centerLong = low.getX() + ((up.getX() - low.getX()) / 2); return;
double centerLat = low.getY() + ((up.getY() - low.getY()) / 2); }
if (!crs.getName().equals(WGS_84)) { if (crs.getProjection().getUnits() == Units.METRES) {
// need to convert coordinates first to WGS84, then find UTM Zone // coordinate system is in meters, do not convert
CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84); if (logger.isInfoEnabled() && verbose) {
ProjCoordinate p1 = new ProjCoordinate(); logger.info(Localization.getText("CityGmlParser.noConversionNeeded"));
p1.setValue(centerLong, centerLat); }
ProjCoordinate p2 = new ProjCoordinate(); return;
BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84); }
bct.transform(p1, p2); parseMeterConversion(config, crs);
centerLong = p2.x; Vector3d low = handler.getLowerCorner();
centerLat = p2.y; Vector3d up = handler.getUpperCorner();
} double centerLong = low.getX() + ((up.getX() - low.getX()) / 2);
int zone = (int) (31 + Math.round(centerLong / 6)); double centerLat = low.getY() + ((up.getY() - low.getY()) / 2);
CoordinateReferenceSystem utm; if (!crs.getName().equals(WGS_84)) {
if (centerLat < 0) { // need to convert coordinates first to WGS84, then find UTM Zone
// south CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
if (logger.isInfoEnabled() && verbose) { ProjCoordinate p1 = new ProjCoordinate();
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone); p1.setValue(centerLong, centerLat);
} ProjCoordinate p2 = new ProjCoordinate();
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south"); BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
} else { bct.transform(p1, p2);
// north centerLong = p2.x;
if (logger.isInfoEnabled() && verbose) { centerLat = p2.y;
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone); }
} int zone = (int) (31 + Math.round(centerLong / 6));
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone); CoordinateReferenceSystem utm;
} if (centerLat < 0) {
config.setCoordinateSystem(crs, utm); // south
} if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone);
private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) { }
Projection projection = crs.getProjection(); utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
double fromMetres = projection.getFromMetres(); } else {
if (fromMetres > 0) { // north
// also transform height information if (logger.isInfoEnabled() && verbose) {
config.setFromMeters(fromMetres); logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone);
} else { }
config.setFromMeters(1.0); utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
} }
} config.setCoordinateSystem(crs, utm);
}
/**
* The srsName (The name by which this reference system is identified) inside private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
* the CityGML file can have multiple formats. This method tries to parse the Projection projection = crs.getProjection();
* string and detect the corresponding reference system. If it is found, it double fromMetres = projection.getFromMetres();
* returns a proj4j.CoordinateReferenceSystem. It throws an if (fromMetres > 0) {
* IllegalArgumentException otherwise. // also transform height information
* <p> config.setFromMeters(fromMetres);
* This method should be able to parse any EPSG id : e.g. "EPSG:1234". German } else {
* Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so config.setFromMeters(1.0);
* those are also included. It isn't guaranteed that those formats are correctly }
* parsed, though. }
* <p>
* 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 * 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
* @param srsName * string and detect the corresponding reference system. If it is found, it
* @return CoordinateReferenceSystem * returns a proj4j.CoordinateReferenceSystem. It throws an
*/ * IllegalArgumentException otherwise.
private static CoordinateReferenceSystem crsFromSrsName(String srsName) { * <p>
srsName = srsName.trim(); * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
Matcher mEPSG = P_EPSG.matcher(srsName); * Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
if (mEPSG.find()) { * those are also included. It isn't guaranteed that those formats are correctly
if ("EPSG:4979".contentEquals(srsName)) { * parsed, though.
srsName = "EPSG:4236"; * <p>
} else if ("EPSG:7415".contentEquals(srsName)) { * The EPSG ids and parameters are defined in resources ('nad/epsg') inside
return CRS_FACTORY.createFromParameters("EPSG:7415", * proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
"+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"); *
} * @param srsName
return CRS_FACTORY.createFromName(srsName); * @return CoordinateReferenceSystem
} */
private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
Matcher mOGC = P_OGC.matcher(srsName); srsName = srsName.trim();
if (mOGC.find()) { Matcher mEPSG = P_EPSG.matcher(srsName);
return getCrsFromOGCSrsCode(mOGC.group(1)); if (mEPSG.find()) {
} if ("EPSG:4979".contentEquals(srsName)) {
Matcher mOGC2 = P_OGC2.matcher(srsName); srsName = "EPSG:4236";
if (mOGC2.find()) { } else if ("EPSG:7415".contentEquals(srsName)) {
return getCrsFromOGCSrsCode(mOGC2.group(1)); return CRS_FACTORY.createFromParameters("EPSG:7415",
"+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");
} }
Matcher mURN = P_URN.matcher(srsName); return CRS_FACTORY.createFromName(srsName);
// NOTE: Could use a HashMap if the switch/case becomes too long. }
if (mURN.find()) {
return switch (mURN.group(1)) { Matcher mOGC = P_OGC.matcher(srsName);
case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466"); if (mOGC.find()) {
case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467"); return getCrsFromOGCSrsCode(mOGC.group(1));
case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468"); }
case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469"); Matcher mOGC2 = P_OGC2.matcher(srsName);
case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832"); if (mOGC2.find()) {
default -> null; return getCrsFromOGCSrsCode(mOGC2.group(1));
};
} }
if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) { Matcher mURN = P_URN.matcher(srsName);
return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu"); // NOTE: Could use a HashMap if the switch/case becomes too long.
} if (mURN.find()) {
return null; return switch (mURN.group(1)) {
} case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) { case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
String crsName = "EPSG:" + srsCode; case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
List<String> params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList()); case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
params.removeIf(P_VUNITS.asPredicate()); default -> null;
params.removeIf(P_GEOIDGRID.asPredicate()); };
return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0])); }
} if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) {
return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
private static List<String> validateFile(CityGMLContext context, GMLValidationHandler handler, Path file) }
throws CityGmlParseException { return null;
if (handler == null) { }
handler = new GMLValidationHandler();
} private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) {
try { String crsName = "EPSG:" + srsCode;
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler()); List<String> params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList());
readAdditionalSchemaDefinitions(context, file, schemaHandler); params.removeIf(P_VUNITS.asPredicate());
Source[] schemas = schemaHandler.getSchemas(); params.removeIf(P_GEOIDGRID.asPredicate());
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); }
Schema schema = schemaFactory.newSchema(schemas);
Validator validator = schema.newValidator(); public static List<String> validateFile(GMLValidationHandler handler, Path file)
validator.setErrorHandler(handler); throws CityGmlParseException {
validator.validate(new StreamSource(file.toFile())); if (handler == null) {
return handler.getMessages(); handler = new GMLValidationHandler();
} catch (SchemaHandlerException | SAXException | IOException e) { }
throw new CityGmlParseException("Failed to validate CityGML file", e); setupContext();
} try {
} SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler);
private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler) Source[] schemas = schemaHandler.getSchemas();
throws CityGmlParseException { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()) schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
.withSchemaHandler(schemaHandler) Schema schema = schemaFactory.newSchema(schemas);
.createReader(file)) { Validator validator = schema.newValidator();
reader.nextTag(); validator.setErrorHandler(handler);
} catch (Exception e) { validator.validate(new StreamSource(file.toFile()));
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e); return handler.getMessages();
} } catch (SchemaHandlerException | SAXException | IOException e) {
} throw new CityGmlParseException("Failed to validate CityGML file", e);
}
private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) { }
List<GmlId> id = new ArrayList<>();
model.createFeatureStream().forEach(co -> { private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
cityObjectConsumer.accept(co); throws CityGmlParseException {
id.add(co.getGmlId()); try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()).withSchemaHandler(schemaHandler)
}); .createReader(file)) {
id.forEach(model::removeFeature); 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);
}
} }
...@@ -65,7 +65,7 @@ public class CityGMLWriterUtils { ...@@ -65,7 +65,7 @@ public class CityGMLWriterUtils {
} }
public static void writeCityModel(String file, CityDoctorModel model) throws CityDoctorWriteException { public static void writeCityModel(String file, CityDoctorModel model) throws CityDoctorWriteException {
CityGMLContext gmlContext = CityGmlParser.getContext(); CityGMLContext gmlContext = CityGmlParser.setupContext();
CityModel cModel = model.getCityModel(); CityModel cModel = model.getCityModel();
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(model.getCityGMLVersion()); CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(model.getCityGMLVersion());
try (CityGMLWriter writer = factory.createCityGMLWriter(new File(file))) { try (CityGMLWriter writer = factory.createCityGMLWriter(new File(file))) {
......
...@@ -37,7 +37,7 @@ public class CityGml3ParserTest { ...@@ -37,7 +37,7 @@ public class CityGml3ParserTest {
@Test @Test
public void testGetContext() { public void testGetContext() {
CityGMLContext context = CityGmlParser.getContext(); CityGMLContext context = CityGmlParser.setupContext();
List<ADE> ades = ADERegistry.getInstance().getADEs(); List<ADE> ades = ADERegistry.getInstance().getADEs();
ADE ade = ades.get(0); ADE ade = ades.get(0);
assertEquals(QualityADEContext.class, ade.getClass()); assertEquals(QualityADEContext.class, ade.getClass());
......
...@@ -65,6 +65,7 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeInvalidError; ...@@ -65,6 +65,7 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeInvalidError;
import de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError; import de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError;
import de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError; import de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError; import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException; import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException;
import de.hft.stuttgart.citydoctor2.checks.CheckContainer; import de.hft.stuttgart.citydoctor2.checks.CheckContainer;
import de.hft.stuttgart.citydoctor2.checks.CheckPrototype; import de.hft.stuttgart.citydoctor2.checks.CheckPrototype;
...@@ -918,6 +919,20 @@ public class Checker { ...@@ -918,6 +919,20 @@ public class Checker {
// create reporter if available // create reporter if available
XmlStreamReporter xmlReporter = getXmlReporter(config, xmlBos, fileName); XmlStreamReporter xmlReporter = getXmlReporter(config, xmlBos, fileName);
PdfStreamReporter pdfReporter = getPdfReporter(config, pdfBos, fileName); PdfStreamReporter pdfReporter = getPdfReporter(config, pdfBos, fileName);
// XML validation
if (config.isUseStreaming() && (xmlReporter != null || pdfReporter != null)) {
List<String> validationIssues = CityGmlParser.validateFile(null, inputFile.toPath());
for (String xmlError : validationIssues) {
XMLValidationError validationError = new XMLValidationError(xmlError);
if (xmlReporter != null) {
xmlReporter.reportGlobalError(validationError);
}
if (pdfReporter != null) {
pdfReporter.reportGlobalError(validationError);
}
}
}
// execute schematron first // execute schematron first
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile); SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile);
......
...@@ -18,6 +18,22 @@ ...@@ -18,6 +18,22 @@
*/ */
package de.hft.stuttgart.citydoctor2.check; package de.hft.stuttgart.citydoctor2.check;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import de.hft.stuttgart.citydoctor2.CityDoctorValidation; import de.hft.stuttgart.citydoctor2.CityDoctorValidation;
import de.hft.stuttgart.citydoctor2.checkresult.CheckReport; import de.hft.stuttgart.citydoctor2.checkresult.CheckReport;
import de.hft.stuttgart.citydoctor2.checkresult.GlobalErrorStatistics; import de.hft.stuttgart.citydoctor2.checkresult.GlobalErrorStatistics;
...@@ -31,160 +47,165 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; ...@@ -31,160 +47,165 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/** /**
* @author Matthias Betz * @author Matthias Betz
*/ */
public class CheckerTest { public class CheckerTest {
@Rule @Rule
public TemporaryFolder folder = new TemporaryFolder(); public TemporaryFolder folder = new TemporaryFolder();
@Test @Test
public void testSchematron() throws CityGmlParseException, InvalidGmlFileException { public void testSchematron() throws CityGmlParseException, InvalidGmlFileException {
ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig(); ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig();
config.getRequirements().get(Requirement.R_SE_BS_ROOF_UNFRAGMENTED.toString()).setEnabled(false); config.getRequirements().get(Requirement.R_SE_BS_ROOF_UNFRAGMENTED.toString()).setEnabled(false);
config.setSchematronFilePathInGlobalParameters("src/test/resources/schematronTest.xml"); config.setSchematronFilePathInGlobalParameters("src/test/resources/schematronTest.xml");
CityDoctorModel model = CityGmlParser.parseCityGmlFile( CityDoctorModel model = CityGmlParser.parseCityGmlFile(
"src/test/resources/SimpleSolid_SrefBS_SchematronTest.gml", config.getParserConfiguration()); "src/test/resources/SimpleSolid_SrefBS_SchematronTest.gml", config.getParserConfiguration());
Checker checker = new Checker(config, model); Checker checker = new Checker(config, model);
checker.runChecks(); checker.runChecks();
for (Building b : model.getBuildings().toList()) { for (Building b : model.getBuildings().toList()) {
if (b.getGmlId().getGmlString().equals("_Simple_BD.1")) { if (b.getGmlId().getGmlString().equals("_Simple_BD.1")) {
assertTrue(b.containsAnyError()); assertTrue(b.containsAnyError());
} else { } else {
assertFalse(b.containsAnyError()); assertFalse(b.containsAnyError());
} }
} }
assertFalse(model.getGlobalErrors().isEmpty()); assertFalse(model.getGlobalErrors().isEmpty());
} }
@Test @Test
public void testChecker() throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException { public void testChecker()
throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException {
File f = folder.newFile();
File f2 = folder.newFile(); File f = folder.newFile();
try { File f2 = folder.newFile();
String[] args = new String[6]; try {
args[0] = "-in"; String[] args = new String[6];
args[1] = "src/test/resources/QA-CS-CONCOMP.gml"; args[0] = "-in";
args[2] = "-xmlReport"; args[1] = "src/test/resources/QA-CS-CONCOMP.gml";
args[3] = f.getAbsolutePath(); args[2] = "-xmlReport";
args[4] = "-pdfReport"; args[3] = f.getAbsolutePath();
args[5] = f2.getAbsolutePath(); args[4] = "-pdfReport";
CityDoctorValidation.main(args); args[5] = f2.getAbsolutePath();
assertTrue(f.exists()); CityDoctorValidation.main(args);
assertTrue(f2.exists()); assertTrue(f.exists());
} finally { assertTrue(f2.exists());
f.delete(); } finally {
f2.delete(); f.delete();
} f2.delete();
} }
}
@Test
public void testStreaming() throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException { @Test
File f = folder.newFile(); public void testStreaming()
File f2 = folder.newFile(); throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException {
File f3 = folder.newFile(); File f = folder.newFile();
try { File f2 = folder.newFile();
String[] args = new String[10]; File f3 = folder.newFile();
args[0] = "-in"; try {
args[1] = "src/test/resources/testarea.gml"; String[] args = new String[10];
args[2] = "-config"; args[0] = "-in";
args[3] = "src/test/resources/testConfigWithStreaming.yml"; args[1] = "src/test/resources/testarea.gml";
args[4] = "-pdfReport"; args[2] = "-config";
args[5] = f.getAbsolutePath(); args[3] = "src/test/resources/testConfigWithStreaming.yml";
args[6] = "-xmlReport"; args[4] = "-pdfReport";
args[7] = f2.getAbsolutePath(); args[5] = f.getAbsolutePath();
args[8] = "-out"; args[6] = "-xmlReport";
args[9] = f3.getAbsolutePath(); args[7] = f2.getAbsolutePath();
CityDoctorValidation.main(args); args[8] = "-out";
assertTrue(f.exists()); args[9] = f3.getAbsolutePath();
assertTrue(f2.exists()); CityDoctorValidation.main(args);
assertTrue(f3.exists()); assertTrue(f.exists());
} finally { assertTrue(f2.exists());
f.delete(); assertTrue(f3.exists());
f2.delete(); } finally {
f3.delete(); f.delete();
} f2.delete();
} f3.delete();
}
@Test }
public void testReportCreation() throws IOException, CityGmlParseException, InvalidGmlFileException {
Path tmpDir = null; @Test
try { public void testStreamingWithXMLValidation() throws CityGmlParseException, IOException, InvalidGmlFileException,
tmpDir = Files.createTempDirectory("testTmp"); CityDoctorWriteException, CheckReportParseException {
tmpDir.toFile().deleteOnExit(); File f2 = folder.newFile();
Path xmlPath = tmpDir.resolve("report.xml"); try {
Path pdfPath = tmpDir.resolve("report.pdf"); String[] args = new String[6];
File features = new File("src/test/resources/feature_types.gml"); args[0] = "-in";
CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse( args[1] = "src/test/resources/SimpleSolid_SrefBS_XMLInvalid.gml";
features.toString(), new ParserConfiguration(8, false)); args[2] = "-config";
assertNotNull(model); args[3] = "src/test/resources/testConfigWithStreamingWithValidation.yml";
Checker checker = new Checker(model); args[4] = "-xmlReport";
checker.runChecks(); args[5] = f2.getAbsolutePath();
checker.writeXmlReport(xmlPath.toString()); CityDoctorValidation.main(args);
checker.writePdfReport(pdfPath.toString()); assertTrue(f2.exists());
assertTrue(Files.exists(xmlPath)); CheckReport checkReport = CheckReport.load(f2.toPath());
assertTrue(Files.exists(pdfPath)); assertEquals(1, checkReport.getValidationResults().getGlobalErrors().size());
try { } finally {
CheckReport report = CheckReport.load(xmlPath.toString()); f2.delete();
assertNotNull(report); }
}
GlobalStatistics globalStatistics = report.getGlobalStatistics();
assertNotNull(globalStatistics); @Test
public void testReportCreation() throws IOException, CityGmlParseException, InvalidGmlFileException {
ModelStatistics modelStats = globalStatistics.getModelStats(); Path tmpDir = null;
assertNotNull(modelStats); try {
assertEquals(1, modelStats.getNumBuildings()); tmpDir = Files.createTempDirectory("testTmp");
assertEquals(1, modelStats.getNumVegetation()); tmpDir.toFile().deleteOnExit();
assertEquals(1, modelStats.getNumTransportation()); Path xmlPath = tmpDir.resolve("report.xml");
assertEquals(1, modelStats.getNumTunnelObjects()); Path pdfPath = tmpDir.resolve("report.pdf");
assertEquals(1, modelStats.getNumBridgeObjects()); File features = new File("src/test/resources/feature_types.gml");
assertEquals(1, modelStats.getNumWaterObjects()); CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse(features.toString(),
assertEquals(1, modelStats.getNumLandObjects()); new ParserConfiguration(8, false));
assertEquals(1, modelStats.getNumCityFurniture()); assertNotNull(model);
assertEquals(1, modelStats.getNumGenericCityObjects()); Checker checker = new Checker(model);
checker.runChecks();
GlobalErrorStatistics globError = globalStatistics.getGlobalErrorStats(); checker.writeXmlReport(xmlPath.toString());
assertNotNull(globError); checker.writePdfReport(pdfPath.toString());
assertEquals(0, globError.getNumErrorBuildings()); assertTrue(Files.exists(xmlPath));
assertEquals(0, globError.getNumErrorVegetation()); assertTrue(Files.exists(pdfPath));
assertEquals(0, globError.getNumErrorTransportation()); try {
assertEquals(0, globError.getNumErrorTunnelObjects()); CheckReport report = CheckReport.load(xmlPath.toString());
assertEquals(0, globError.getNumErrorBridgeObjects()); assertNotNull(report);
assertEquals(0, globError.getNumErrorWaterObjects());
assertEquals(0, globError.getNumErrorLandObjects()); GlobalStatistics globalStatistics = report.getGlobalStatistics();
assertEquals(1, globError.getNumErrorCityFurniture()); assertNotNull(globalStatistics);
assertEquals(1, globError.getNumErrorGenericCityObjects());
ModelStatistics modelStats = globalStatistics.getModelStats();
assertNotNull(modelStats);
} catch (CheckReportParseException e) { assertEquals(1, modelStats.getNumBuildings());
fail("Report should be valid file"); assertEquals(1, modelStats.getNumVegetation());
} assertEquals(1, modelStats.getNumTransportation());
assertEquals(1, modelStats.getNumTunnelObjects());
assertEquals(1, modelStats.getNumBridgeObjects());
} finally { assertEquals(1, modelStats.getNumWaterObjects());
if (tmpDir != null) { assertEquals(1, modelStats.getNumLandObjects());
FileUtils.deleteDirectory(tmpDir.toFile()); assertEquals(1, modelStats.getNumCityFurniture());
} assertEquals(1, modelStats.getNumGenericCityObjects());
}
} GlobalErrorStatistics globError = globalStatistics.getGlobalErrorStats();
assertNotNull(globError);
assertEquals(0, globError.getNumErrorBuildings());
assertEquals(0, globError.getNumErrorVegetation());
assertEquals(0, globError.getNumErrorTransportation());
assertEquals(0, globError.getNumErrorTunnelObjects());
assertEquals(0, globError.getNumErrorBridgeObjects());
assertEquals(0, globError.getNumErrorWaterObjects());
assertEquals(0, globError.getNumErrorLandObjects());
assertEquals(1, globError.getNumErrorCityFurniture());
assertEquals(1, globError.getNumErrorGenericCityObjects());
} catch (CheckReportParseException e) {
fail("Report should be valid file");
}
} finally {
if (tmpDir != null) {
FileUtils.deleteDirectory(tmpDir.toFile());
}
}
}
} }
<?xml version="1.0" encoding="utf-8"?>
<core:CityModel xmlns:core="http://www.opengis.net/citygml/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:dem="http://www.opengis.net/citygml/relief/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/relief/2.0 http://schemas.opengis.net/citygml/relief/2.0/relief.xsd http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd">
<!--
Einfaches Gebäude mit Grundriss 3m x 5m und Satteldach, Traufhöhe 3m, Firsthöhe 4,5m
Modelliert mit Begrenzungsflächen (eine Dachfläche, 4 Wandflächen, 1 Grundfläche),
die Gebäudegeometrie als Solid, der auf die Polygone der Begrenzungsflächen referenziert.
Fehler in der Reihenfolge der Attribute.
CityGML 2.0
Gebäudevolumen: 56,25 m3
12.12.2025
Author: V. Coors, HFT Stuttgart
Lizenz:
-->
<core:cityObjectMember>
<bldg:Building gml:id="_Simple_BD.1">
<gen:stringAttribute name="averageStoreyHeight">
<gen:value>2.5</gen:value>
</gen:stringAttribute>
<gml:name>falsche Reihenfolge</gml:name>
<bldg:lod2Solid>
<gml:Solid>
<gml:exterior>
<gml:CompositeSurface>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.1"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.2"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.3"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.4"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.5"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.6"/>
<gml:surfaceMember xlink:href="#_Simple_BD.1_PG.7"/>
</gml:CompositeSurface>
</gml:exterior>
</gml:Solid>
</bldg:lod2Solid>
<bldg:boundedBy>
<bldg:WallSurface gml:id="_Simple_BD.1_WallSurface_1">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.2">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.2_LR.1">
<gml:posList srsDimension="3">
13.0 15.0 0.0
13.0 15.0 3.0
13.0 10.0 3.0
13.0 10.0 0.0
13.0 15.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface gml:id="_Simple_BD.1_WallSurface_2">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.3">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.3_LR.1">
<gml:posList srsDimension="3">
10.0 15.0 0.0
10.0 15.0 3.0
11.5 15.0 4.5
13.0 15.0 3.0
13.0 15.0 0.0
10.0 15.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface gml:id="_Simple_BD.1_WallSurface_3">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.4">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.4_LR.1">
<gml:posList srsDimension="3">
10.0 10.0 3.0
10.0 15.0 3.0
10.0 15.0 0.0
10.0 10.0 0.0
10.0 10.0 3.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface gml:id="_Simple_BD.1_WallSurface_4">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.5">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.5_LR.1">
<gml:posList srsDimension="3">
13.0 10.0 0.0
13.0 10.0 3.0
11.5 10.0 4.5
10.0 10.0 3.0
10.0 10.0 0.0
13.0 10.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:RoofSurface gml:id="_Simple_BD.1_RoofSurface_1">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.6">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.6_LR.1">
<gml:posList srsDimension="3">
10.0 10.0 3.0
11.5 10.0 4.5
11.5 15.0 4.5
10.0 15.0 3.0
10.0 10.0 3.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.7">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.7_LR.1">
<gml:posList srsDimension="3">
11.5 10.0 4.5
13.0 10.0 3.0
13.0 15.0 3.0
11.5 15.0 4.5
11.5 10.0 4.5
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:RoofSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:GroundSurface gml:id="_Simple_BD.1_GroundSurface_1">
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon gml:id="_Simple_BD.1_PG.1">
<gml:exterior>
<gml:LinearRing gml:id="_Simple_BD.1_PG.1_LR.1">
<gml:posList srsDimension="3">
10.0 10.0 0.0
10.0 15.0 0.0
13.0 15.0 0.0
13.0 10.0 0.0
10.0 10.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:GroundSurface>
</bldg:boundedBy>
</bldg:Building>
</core:cityObjectMember>
</core:CityModel>
\ No newline at end of file
globalParameters:
numberOfRoundingPlaces: 8
schematronFilePath: src/test/resources/schematronTest.xml
useStreaming: true
requirements:
R_GE_R_TOO_FEW_POINTS:
enabled: true
R_GE_R_NOT_CLOSED:
enabled: true
R_GE_R_CONSECUTIVE_POINTS_SAME:
enabled: true
R_GE_R_SELF_INTERSECTION:
enabled: true
R_GE_S_MULTIPLE_CONNECTED_COMPONENTS:
enabled: true
R_GE_P_INTERIOR_DISCONNECTED:
enabled: true
R_GE_P_INTERSECTING_RINGS:
enabled: true
R_GE_P_NON_PLANAR:
enabled: false
parameters:
# one of ("distance", "angle", "both")
type: distance
distanceTolerance: 0.01
angleTolerance: 0.1
R_GE_P_HOLE_OUTSIDE:
enabled: true
R_GE_P_ORIENTATION_RINGS_SAME:
enabled: true
R_GE_P_INNER_RINGS_NESTED:
enabled: true
R_GE_S_TOO_FEW_POLYGONS:
enabled: true
R_GE_S_NOT_CLOSED:
enabled: true
R_GE_S_NON_MANIFOLD_EDGE:
enabled: true
R_GE_S_POLYGON_WRONG_ORIENTATION:
enabled: true
R_GE_S_ALL_POLYGONS_WRONG_ORIENTATION:
enabled: true
R_GE_S_NON_MANIFOLD_VERTEX:
enabled: true
R_GE_S_SELF_INTERSECTION:
enabled: true
\ No newline at end of file
...@@ -289,7 +289,7 @@ public class Healer { ...@@ -289,7 +289,7 @@ public class Healer {
private byte[] writeCityGml(AbstractCityObject aco) throws CityGMLWriteException { private byte[] writeCityGml(AbstractCityObject aco) throws CityGMLWriteException {
CityModel model = new CityModel(); CityModel model = new CityModel();
model.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); model.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
CityGMLContext context = CityGmlParser.getContext(); CityGMLContext context = CityGmlParser.setupContext();
CityGMLOutputFactory outputFactory = context.createCityGMLOutputFactory(CityGMLVersion.v2_0); CityGMLOutputFactory outputFactory = context.createCityGMLOutputFactory(CityGMLVersion.v2_0);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
try (CityGMLWriter writer = outputFactory.createCityGMLWriter(out)) { try (CityGMLWriter writer = outputFactory.createCityGMLWriter(out)) {
......
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