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();
} }
...@@ -131,9 +131,9 @@ public class CityGmlParser { ...@@ -131,9 +131,9 @@ public class CityGmlParser {
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", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); System.setProperty("javax.xml.transform.TransformerFactory",
"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);
...@@ -149,7 +149,7 @@ public class CityGmlParser { ...@@ -149,7 +149,7 @@ public class CityGmlParser {
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());
...@@ -168,13 +168,17 @@ public class CityGmlParser { ...@@ -168,13 +168,17 @@ public class CityGmlParser {
} }
/** /**
* 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
* a {@link CityDoctorModel}.
* <p/> * <p/>
* The nested parse uses an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}, which * The nested parse uses an
* is not connected to the database. * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
* UnconnectedCache}, which is not connected to the database.
* <p/> * <p/>
* Nested parsing should only be used for accessing Features in auxiliary, small CityGML files, e.g. parsing of * Nested parsing should only be used for accessing Features in auxiliary, small
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject} files. * CityGML files, e.g. parsing of
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject
* LibraryObject} files.
* *
* @param filePath String path to the file * @param filePath String path to the file
* @param config Configuration of the parser * @param config Configuration of the parser
...@@ -188,10 +192,12 @@ public class CityGmlParser { ...@@ -188,10 +192,12 @@ public class CityGmlParser {
} }
/** /**
* 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
* a {@link CityDoctorModel}.
* <p/> * <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references * Features are held in a non-persistent, embedded Database with an in-memory
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. * 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 * @param filePath String path to the file
* @param config Configuration of the parser * @param config Configuration of the parser
...@@ -204,12 +210,13 @@ public class CityGmlParser { ...@@ -204,12 +210,13 @@ public class CityGmlParser {
return parseCityGmlFile(filePath, config, null, null); return parseCityGmlFile(filePath, config, null, null);
} }
/** /**
* 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
* a {@link CityDoctorModel}.
* <p/> * <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references * Features are held in a non-persistent, embedded Database with an in-memory
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. * 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 * @param filePath String path to the file
* @param config Configuration of the parser * @param config Configuration of the parser
...@@ -224,10 +231,12 @@ public class CityGmlParser { ...@@ -224,10 +231,12 @@ public class CityGmlParser {
} }
/** /**
* 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
* a {@link CityDoctorModel}.
* <p/> * <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references * Features are held in a non-persistent, embedded Database with an in-memory
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. * 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 * @param filePath String path to the file
* @param config Configuration of the parser * @param config Configuration of the parser
...@@ -243,18 +252,22 @@ public class CityGmlParser { ...@@ -243,18 +252,22 @@ public class CityGmlParser {
} }
/** /**
* 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
* a {@link CityDoctorModel}.
* <p/> * <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references * Features are held in a non-persistent, embedded Database with an in-memory
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache. * 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 * @param filePath String path to the file
* @param config Configuration of the parser * @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress * @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation * @param handler Validation handler for GML-file validation
* @param verbose If verbosity is enabled the parser will write info logs to the logger * @param verbose If verbosity is enabled the parser will write info logs to
* @param unconnected If true the parser will use an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache} * the logger
* to collect the parsed CityObjects * @param unconnected If true the parser will use an
* {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
* UnconnectedCache} to collect the parsed CityObjects
* @return A {@link CityDoctorModel} object with the parsed Features * @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed * @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error * @throws InvalidGmlFileException if the GMLValidationHandler found an error
...@@ -262,13 +275,12 @@ public class CityGmlParser { ...@@ -262,13 +275,12 @@ public class CityGmlParser {
private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l, private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
GMLValidationHandler handler, boolean verbose, boolean unconnected) GMLValidationHandler handler, boolean verbose, boolean unconnected)
throws CityGmlParseException, InvalidGmlFileException { throws CityGmlParseException, InvalidGmlFileException {
CityGMLContext context = getContext(); CityGMLContext context = setupContext();
Path file = Paths.get(filePath); Path file = Paths.get(filePath);
List<String> validationIssues = Collections.emptyList(); List<String> validationIssues = Collections.emptyList();
if (config.getValidate()) { if (config.getValidate()) {
validationIssues = validateFile(context, handler, file); validationIssues = validateFile(handler, file);
} }
try { try {
parseEpsgCodeFromFile(file, config, verbose); parseEpsgCodeFromFile(file, config, verbose);
CityGMLInputFactory in = context.createCityGMLInputFactory() CityGMLInputFactory in = context.createCityGMLInputFactory()
...@@ -288,8 +300,6 @@ public class CityGmlParser { ...@@ -288,8 +300,6 @@ public class CityGmlParser {
} }
} }
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer, public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
String outputFile) throws CityGmlParseException { String outputFile) throws CityGmlParseException {
Path f = Paths.get(file); Path f = Paths.get(file);
...@@ -342,18 +352,18 @@ public class CityGmlParser { ...@@ -342,18 +352,18 @@ public class CityGmlParser {
private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois, private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false); Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile); readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile, config, file);
} }
private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois, private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException { CityGmlConsumer cityObjectConsumer, String outputFile, ParserConfiguration config, Path file)
getContext(); throws CityGMLReadException {
setupContext();
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory() CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false)); .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
CityGMLChunkWriter writer = null; CityGMLChunkWriter writer = null;
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) { try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
CityDoctorModel model = mapper.getModel(); CityDoctorModel model = mapper.getModel();
boolean isInitialized = false; boolean isInitialized = false;
while (reader.hasNext()) { while (reader.hasNext()) {
...@@ -418,7 +428,7 @@ public class CityGmlParser { ...@@ -418,7 +428,7 @@ public class CityGmlParser {
if (outputFile == null) { if (outputFile == null) {
return null; return null;
} }
CityGMLContext gmlContext = CityGmlParser.getContext(); CityGMLContext gmlContext = CityGmlParser.setupContext();
CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI()); CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version); CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile), CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile),
...@@ -432,8 +442,8 @@ public class CityGmlParser { ...@@ -432,8 +442,8 @@ public class CityGmlParser {
} }
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file, private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
CityGMLInputFactory inputFactory, ObservedInputStream ois, CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose, boolean unconnected)
boolean verbose, boolean unconnected) throws CityGMLReadException { throws CityGMLReadException {
return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose); return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
} }
...@@ -448,7 +458,7 @@ public class CityGmlParser { ...@@ -448,7 +458,7 @@ public class CityGmlParser {
CityModel cgmlModel = null; CityModel cgmlModel = null;
CityDoctorModel cdModel = mapper.getModel(); CityDoctorModel cdModel = mapper.getModel();
AutoCloseable parsingContext = cdModel.getCache().startParsingMode(); AutoCloseable parsingContext = cdModel.getCache().startParsingMode();
try (parsingContext){ try (parsingContext) {
List<AbstractCityObject> queuedObjects = new ArrayList<>(); List<AbstractCityObject> queuedObjects = new ArrayList<>();
while (reader.hasNext()) { while (reader.hasNext()) {
AbstractFeature chunk = reader.next(); AbstractFeature chunk = reader.next();
...@@ -473,7 +483,7 @@ public class CityGmlParser { ...@@ -473,7 +483,7 @@ public class CityGmlParser {
if (cgmlModel == null) { if (cgmlModel == null) {
// Add object to queue till the CityModel has been parsed // Add object to queue till the CityModel has been parsed
queuedObjects.add(aco); queuedObjects.add(aco);
}else if (!cdModel.containsGmlId(id)){ } else if (!cdModel.containsGmlId(id)) {
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
} }
} }
...@@ -486,7 +496,8 @@ public class CityGmlParser { ...@@ -486,7 +496,8 @@ public class CityGmlParser {
mapper.setCityModel(new CityModel()); mapper.setCityModel(new CityModel());
for (AbstractCityObject aco : queuedObjects) { for (AbstractCityObject aco : queuedObjects) {
if (!cdModel.containsGmlId(aco.getId())) { if (!cdModel.containsGmlId(aco.getId())) {
mapper.getModel().getCityModel().getCityObjectMembers().add(new AbstractCityObjectProperty(aco)); mapper.getModel().getCityModel().getCityObjectMembers()
.add(new AbstractCityObjectProperty(aco));
} }
} }
} }
...@@ -506,7 +517,8 @@ public class CityGmlParser { ...@@ -506,7 +517,8 @@ public class CityGmlParser {
} }
} }
private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose) throws CityGmlParseException { private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose)
throws CityGmlParseException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
parseEpsgCodeFromStream(bis, config, verbose); parseEpsgCodeFromStream(bis, config, verbose);
} catch (ParserConfigurationException | SAXException | IOException e) { } catch (ParserConfigurationException | SAXException | IOException e) {
...@@ -673,11 +685,12 @@ public class CityGmlParser { ...@@ -673,11 +685,12 @@ public class CityGmlParser {
return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0])); return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
} }
private static List<String> validateFile(CityGMLContext context, GMLValidationHandler handler, Path file) public static List<String> validateFile(GMLValidationHandler handler, Path file)
throws CityGmlParseException { throws CityGmlParseException {
if (handler == null) { if (handler == null) {
handler = new GMLValidationHandler(); handler = new GMLValidationHandler();
} }
setupContext();
try { try {
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler()); SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler); readAdditionalSchemaDefinitions(context, file, schemaHandler);
...@@ -696,8 +709,7 @@ public class CityGmlParser { ...@@ -696,8 +709,7 @@ public class CityGmlParser {
private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler) private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
throws CityGmlParseException { throws CityGmlParseException {
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()) try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()).withSchemaHandler(schemaHandler)
.withSchemaHandler(schemaHandler)
.createReader(file)) { .createReader(file)) {
reader.nextTag(); reader.nextTag();
} catch (Exception e) { } catch (Exception e) {
...@@ -715,5 +727,4 @@ public class CityGmlParser { ...@@ -715,5 +727,4 @@ public class CityGmlParser {
} }
} }
...@@ -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;
...@@ -919,6 +920,20 @@ public class Checker { ...@@ -919,6 +920,20 @@ public class Checker {
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);
CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l); CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l);
......
...@@ -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,22 +47,6 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; ...@@ -31,22 +47,6 @@ 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
...@@ -76,7 +76,8 @@ public class CheckerTest { ...@@ -76,7 +76,8 @@ public class CheckerTest {
} }
@Test @Test
public void testChecker() throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException { public void testChecker()
throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException {
File f = folder.newFile(); File f = folder.newFile();
File f2 = folder.newFile(); File f2 = folder.newFile();
...@@ -98,7 +99,8 @@ public class CheckerTest { ...@@ -98,7 +99,8 @@ public class CheckerTest {
} }
@Test @Test
public void testStreaming() throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException { public void testStreaming()
throws CityGmlParseException, IOException, InvalidGmlFileException, CityDoctorWriteException {
File f = folder.newFile(); File f = folder.newFile();
File f2 = folder.newFile(); File f2 = folder.newFile();
File f3 = folder.newFile(); File f3 = folder.newFile();
...@@ -125,6 +127,27 @@ public class CheckerTest { ...@@ -125,6 +127,27 @@ public class CheckerTest {
} }
} }
@Test
public void testStreamingWithXMLValidation() throws CityGmlParseException, IOException, InvalidGmlFileException,
CityDoctorWriteException, CheckReportParseException {
File f2 = folder.newFile();
try {
String[] args = new String[6];
args[0] = "-in";
args[1] = "src/test/resources/SimpleSolid_SrefBS_XMLInvalid.gml";
args[2] = "-config";
args[3] = "src/test/resources/testConfigWithStreamingWithValidation.yml";
args[4] = "-xmlReport";
args[5] = f2.getAbsolutePath();
CityDoctorValidation.main(args);
assertTrue(f2.exists());
CheckReport checkReport = CheckReport.load(f2.toPath());
assertEquals(1, checkReport.getValidationResults().getGlobalErrors().size());
} finally {
f2.delete();
}
}
@Test @Test
public void testReportCreation() throws IOException, CityGmlParseException, InvalidGmlFileException { public void testReportCreation() throws IOException, CityGmlParseException, InvalidGmlFileException {
Path tmpDir = null; Path tmpDir = null;
...@@ -134,8 +157,8 @@ public class CheckerTest { ...@@ -134,8 +157,8 @@ public class CheckerTest {
Path xmlPath = tmpDir.resolve("report.xml"); Path xmlPath = tmpDir.resolve("report.xml");
Path pdfPath = tmpDir.resolve("report.pdf"); Path pdfPath = tmpDir.resolve("report.pdf");
File features = new File("src/test/resources/feature_types.gml"); File features = new File("src/test/resources/feature_types.gml");
CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse( CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse(features.toString(),
features.toString(), new ParserConfiguration(8, false)); new ParserConfiguration(8, false));
assertNotNull(model); assertNotNull(model);
Checker checker = new Checker(model); Checker checker = new Checker(model);
checker.runChecks(); checker.runChecks();
...@@ -174,12 +197,10 @@ public class CheckerTest { ...@@ -174,12 +197,10 @@ public class CheckerTest {
assertEquals(1, globError.getNumErrorCityFurniture()); assertEquals(1, globError.getNumErrorCityFurniture());
assertEquals(1, globError.getNumErrorGenericCityObjects()); assertEquals(1, globError.getNumErrorGenericCityObjects());
} catch (CheckReportParseException e) { } catch (CheckReportParseException e) {
fail("Report should be valid file"); fail("Report should be valid file");
} }
} finally { } finally {
if (tmpDir != null) { if (tmpDir != null) {
FileUtils.deleteDirectory(tmpDir.toFile()); 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