Commit 297334ab authored by Matthias Betz's avatar Matthias Betz
Browse files

Add design spec for OpenGL 3D view



Replaces the JavaFX SubScene/MeshView rendering with an OpenGL viewport
(openglfx-jogl, GL 3.3 core), single global VBO, GPU color-picking, and
baked fake shading. Windows/Linux targets; JavaFX 3D path removed.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parents 5c2847ef bd40b396
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.16.0</version> <version>3.18.3</version>
</parent> </parent>
<artifactId>CityDoctorValidation</artifactId> <artifactId>CityDoctorValidation</artifactId>
<name>CityDoctorValidation</name> <name>CityDoctorValidation</name>
......
#!/bin/sh #!/bin/sh
./runtime/bin/java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml ./runtime/bin/java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location "${TMPDIR:-/tmp}"
\ No newline at end of file \ No newline at end of file
java -classpath app/*;plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml java -classpath app/*;plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location %TEMP%/citydoctor/
pause pause
\ No newline at end of file
#!/bin/sh #!/bin/sh
java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location #!/bin/sh
\ No newline at end of file ./runtime/bin/java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location "${TMPDIR:-/tmp}"
\ No newline at end of file
"runtime/bin/java.exe" -classpath app/*;plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml "runtime/bin/java.exe" -classpath app/*;plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location %TEMP%/citydoctor/
pause pause
\ No newline at end of file
...@@ -23,9 +23,10 @@ import java.io.FileNotFoundException; ...@@ -23,9 +23,10 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import de.hft.stuttgart.citydoctor2.database.DatabaseSettings;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.citygml4j.core.ade.ADEException;
import de.hft.stuttgart.citydoctor2.check.Checker; import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration; import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
...@@ -70,9 +71,11 @@ public class CityDoctorValidation { ...@@ -70,9 +71,11 @@ public class CityDoctorValidation {
String xmlOutput = getXmlOutput(argParser); String xmlOutput = getXmlOutput(argParser);
String pdfOutput = getPdfOutput(argParser); String pdfOutput = getPdfOutput(argParser);
String outputFile = getOutputFile(argParser, true); String outputFile = getOutputFile(argParser, true);
lookForDbConfigFileParameter(argParser);
lookForDbLocationParameter(argParser);
ValidationConfiguration config = getValidationConfig(argParser); ValidationConfiguration config = getValidationConfig(argParser);
startValidationProcess(new File(inputFile), xmlOutput, pdfOutput, config, outputFile); startValidationProcess(inputFile, xmlOutput, pdfOutput, config, outputFile);
} }
private static String getOutputFile(ArgumentParser argParser, boolean optional) { private static String getOutputFile(ArgumentParser argParser, boolean optional) {
...@@ -81,18 +84,42 @@ public class CityDoctorValidation { ...@@ -81,18 +84,42 @@ public class CityDoctorValidation {
if (optional) { if (optional) {
return null; return null;
} }
logger.error("No output file specified. (-out [FILE])"); logger.fatal(Localization.getText("CityDoctorValidation.noOutputFile"));
System.exit(11); System.exit(11);
} }
List<String> outFiles = argParser.getValues("out"); List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) { if (outFiles.size() != 1) {
logger.error("Specify exactly one file as output."); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile"));
System.exit(12); System.exit(12);
} }
outputFile = outFiles.get(0); outputFile = outFiles.get(0);
return outputFile; return outputFile;
} }
public static void lookForDbConfigFileParameter(ArgumentParser argParser) {
if (!argParser.containsOption("db_config")) {
return;
}
List<String> dbConfigFiles = argParser.getValues("db_config");
if (dbConfigFiles.size() == 1) {
DatabaseSettings.loadPropertiesFromFile(new File(dbConfigFiles.get(0)));
} else if (dbConfigFiles.size() > 1) {
logger.warn("More than one DB config file provided! Using standard config instead.");
}
}
public static void lookForDbLocationParameter(ArgumentParser argParser) {
if (!argParser.containsOption("db_location")) {
return;
}
List<String> dbLocations = argParser.getValues("db_location");
if (dbLocations.size() == 1) {
DatabaseSettings.setDBLocation(dbLocations.get(0));
} else if (dbLocations.size() > 1) {
logger.warn("More than one DB location provided! Using standard location instead.");
}
}
/** /**
* Validates the cityGML input file with a standard validation configuration. * Validates the cityGML input file with a standard validation configuration.
* *
...@@ -133,7 +160,7 @@ public class CityDoctorValidation { ...@@ -133,7 +160,7 @@ public class CityDoctorValidation {
config = ValidationConfiguration.loadStandardValidationConfig(); config = ValidationConfiguration.loadStandardValidationConfig();
config.setUseStreaming(true); config.setUseStreaming(true);
} else { } else {
config = ValidationConfiguration.loadValidationConfig(validationConfigFile.getAbsolutePath()); config = ValidationConfiguration.loadValidationConfigFile(validationConfigFile.getAbsolutePath());
} }
String xmlOutputPath = null; String xmlOutputPath = null;
if (xmlOutput != null) { if (xmlOutput != null) {
...@@ -144,13 +171,29 @@ public class CityDoctorValidation { ...@@ -144,13 +171,29 @@ public class CityDoctorValidation {
pdfOutputPath = pdfOutput.getAbsolutePath(); pdfOutputPath = pdfOutput.getAbsolutePath();
} }
try { try {
startValidationProcess(inputFile, xmlOutputPath, pdfOutputPath, config, null); startFileValidation(inputFile, xmlOutputPath, pdfOutputPath, config, null);
} catch (CityDoctorWriteException e) { } catch (CityDoctorWriteException e) {
// this does not happen as no output file is specified // this does not happen as no output file is specified
logger.catching(e); logger.catching(e);
} }
} }
public static void startValidationProcess(String input, String xmlOutput, String pdfOutput,
ValidationConfiguration config, String outputFile) throws CityDoctorWriteException, CityGmlParseException, IOException, InvalidGmlFileException {
File inputFile = new File(input);
if (!inputFile.exists()) {
logger.error(Localization.getText("CityDoctorValidation.inputFileNotExisting"), input);
System.exit(1);
}
if (inputFile.getName().endsWith(".gml") || inputFile.getName().endsWith(".xml")) {
startFileValidation(inputFile, xmlOutput, pdfOutput, config, outputFile);
} else {
logger.error(Localization.getText("CityDoctorValidation.wrongInputFileFormat"), input);
System.exit(1);
}
}
/** /**
* This function will handle the complete validation process * This function will handle the complete validation process
* *
...@@ -159,12 +202,13 @@ public class CityDoctorValidation { ...@@ -159,12 +202,13 @@ public class CityDoctorValidation {
* @param pdfOutput the output path for the pdf report (optional) * @param pdfOutput the output path for the pdf report (optional)
* @param config the configuration path for the validation plan * @param config the configuration path for the validation plan
* @param outputFile storing the validated gml file with quality ade * @param outputFile storing the validated gml file with quality ade
* @throws CityDoctorWriteException if something goes wrong while writing the gml *
* file * @throws IOException When an exception while writing the report files happens.
* @throws CityGMLBuilderException * @throws CityGmlParseException When an error while parsing the cityGML file happens.
* @throws ADEException * @throws InvalidGmlFileException If the cityGML file is not valid according to the cityGML schema.
* @throws CityDoctorWriteException If something goes wrong while writing the output gml file
*/ */
public static void startValidationProcess(File inputFile, String xmlOutput, String pdfOutput, public static void startFileValidation(File inputFile, String xmlOutput, String pdfOutput,
ValidationConfiguration config, String outputFile) throws IOException, CityGmlParseException, ValidationConfiguration config, String outputFile) throws IOException, CityGmlParseException,
InvalidGmlFileException, CityDoctorWriteException { InvalidGmlFileException, CityDoctorWriteException {
...@@ -185,7 +229,7 @@ public class CityDoctorValidation { ...@@ -185,7 +229,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("pdfreport")) { if (argParser.containsOption("pdfreport")) {
List<String> reportFiles = argParser.getValues("pdfreport"); List<String> reportFiles = argParser.getValues("pdfreport");
if (reportFiles.size() != 1) { if (reportFiles.size() != 1) {
logger.error("Specify exactly one file as pdfReport"); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
System.exit(6); System.exit(6);
} }
return reportFiles.get(0); return reportFiles.get(0);
...@@ -197,7 +241,7 @@ public class CityDoctorValidation { ...@@ -197,7 +241,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("xmlreport")) { if (argParser.containsOption("xmlreport")) {
List<String> reportFiles = argParser.getValues("xmlreport"); List<String> reportFiles = argParser.getValues("xmlreport");
if (reportFiles.size() != 1) { if (reportFiles.size() != 1) {
logger.error("Specify exactly one file as xmlReport"); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
System.exit(5); System.exit(5);
} }
return reportFiles.get(0); return reportFiles.get(0);
...@@ -214,16 +258,16 @@ public class CityDoctorValidation { ...@@ -214,16 +258,16 @@ public class CityDoctorValidation {
if (argParser.containsOption("config")) { if (argParser.containsOption("config")) {
List<String> configFiles = argParser.getValues("config"); List<String> configFiles = argParser.getValues("config");
if (configFiles.size() != 1) { if (configFiles.size() != 1) {
logger.error("Specify exactly one file as configuration"); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
System.exit(4); System.exit(4);
} }
return ValidationConfiguration.loadValidationConfig(configFiles.get(0)); return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0));
} else { } else {
if (optional) { if (optional) {
logger.warn("No configuration file given, loading default configuration"); logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig"));
return ValidationConfiguration.loadStandardValidationConfig(); return ValidationConfiguration.loadStandardValidationConfig();
} else { } else {
logger.error("No configuration file specified with -config [FILE]"); logger.fatal(Localization.getText("CityDoctorValidation.noConfig"));
System.exit(7); System.exit(7);
return null; return null;
} }
...@@ -235,7 +279,7 @@ public class CityDoctorValidation { ...@@ -235,7 +279,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("out")) { if (argParser.containsOption("out")) {
List<String> outFiles = argParser.getValues("out"); List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) { if (outFiles.size() != 1) {
logger.error("Specify exactly one file as output."); logger.fatal(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
System.exit(3); System.exit(3);
} }
return outFiles.get(0); return outFiles.get(0);
...@@ -254,12 +298,12 @@ public class CityDoctorValidation { ...@@ -254,12 +298,12 @@ public class CityDoctorValidation {
if (optional) { if (optional) {
return null; return null;
} }
logger.error("No input file specified. (-in [FILE])"); logger.fatal(Localization.getText("CityDoctorValidation.noInputFile"));
System.exit(1); System.exit(1);
} }
List<String> inFiles = argParser.getValues("in"); List<String> inFiles = argParser.getValues("in");
if (inFiles.size() != 1) { if (inFiles.size() != 1) {
logger.error("Specify exactly one file as input."); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
System.exit(2); System.exit(2);
} }
inputFile = inFiles.get(0); inputFile = inFiles.get(0);
......
package de.hft.stuttgart.citydoctor2;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.database.DatabaseSettings;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import java.io.File;
import java.io.IOException;
@Command(name = "citydoctor2", mixinStandardHelpOptions = true, description = "Validates CityGML files according to a provided validation-plan.")
public class CityDoctorValidationCLI implements Runnable {
@Option(names = {"-i" , "--in" , "--input"},
description = "Filepath of the CityGML file.",
required = true)
private String input;
@Option(names = {"-c" , "--config" , "--configuration", "--plan"},
description = "Filepath of the validation configuration .yml",
defaultValue = Option.NULL_VALUE)
private File config;
@Option(names = {"-x" , "--xml" , "--xmlReport"},
description = "Creates a XML validation report at target location",
defaultValue = Option.NULL_VALUE)
private String xmlReport;
@Option(names = {"-p" , "--pdf" , "--pdfReport"},
description = "Creates a PDF validation report at target location",
defaultValue = Option.NULL_VALUE)
private String pdfReport;
@Option(names = {"-o" , "--out" , "--output"},
description = "Creates a validated copy of the CityGML input with QualityADE at target location.",
defaultValue = Option.NULL_VALUE)
private String output;
@Option(names = {"--db_location", "--db_loc"},
description = "Creates the embedded database file at target location instead of CityDoctor's working directory.",
defaultValue = Option.NULL_VALUE)
private String dbLocation;
@Option(names = {"-db", "--db_config", "--db_settings"},
description = "Filepath to a .properties file for overriding the default configuration of the embedded database",
defaultValue = Option.NULL_VALUE)
private String dbConfig;
@Override
public void run(){
try {
ValidationConfiguration valConfig;
if(config == null){
valConfig=ValidationConfiguration.loadStandardValidationConfig();
} else{
valConfig=ValidationConfiguration.loadValidationConfigFile(config.getAbsolutePath());
}
if(dbConfig != null){
DatabaseSettings.loadPropertiesFromFile(new File(dbConfig));
}
if (dbLocation != null) {
DatabaseSettings.setDBLocation(dbLocation);
}
CityDoctorValidation.startValidationProcess(input, xmlReport, pdfReport, valConfig,output);
} catch (CityDoctorWriteException | CityGmlParseException | IOException | InvalidGmlFileException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
int exitCode = new CommandLine(new CityDoctorValidationCLI()).execute(args);
System.exit(exitCode);
}
}
/*- /*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart * Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
* *
* This file is part of CityDoctor2. * This file is part of CityDoctor2.
* *
* CityDoctor2 is free software: you can redistribute it and/or modify * CityDoctor2 is free software: you can redistribute it and/or modify
...@@ -26,6 +26,8 @@ import java.io.FileOutputStream; ...@@ -26,6 +26,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
...@@ -35,8 +37,17 @@ import java.util.List; ...@@ -35,8 +37,17 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.transform.Result; import javax.xml.transform.Result;
import javax.xml.transform.Source; import javax.xml.transform.Source;
...@@ -55,15 +66,18 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeInvalidError; ...@@ -55,15 +66,18 @@ 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;
import de.hft.stuttgart.citydoctor2.checks.Checks; import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler; import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler;
import de.hft.stuttgart.citydoctor2.checks.util.FeatureCheckedListener; import de.hft.stuttgart.citydoctor2.checks.util.FeatureCheckedListener;
import de.hft.stuttgart.citydoctor2.database.CityObjectCache;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.FeatureType; import de.hft.stuttgart.citydoctor2.datastructure.FeatureType;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.parser.CityGmlConsumer; import de.hft.stuttgart.citydoctor2.parser.CityGmlConsumer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
...@@ -76,6 +90,7 @@ import de.hft.stuttgart.citydoctor2.reporting.XmlValidationReporter; ...@@ -76,6 +90,7 @@ import de.hft.stuttgart.citydoctor2.reporting.XmlValidationReporter;
import de.hft.stuttgart.citydoctor2.reporting.pdf.PdfReporter; import de.hft.stuttgart.citydoctor2.reporting.pdf.PdfReporter;
import de.hft.stuttgart.citydoctor2.reporting.pdf.PdfStreamReporter; import de.hft.stuttgart.citydoctor2.reporting.pdf.PdfStreamReporter;
import de.hft.stuttgart.citydoctor2.utils.Localization; import de.hft.stuttgart.citydoctor2.utils.Localization;
import de.hft.stuttgart.citydoctor2.utils.TimeFormatter;
import de.hft.stuttgart.quality.model.enums.RequirementId; import de.hft.stuttgart.quality.model.enums.RequirementId;
import de.hft.stuttgart.quality.model.enums.TopLevelFeatureType; import de.hft.stuttgart.quality.model.enums.TopLevelFeatureType;
import de.hft.stuttgart.quality.model.properties.CheckingProperty; import de.hft.stuttgart.quality.model.properties.CheckingProperty;
...@@ -90,305 +105,363 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan; ...@@ -90,305 +105,363 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan;
/** /**
* The main container class for checking. It contains the logic for validation, * The main container class for checking. It contains the logic for validation,
* as well as contains the state of the checks performed. * as well as contains the state of the checks performed.
*
* @author Matthias Betz
* *
* @author Matthias Betz
*/ */
public class Checker { public class Checker {
private static final Logger logger = LogManager.getLogger(Checker.class); private static final Logger logger = LogManager.getLogger(Checker.class);
private ValidationConfiguration config; private ValidationConfiguration config;
private List<List<Check>> execLayers; private List<List<Check>> execLayers;
private List<Filter> includeFilters; private List<Filter> includeFilters;
private List<Filter> excludeFilters; private List<Filter> excludeFilters;
private final Checks checkConfig; private final Checks checkConfig;
private final CityDoctorModel model; private final CityDoctorModel model;
public Checker(CityDoctorModel model) { public Checker(CityDoctorModel model) {
this(ValidationConfiguration.loadStandardValidationConfig(), model); this(ValidationConfiguration.loadStandardValidationConfig(), model);
} }
public Checker(ValidationConfiguration config, CityDoctorModel model) {
this.model = model;
checkConfig = new Checks();
setValidationConfig(config);
}
public Checks getChecks() {
return checkConfig;
}
public CityDoctorModel getModel() { public Checker(ValidationConfiguration config, CityDoctorModel model) {
return model; this.model = model;
} checkConfig = new Checks();
setValidationConfig(config);
/** }
* Write the xml report for the given CityDoctorModel. If no report location is
* given or this checker has not validated anything, nothing is done.
*
* @param xmlOutput the output file location for the XML report. Can be null.
*/
public void writeXmlReport(String xmlOutput) {
if (!model.isValidated() || xmlOutput == null) {
return;
}
File xmlFile = new File(xmlOutput);
if (xmlFile.getParentFile() != null) {
xmlFile.getParentFile().mkdirs();
}
Reporter reporter = new XmlValidationReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config);
} catch (CheckReportWriteException | IOException e) {
logger.error(Localization.getText("Checker.failXml"), e);
}
}
public void writePdfReport(String pdfOutput) {
if (!model.isValidated() || pdfOutput == null) {
return;
}
File pdfFile = new File(pdfOutput);
if (pdfFile.getParentFile() != null) {
pdfFile.getParentFile().mkdirs();
}
Reporter reporter = new PdfReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config);
} catch (IOException | CheckReportWriteException e) {
logger.error(Localization.getText("Checker.failPdf"), e);
}
}
public void runChecks() { public Checks getChecks() {
runChecks((ProgressListener) null); return checkConfig;
} }
public void runChecks(String xmlOutput) { public CityDoctorModel getModel() {
runChecks(); return model;
writeXmlReport(xmlOutput); }
}
public void runChecks(String xmlOutput, String pdfOutput) { /**
runChecks(); * Write the xml report for the given CityDoctorModel. If no report location is
writeXmlReport(xmlOutput); * given or this checker has not validated anything, nothing is done.
writePdfReport(pdfOutput); *
} * @param xmlOutput the output file location for the XML report. Can be null.
*/
public void writeXmlReport(String xmlOutput) {
if (!model.isValidated() || xmlOutput == null) {
return;
}
File xmlFile = new File(xmlOutput);
if (xmlFile.getParentFile() != null && xmlFile.getParentFile().mkdirs()) {
logger.trace("Xml dir created");
}
Reporter reporter = new XmlValidationReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config);
} catch (CheckReportWriteException | IOException e) {
logger.error(Localization.getText("Checker.failXml"), e);
}
}
public void runChecks(String xmlOutput, String pdfOutput, ProgressListener l) { public void writePdfReport(String pdfOutput) {
runChecks(l); if (!model.isValidated() || pdfOutput == null) {
writeXmlReport(xmlOutput); return;
writePdfReport(pdfOutput); }
} File pdfFile = new File(pdfOutput);
if (pdfFile.getParentFile() != null && pdfFile.getParentFile().mkdirs()){
logger.trace("Pdf dir created");
}
public void runChecks(ProgressListener l) { Reporter reporter = new PdfReporter();
if (config == null) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) {
config = ValidationConfiguration.loadStandardValidationConfig(); reporter.writeReport(checkConfig, bos, model, config);
} } catch (IOException | CheckReportWriteException e) {
checkCityModel(model, l); logger.error(Localization.getText("Checker.failPdf"), e);
if (logger.isInfoEnabled()) { }
logger.info(Localization.getText("Checker.checksFinished")); }
}
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, model.getFile()); public void runChecks() {
if (handler != null) { runChecks((ProgressListener) null);
handleSchematronResults(handler); }
}
model.setValidated(createValidationPlan()); public void runChecks(String xmlOutput) {
} runChecks();
writeXmlReport(xmlOutput);
}
public void runChecks(String xmlOutput, String pdfOutput) {
runChecks();
writeXmlReport(xmlOutput);
writePdfReport(pdfOutput);
}
public void runChecks(String xmlOutput, String pdfOutput, ProgressListener l) {
runChecks(l);
writeXmlReport(xmlOutput);
writePdfReport(pdfOutput);
}
public void runChecks(ProgressListener l) {
if (config == null) {
config = ValidationConfiguration.loadStandardValidationConfig();
}
AutoCloseable checkingContext = model.getCache().startCheckingMode();
try(checkingContext){
Callable<SvrlContentHandler> schematronThread = () -> executeSchematronValidationIfAvailable(config, model.getFile());
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<SvrlContentHandler> handlerFuture = executor.submit(schematronThread);
executor.shutdown();
checkCityModel(model, l);
getSchematronResults(handlerFuture, l);
model.setValidated(createValidationPlan());
} catch (Exception e){
logger.error("Validation failed due to encountering error", e);
}
}
private void handleSchematronResults(SvrlContentHandler handler) { private void getSchematronResults(Future<SvrlContentHandler> handlerFuture, ProgressListener l) {
model.addGlobalErrors(handler.getGeneralErrors()); if (!handlerFuture.isDone()) {
Map<String, CityObject> featureMap = new HashMap<>(); if (logger.isInfoEnabled()) {
model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f)); logger.info(Localization.getText("Checker.schematronStillRunning"));
handler.getFeatureErrors().forEach((k, v) -> { }
if (k.trim().isEmpty()) { if(l !=null){
// missing gml id, ignore? l.updateProgress(-0.5f);
return; }
} }
CityObject co = featureMap.get(k); try {
if (co == null) { SvrlContentHandler handler = handlerFuture.get();
// gml id reported by schematron was not found, add to general errors if (handler != null) {
for (SchematronError se : v) { handleSchematronResults(handler);
model.addGlobalError(se); }
} } catch (ExecutionException e) {
} else { logger.error(e);
handleSchematronErrorsForCityObject(v, co); } catch (InterruptedException e) {
} logger.error(Localization.getText("Checker.schematronInterrupted"), e);
}); Thread.currentThread().interrupt();
} }
}
private void handleSchematronResults(SvrlContentHandler handler) {
CityObjectCache cache = model.getCache();
handleSchematronErrorsGlobal(handler.getGeneralErrors());
boolean onlySchematron = execLayers.isEmpty();
if (onlySchematron) {
CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() {
@Override
public void check(Checkable checkable) {
checkable.setValidated(true);
}
};
model.createFeatureStream().forEach(co -> co.accept(visitor));
}
handler.getFeatureErrors().forEach((k, v) -> {
String trimmedId = k.trim();
CityObject co = cache.get(new GmlId(trimmedId));
if (co == null) {
// gml id reported by schematron was not found, add to general errors
handleSchematronErrorsGlobal(v);
} else {
handleSchematronErrorsForCityObject(v, co);
co.setValidated(true);
}
});
}
public static void handleSchematronErrorsForCityObject(List<SchematronError> v, CityObject co) { private void handleSchematronErrorsGlobal(List<SchematronError> v) {
int count = 0;
for (SchematronError se : v) { for (SchematronError se : v) {
CheckError err; CheckError err;
if (AttributeMissingError.ID.getIdString().equals(se.getErrorIdString())) { if (AttributeMissingError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeMissingError(co, se.getChildId(), se.getNameOfAttribute()); err = new AttributeMissingError(null, se.getChildId(), se.getNameOfAttribute());
} else if (AttributeValueWrongError.ID.getIdString().equals(se.getErrorIdString())) { } else if (AttributeValueWrongError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeValueWrongError(co, se.getChildId(), se.getNameOfAttribute()); err = new AttributeValueWrongError(null, se.getChildId(), se.getNameOfAttribute());
} else if (AttributeInvalidError.ID.getIdString().equals(se.getErrorIdString())) { } else if (AttributeInvalidError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeInvalidError(co, se.getChildId(), se.getNameOfAttribute()); err = new AttributeInvalidError(null, se.getChildId(), se.getNameOfAttribute());
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
"Unknown error ID was given in schematron file: " + se.getErrorIdString()); "Unknown error ID was given in schematron file: " + se.getErrorIdString());
} }
co.addCheckResult(new CheckResult(new CheckId("SchematronCheck " + count), ResultStatus.ERROR, err)); model.addGlobalError(err);
count++; }
} }
}
public static void handleSchematronErrorsForCityObject(List<SchematronError> v, CityObject co) {
ValidationPlan createValidationPlan() { int count = 0;
ValidationPlan plan = new ValidationPlan(); for (SchematronError se : v) {
de.hft.stuttgart.quality.model.types.Filter filter = createFilter(); CheckError err;
plan.setFilter(new FilterProperty(filter)); if (AttributeMissingError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeMissingError(co, se.getChildId(), se.getNameOfAttribute());
Map<String, de.hft.stuttgart.citydoctor2.check.Requirement> reqs = Checks.getAvailableRequirements(); } else if (AttributeValueWrongError.ID.getIdString().equals(se.getErrorIdString())) {
for (Entry<String, RequirementConfiguration> e : config.getRequirements().entrySet()) { err = new AttributeValueWrongError(co, se.getChildId(), se.getNameOfAttribute());
RequirementId reqId = mapToRequirement(e.getKey()); } else if (AttributeInvalidError.ID.getIdString().equals(se.getErrorIdString())) {
if (reqId == null) { err = new AttributeInvalidError(co, se.getChildId(), se.getNameOfAttribute());
continue; } else {
} throw new IllegalStateException(
de.hft.stuttgart.quality.model.types.Requirement req = new de.hft.stuttgart.quality.model.types.Requirement(); "Unknown error ID was given in schematron file: " + se.getErrorIdString());
req.setRequirementType(reqId); }
req.setEnabled(e.getValue().isEnabled()); co.addCheckResult(new CheckResult(new CheckId("SchematronCheck " + count), ResultStatus.ERROR, err));
plan.getRequirements().add(new RequirementProperty(req)); count++;
Map<String, String> parameters = e.getValue().getParameters(); }
if (parameters != null) { }
for (Entry<String, String> param : parameters.entrySet()) {
Parameter p = new Parameter(); ValidationPlan createValidationPlan() {
DefaultParameter defaultP = getDefaultParameter(e.getKey(), reqs, param.getKey()); ValidationPlan plan = new ValidationPlan();
if (defaultP != null) { de.hft.stuttgart.quality.model.types.Filter filter = createFilter();
p.setUom(defaultP.getUnitType().getGmlRepresentation()); plan.setFilter(new FilterProperty(filter));
}
p.setName(param.getKey()); Map<String, de.hft.stuttgart.citydoctor2.check.Requirement> reqs = Checks.getAvailableRequirements();
p.setValue(param.getValue()); for (Entry<String, RequirementConfiguration> e : config.getRequirements().entrySet()) {
req.getParameters().add(new ParameterProperty(p)); RequirementId reqId = mapToRequirement(e.getKey());
} if (reqId == null) {
} continue;
} }
de.hft.stuttgart.quality.model.types.Requirement req = new de.hft.stuttgart.quality.model.types.Requirement();
de.hft.stuttgart.quality.model.types.Requirement missing = new de.hft.stuttgart.quality.model.types.Requirement(); req.setRequirementType(reqId);
missing.setRequirementType(RequirementId.R_SE_ATTRIBUTES_EXISTING); req.setEnabled(e.getValue().isEnabled());
de.hft.stuttgart.quality.model.types.Requirement correct = new de.hft.stuttgart.quality.model.types.Requirement(); plan.getRequirements().add(new RequirementProperty(req));
correct.setRequirementType(RequirementId.R_SE_ATTRIBUTES_CORRECT); Map<String, String> parameters = e.getValue().getParameters();
missing.setEnabled(config.getSchematronFilePath() != null); if (parameters != null) {
correct.setEnabled(config.getSchematronFilePath() != null); for (Entry<String, String> param : parameters.entrySet()) {
plan.getRequirements().add(new RequirementProperty(missing)); Parameter p = new Parameter();
plan.getRequirements().add(new RequirementProperty(correct)); DefaultParameter defaultP = getDefaultParameter(e.getKey(), reqs, param.getKey());
if (defaultP != null) {
Parameter numRounding = new Parameter(); p.setUom(defaultP.getUnitType().getGmlRepresentation());
numRounding.setName("numberOfRoundingPlaces"); } else {
numRounding.setValue("" + config.getNumberOfRoundingPlaces()); p.setUom("");
Parameter minVertexDistance = new Parameter(); }
minVertexDistance.setName("minVertexDistance"); p.setName(param.getKey());
minVertexDistance.setUom("m"); p.setValue(param.getValue());
minVertexDistance.setValue("" + config.getMinVertexDistance()); req.getParameters().add(new ParameterProperty(p));
Parameter schematronFile = new Parameter(); }
schematronFile.setName("schematronFile"); }
schematronFile.setValue(config.getSchematronFilePath()); }
de.hft.stuttgart.quality.model.types.GlobalParameters globParams = new de.hft.stuttgart.quality.model.types.GlobalParameters();
plan.setGlobalParameters(new GlobalParametersProperty(globParams));
globParams.getParameters().add(new ParameterProperty(numRounding));
globParams.getParameters().add(new ParameterProperty(minVertexDistance));
globParams.getParameters().add(new ParameterProperty(schematronFile));
return plan;
}
private DefaultParameter getDefaultParameter(String reqKey,
Map<String, de.hft.stuttgart.citydoctor2.check.Requirement> reqs, String paramName) {
de.hft.stuttgart.citydoctor2.check.Requirement requirement = reqs.get(reqKey);
if (requirement != null) {
for (DefaultParameter param : requirement.getDefaultParameter()) {
if (param.getName().equals(paramName)) {
return param;
}
}
}
return null;
}
private de.hft.stuttgart.quality.model.types.Filter createFilter() {
var filter = new de.hft.stuttgart.quality.model.types.Filter();
handleInputFilter(filter);
if (excludeFilters != null) {
for (Filter f : excludeFilters) {
if (f instanceof TypeFilter tf) {
FeatureType type = tf.getType();
TopLevelFeatureType tlft = mapToTopLevelFeatureType(type);
if (tlft == null) {
continue;
}
removeFilter(tlft, filter);
}
}
}
return filter;
}
private void handleInputFilter(de.hft.stuttgart.quality.model.types.Filter filter) {
if (includeFilters == null || includeFilters.isEmpty()) {
// no filter means, use all
addAllFilters(filter);
} else {
for (Filter f : includeFilters) {
if (f instanceof TypeFilter tf) {
FeatureType type = tf.getType();
TopLevelFeatureType tlft = mapToTopLevelFeatureType(type);
if (tlft == null) {
continue;
}
Checking c = new Checking();
c.setFeatureType(tlft);
filter.getChecking().add(new CheckingProperty(c));
}
}
if (filter.getChecking().isEmpty()) {
// this happens if no type include filter was used
// it is possible only single objects were tested then
// so include everything
addAllFilters(filter);
}
}
}
private void addAllFilters(de.hft.stuttgart.quality.model.types.Filter filter) {
Checking buildingChecking = new Checking();
buildingChecking.setFeatureType(TopLevelFeatureType.BUILDING);
filter.getChecking().add(new CheckingProperty(buildingChecking));
Checking bridgeChecking = new Checking();
bridgeChecking.setFeatureType(TopLevelFeatureType.BRIDGE);
filter.getChecking().add(new CheckingProperty(bridgeChecking));
Checking landChecking = new Checking();
landChecking.setFeatureType(TopLevelFeatureType.LAND);
filter.getChecking().add(new CheckingProperty(landChecking));
Checking transportationChecking = new Checking();
transportationChecking.setFeatureType(TopLevelFeatureType.TRANSPORTATION);
filter.getChecking().add(new CheckingProperty(transportationChecking));
Checking vegetationChecking = new Checking();
vegetationChecking.setFeatureType(TopLevelFeatureType.VEGETATION);
filter.getChecking().add(new CheckingProperty(vegetationChecking));
Checking waterChecking = new Checking();
waterChecking.setFeatureType(TopLevelFeatureType.WATER);
filter.getChecking().add(new CheckingProperty(waterChecking));
}
private void removeFilter(TopLevelFeatureType tlft, de.hft.stuttgart.quality.model.types.Filter filter) { de.hft.stuttgart.quality.model.types.Requirement missing = new de.hft.stuttgart.quality.model.types.Requirement();
for (CheckingProperty c : filter.getChecking()) { missing.setRequirementType(RequirementId.R_SE_ATTRIBUTES_EXISTING);
if (c.getObject().getFeatureType().equals(tlft)) { de.hft.stuttgart.quality.model.types.Requirement correct = new de.hft.stuttgart.quality.model.types.Requirement();
filter.getChecking().remove(c); correct.setRequirementType(RequirementId.R_SE_ATTRIBUTES_CORRECT);
return; missing.setEnabled(config.getSchematronFilePath() != null);
} correct.setEnabled(config.getSchematronFilePath() != null);
} plan.getRequirements().add(new RequirementProperty(missing));
} plan.getRequirements().add(new RequirementProperty(correct));
de.hft.stuttgart.quality.model.types.GlobalParameters globParams = new de.hft.stuttgart.quality.model.types.GlobalParameters();
plan.setGlobalParameters(new GlobalParametersProperty(globParams));
Parameter numRounding = new Parameter();
numRounding.setName("numberOfRoundingPlaces");
numRounding.setUom("amount");
numRounding.setValue("" + config.getNumberOfRoundingPlaces());
globParams.getParameters().add(new ParameterProperty(numRounding));
Parameter minVertexDistance = new Parameter();
minVertexDistance.setName("minVertexDistance");
minVertexDistance.setUom("m");
minVertexDistance.setValue("" + config.getMinVertexDistance());
globParams.getParameters().add(new ParameterProperty(minVertexDistance));
if (config.getSchematronFilePath() != null) {
Parameter schematronFile = new Parameter();
schematronFile.setName("schematronFile");
schematronFile.setUom("path");
schematronFile.setValue(config.getSchematronFilePath());
globParams.getParameters().add(new ParameterProperty(schematronFile));
}
return plan;
}
private DefaultParameter getDefaultParameter(String reqKey,
Map<String, de.hft.stuttgart.citydoctor2.check.Requirement> reqs, String paramName) {
de.hft.stuttgart.citydoctor2.check.Requirement requirement = reqs.get(reqKey);
if (requirement != null) {
for (DefaultParameter param : requirement.getDefaultParameter()) {
if (param.getName().equals(paramName)) {
return param;
}
}
}
return null;
}
private de.hft.stuttgart.quality.model.types.Filter createFilter() {
var filter = new de.hft.stuttgart.quality.model.types.Filter();
handleInputFilter(filter);
if (excludeFilters != null) {
for (Filter f : excludeFilters) {
if (f instanceof TypeFilter tf) {
FeatureType type = tf.getType();
TopLevelFeatureType tlft = mapToTopLevelFeatureType(type);
if (tlft == null) {
continue;
}
removeFilter(tlft, filter);
}
}
}
return filter;
}
private void handleInputFilter(de.hft.stuttgart.quality.model.types.Filter filter) {
if (includeFilters == null || includeFilters.isEmpty()) {
// no filter means, use all
addAllFilters(filter);
} else {
for (Filter f : includeFilters) {
if (f instanceof TypeFilter tf) {
FeatureType type = tf.getType();
TopLevelFeatureType tlft = mapToTopLevelFeatureType(type);
if (tlft == null) {
continue;
}
Checking c = new Checking();
c.setFeatureType(tlft);
filter.getChecking().add(new CheckingProperty(c));
}
}
if (filter.getChecking().isEmpty()) {
// this happens if no type include filter was used
// it is possible only single objects were tested then
// so include everything
addAllFilters(filter);
}
}
}
private void addAllFilters(de.hft.stuttgart.quality.model.types.Filter filter) {
Checking buildingChecking = new Checking();
buildingChecking.setFeatureType(TopLevelFeatureType.BUILDING);
filter.getChecking().add(new CheckingProperty(buildingChecking));
Checking bridgeChecking = new Checking();
bridgeChecking.setFeatureType(TopLevelFeatureType.BRIDGE);
filter.getChecking().add(new CheckingProperty(bridgeChecking));
Checking landChecking = new Checking();
landChecking.setFeatureType(TopLevelFeatureType.LAND);
filter.getChecking().add(new CheckingProperty(landChecking));
Checking transportationChecking = new Checking();
transportationChecking.setFeatureType(TopLevelFeatureType.TRANSPORTATION);
filter.getChecking().add(new CheckingProperty(transportationChecking));
Checking vegetationChecking = new Checking();
vegetationChecking.setFeatureType(TopLevelFeatureType.VEGETATION);
filter.getChecking().add(new CheckingProperty(vegetationChecking));
Checking waterChecking = new Checking();
waterChecking.setFeatureType(TopLevelFeatureType.WATER);
filter.getChecking().add(new CheckingProperty(waterChecking));
}
private void removeFilter(TopLevelFeatureType tlft, de.hft.stuttgart.quality.model.types.Filter filter) {
for (CheckingProperty c : filter.getChecking()) {
if (c.getObject().getFeatureType().equals(tlft)) {
filter.getChecking().remove(c);
return;
}
}
}
private TopLevelFeatureType mapToTopLevelFeatureType(FeatureType type) { private TopLevelFeatureType mapToTopLevelFeatureType(FeatureType type) {
return switch (type) { return switch (type) {
case BRIDGE -> TopLevelFeatureType.BRIDGE; case BRIDGE -> TopLevelFeatureType.BRIDGE;
case BUILDING -> TopLevelFeatureType.BUILDING; case BUILDING -> TopLevelFeatureType.BUILDING;
...@@ -398,299 +471,397 @@ public class Checker { ...@@ -398,299 +471,397 @@ public class Checker {
case WATER -> TopLevelFeatureType.WATER; case WATER -> TopLevelFeatureType.WATER;
default -> null; default -> null;
}; };
} }
private RequirementId mapToRequirement(String requirementName) { private RequirementId mapToRequirement(String requirementName) {
try { try {
return RequirementId.valueOf(requirementName); return RequirementId.valueOf(requirementName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return null; return null;
} }
} }
public ValidationConfiguration getConfig() { public ValidationConfiguration getConfig() {
return config; return config;
} }
public static SvrlContentHandler executeSchematronValidationIfAvailable(ValidationConfiguration config, File file) { public static SvrlContentHandler executeSchematronValidationIfAvailable(ValidationConfiguration config, File file) {
if (file == null || !file.exists()) { if (file == null || !file.exists()) {
return null; return null;
} }
try { try {
return executeSchematronValidationIfAvailable(config, new FileInputStream(file)); return executeSchematronValidationIfAvailable(config, new FileInputStream(file));
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
@SuppressWarnings("resource") public static SvrlContentHandler executeSchematronValidationIfAvailable(ValidationConfiguration config,
public static SvrlContentHandler executeSchematronValidationIfAvailable(ValidationConfiguration config, InputStream in) {
InputStream in) { if (config.getSchematronFilePath() != null && !config.getSchematronFilePath().isEmpty()) {
if (config.getSchematronFilePath() != null && !config.getSchematronFilePath().isEmpty()) { if (logger.isInfoEnabled()) {
if (logger.isInfoEnabled()) { logger.info(Localization.getText("Checker.schematronValidation"));
logger.info(Localization.getText("Checker.schematronValidation")); }
} if (!Files.exists(Path.of(config.getSchematronFilePath()))) {
logger.warn(Localization.getText("Checker.schematronFileNotFound"), config.getSchematronFilePath());
return null;
}
try { try {
TransformerFactory transformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", Checker.class.getClassLoader()); TransformerFactory transformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", Checker.class.getClassLoader());
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
transformerFactory.setURIResolver((href, base) -> new StreamSource(Checker.class.getResourceAsStream(href)));
Source dsdlXslSource = new StreamSource(Checker.class.getResourceAsStream("iso_dsdl_include.xsl"));
Transformer dsdlXslTransformer = transformerFactory.newTransformer(dsdlXslSource);
DOMResult dsdlXslResult = new DOMResult();
dsdlXslTransformer.transform(new StreamSource(new File(config.getSchematronFilePath())), dsdlXslResult);
Source abstractExpandXsl = new StreamSource(Checker.class.getResourceAsStream("iso_abstract_expand.xsl"));
Transformer abstractExpandTransformer = transformerFactory.newTransformer(abstractExpandXsl);
DOMResult abstractExpandResult = new DOMResult();
abstractExpandTransformer.transform(new DOMSource(dsdlXslResult.getNode()), abstractExpandResult);
Source svrlXslSource = new StreamSource(Checker.class.getResourceAsStream("iso_svrl_for_xslt2.xsl"));
Transformer svrlTransformer = transformerFactory.newTransformer(svrlXslSource);
DOMResult schematronXsltResult = new DOMResult();
svrlTransformer.transform(new DOMSource(abstractExpandResult.getNode()), schematronXsltResult);
Transformer schematronTransformer = transformerFactory.newTransformer(new DOMSource(schematronXsltResult.getNode()));
Source cityGmlSource = new StreamSource(in);
SvrlContentHandler handler = new SvrlContentHandler();
Result finalResult = new SAXResult(handler);
schematronTransformer.transform(cityGmlSource, finalResult);
return handler;
} catch (TransformerException e) {
logger.catching(e);
}
}
return null;
}
private void buildFilters() { transformerFactory.setURIResolver((href, base) -> new StreamSource(Checker.class.getResourceAsStream(href)));
FilterConfiguration filterConfig = config.getFilter();
if (filterConfig == null) {
includeFilters = Collections.emptyList();
excludeFilters = Collections.emptyList();
return;
}
excludeFilters = buildExcludeFilters(filterConfig);
includeFilters = buildIncludeFilters(filterConfig);
}
private List<Filter> buildExcludeFilters(FilterConfiguration filterConfig) { Source dsdlXslSource = new StreamSource(Checker.class.getResourceAsStream("iso_dsdl_include.xsl"));
if (filterConfig == null) { Transformer dsdlXslTransformer = transformerFactory.newTransformer(dsdlXslSource);
return Collections.emptyList();
}
ExcludeFilterConfiguration excludeConfig = filterConfig.getExclude();
if (excludeConfig == null) {
return Collections.emptyList();
} else {
List<Filter> filters = new ArrayList<>();
if (excludeConfig.getTypes() != null) {
for (FeatureType excludeType : excludeConfig.getTypes()) {
filters.add(new TypeFilter(excludeType));
}
}
if (excludeConfig.getIds() != null) {
for (String excludePattern : excludeConfig.getIds()) {
Filter f = new EqualsIgnoreCaseFilter(excludePattern);
filters.add(f);
}
}
return filters;
}
}
private List<Filter> buildIncludeFilters(FilterConfiguration filterConfig) { DOMResult dsdlXslResult = new DOMResult();
if (filterConfig == null) { dsdlXslTransformer.transform(new StreamSource(new File(config.getSchematronFilePath())), dsdlXslResult);
return Collections.emptyList();
}
IncludeFilterConfiguration includeConfig = filterConfig.getInclude();
if (includeConfig == null) {
return Collections.emptyList();
} else {
List<Filter> filters = new ArrayList<>();
if (includeConfig.getTypes() != null) {
for (FeatureType includeType : includeConfig.getTypes()) {
filters.add(new TypeFilter(includeType));
}
}
if (includeConfig.getIds() != null) {
for (String includePattern : includeConfig.getIds()) {
Filter f = new EqualsIgnoreCaseFilter(includePattern);
filters.add(f);
}
}
return filters;
}
}
private void setValidationConfig(ValidationConfiguration config) { Source abstractExpandXsl = new StreamSource(Checker.class.getResourceAsStream("iso_abstract_expand.xsl"));
if (config == null) { Transformer abstractExpandTransformer = transformerFactory.newTransformer(abstractExpandXsl);
throw new IllegalArgumentException("Validation configuration may not be null");
}
this.config = config;
buildFilters();
ParserConfiguration parserConfig = config.getParserConfiguration();
List<Check> checks = collectEnabledChecksAndInit(parserConfig, config);
execLayers = buildExecutionLayers(checks);
}
private List<Check> collectEnabledChecksAndInit(ParserConfiguration parserConfig, ValidationConfiguration config) { DOMResult abstractExpandResult = new DOMResult();
Set<CheckId> enabledCheck = new HashSet<>(); abstractExpandTransformer.transform(new DOMSource(dsdlXslResult.getNode()), abstractExpandResult);
Map<CheckId, Map<String, String>> parameterMap = new HashMap<>();
for (Entry<String, RequirementConfiguration> e : config.getRequirements().entrySet()) {
de.hft.stuttgart.citydoctor2.check.Requirement req = Checks.getAvailableRequirements().get(e.getKey());
if (req == null) {
logger.warn("Could not find any check that satisfies requirement {}, it will not be checked",
e.getKey());
} else {
if (e.getValue().isEnabled()) {
// this requirement is enabled
insertGlobalParameters(config, enabledCheck, parameterMap, e, req);
}
}
}
fillParameterMapsWithDefaultParameter(enabledCheck, parameterMap);
ArrayList<Check> checkList = new ArrayList<>();
for (CheckId id : enabledCheck) {
Check c = checkConfig.getCheckForId(id);
c.init(parameterMap.get(id), parserConfig);
checkList.add(c);
}
return checkList;
}
private void insertGlobalParameters(ValidationConfiguration config, Set<CheckId> enabledCheck, Source svrlXslSource = new StreamSource(Checker.class.getResourceAsStream("iso_svrl_for_xslt2.xsl"));
Map<CheckId, Map<String, String>> parameterMap, Entry<String, RequirementConfiguration> e, Transformer svrlTransformer = transformerFactory.newTransformer(svrlXslSource);
de.hft.stuttgart.citydoctor2.check.Requirement req) {
for (CheckPrototype proto : Checks.getCheckPrototypes()) {
if (proto.checksRequirements().contains(req)) {
// this requirement is checked by this check
// put all requirement parameter in the map
parameterMap.compute(proto.getCheckId(), (k, v) -> {
if (v == null) {
v = new HashMap<>();
v.put(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, config.getNumberOfRoundingPlacesAsString());
v.put(GlobalParameters.MIN_VERTEX_DISTANCE, config.getMinVertexDistanceAsString());
}
v.putAll(e.getValue().getParameters());
return v;
});
enabledCheck.add(proto.getCheckId());
collectDependencyChecks(proto, enabledCheck);
}
}
}
private void fillParameterMapsWithDefaultParameter(Set<CheckId> enabledCheck, DOMResult schematronXsltResult = new DOMResult();
Map<CheckId, Map<String, String>> parameterMap) { svrlTransformer.transform(new DOMSource(abstractExpandResult.getNode()), schematronXsltResult);
for (CheckId id : enabledCheck) {
CheckPrototype proto = Checks.getCheckPrototypeForId(id);
Map<String, String> map = parameterMap.computeIfAbsent(id, k -> new HashMap<>());
for (de.hft.stuttgart.citydoctor2.check.Requirement req : proto.checksRequirements()) {
if (proto.checksRequirements().contains(req)) {
for (DefaultParameter param : req.getDefaultParameter()) {
map.computeIfAbsent(param.getName(), k -> param.getValue());
}
}
}
}
}
private void collectDependencyChecks(CheckPrototype proto, Set<CheckId> enabledChecks) { Transformer schematronTransformer = transformerFactory.newTransformer(new DOMSource(schematronXsltResult.getNode()));
enabledChecks.addAll(proto.getDependencies()); Source cityGmlSource = new StreamSource(in);
for (CheckId id : proto.getDependencies()) {
if (enabledChecks.contains(id)) {
continue;
}
CheckPrototype depProto = Checks.getCheckPrototypeForId(id);
collectDependencyChecks(depProto, enabledChecks);
}
}
private void checkCityModel(CityDoctorModel model, ProgressListener l) { SvrlContentHandler handler = new SvrlContentHandler();
Stream<CityObject> features = model.createFeatureStream(); Result finalResult = new SAXResult(handler);
float featureSum = model.getNumberOfFeatures();
// stupid lamda with final variable restrictions
int[] currentFeature = new int[1];
features.forEach(co -> {
if (config.getParserConfiguration().useLowMemoryConsumption()) {
// no edges have been created yet, create them
co.prepareForChecking();
}
// check every feature
executeChecksForCityObject(co);
if (config.getParserConfiguration().useLowMemoryConsumption()) { schematronTransformer.transform(cityGmlSource, finalResult);
// low memory consumption, remove edges again return handler;
co.clearMetaInformation(); } catch (TransformerException e) {
} logger.catching(e);
if (l != null) { }
currentFeature[0]++; }
l.updateProgress(currentFeature[0] / featureSum); return null;
} }
});
} private void buildFilters() {
FilterConfiguration filterConfig = config.getFilter();
if (filterConfig == null) {
includeFilters = Collections.emptyList();
excludeFilters = Collections.emptyList();
return;
}
excludeFilters = buildExcludeFilters(filterConfig);
includeFilters = buildIncludeFilters(filterConfig);
}
private boolean filterObject(CityObject co) { private List<Filter> buildExcludeFilters(FilterConfiguration filterConfig) {
return isObjectIncluded(co, includeFilters, excludeFilters); if (filterConfig == null) {
} return Collections.emptyList();
}
ExcludeFilterConfiguration excludeConfig = filterConfig.getExclude();
if (excludeConfig == null) {
return Collections.emptyList();
} else {
List<Filter> filters = new ArrayList<>();
if (excludeConfig.getTypes() != null) {
for (FeatureType excludeType : excludeConfig.getTypes()) {
filters.add(new TypeFilter(excludeType));
}
}
if (excludeConfig.getIds() != null) {
for (String excludePattern : excludeConfig.getIds()) {
Filter f = new EqualsIgnoreCaseFilter(excludePattern);
filters.add(f);
}
}
return filters;
}
}
private boolean isObjectIncluded(CityObject co, List<Filter> includeFilters, List<Filter> excludeFilters) { private List<Filter> buildIncludeFilters(FilterConfiguration filterConfig) {
if (!includeFilters.isEmpty()) { if (filterConfig == null) {
boolean include = false; return Collections.emptyList();
for (Filter f : includeFilters) { }
if (f.matches(co)) { IncludeFilterConfiguration includeConfig = filterConfig.getInclude();
include = true; if (includeConfig == null) {
break; return Collections.emptyList();
} } else {
} List<Filter> filters = new ArrayList<>();
if (!include) { if (includeConfig.getTypes() != null) {
// not included, ignore for (FeatureType includeType : includeConfig.getTypes()) {
return false; filters.add(new TypeFilter(includeType));
} }
} }
// check if object is excluded if (includeConfig.getIds() != null) {
for (Filter f : excludeFilters) { for (String includePattern : includeConfig.getIds()) {
if (f.matches(co)) { Filter f = new EqualsIgnoreCaseFilter(includePattern);
// exclude object filters.add(f);
return false; }
} }
} return filters;
return true; }
} }
/** private void setValidationConfig(ValidationConfiguration config) {
* Checks the city object if it has not been removed by the filters. The check if (config == null) {
* result are stored into the city object itself. throw new IllegalArgumentException("Validation configuration may not be null");
* }
* @param co the city object that is going to be checked this.config = config;
*/ buildFilters();
private void executeChecksForCityObject(CityObject co) { ParserConfiguration parserConfig = config.getParserConfiguration();
if (!filterObject(co)) { List<Check> checks = collectEnabledChecksAndInit(parserConfig, config);
return; execLayers = buildExecutionLayers(checks);
} }
executeChecksForCheckable(co);
} private List<Check> collectEnabledChecksAndInit(ParserConfiguration parserConfig, ValidationConfiguration config) {
Set<CheckId> enabledCheck = new HashSet<>();
Map<CheckId, Map<String, String>> parameterMap = new HashMap<>();
for (Entry<String, RequirementConfiguration> e : config.getRequirements().entrySet()) {
de.hft.stuttgart.citydoctor2.check.Requirement req = Checks.getAvailableRequirements().get(e.getKey());
if (req == null) {
logger.warn(Localization.getText("Checker.noCheckSatisfiesRequirement"),
e.getKey());
} else {
if (e.getValue().isEnabled()) {
// this requirement is enabled
insertGlobalParameters(config, enabledCheck, parameterMap, e, req);
}
}
}
fillParameterMapsWithDefaultParameter(enabledCheck, parameterMap);
ArrayList<Check> checkList = new ArrayList<>();
for (CheckId id : enabledCheck) {
Check c = checkConfig.getCheckForId(id);
c.init(parameterMap, parserConfig);
checkList.add(c);
}
return checkList;
}
private void insertGlobalParameters(ValidationConfiguration config, Set<CheckId> enabledCheck,
Map<CheckId, Map<String, String>> parameterMap, Entry<String, RequirementConfiguration> e,
de.hft.stuttgart.citydoctor2.check.Requirement req) {
for (CheckPrototype proto : Checks.getCheckPrototypes()) {
if (proto.checksRequirements().contains(req)) {
// this requirement is checked by this check
// put all requirement parameter in the map
parameterMap.compute(proto.getCheckId(), (k, v) -> {
if (v == null) {
v = new HashMap<>();
v.put(GlobalParameters.MIN_VERTEX_DISTANCE, config.getMinVertexDistanceAsString());
}
v.putAll(e.getValue().getParameters());
return v;
});
enabledCheck.add(proto.getCheckId());
collectDependencyChecks(proto, enabledCheck);
}
}
}
private void fillParameterMapsWithDefaultParameter(Set<CheckId> enabledCheck,
Map<CheckId, Map<String, String>> parameterMap) {
for (CheckId id : enabledCheck) {
CheckPrototype proto = Checks.getCheckPrototypeForId(id);
Map<String, String> map = parameterMap.computeIfAbsent(id, k -> new HashMap<>());
for (de.hft.stuttgart.citydoctor2.check.Requirement req : proto.checksRequirements()) {
if (proto.checksRequirements().contains(req)) {
for (DefaultParameter param : req.getDefaultParameter()) {
map.computeIfAbsent(param.getName(), k -> param.getValue());
}
}
}
}
}
/** private void collectDependencyChecks(CheckPrototype proto, Set<CheckId> enabledChecks) {
* Executes all checks for the checkable. This will bypass the filters. This enabledChecks.addAll(proto.getDependencies());
* will clear the old check results for (CheckId id : proto.getDependencies()) {
* if (enabledChecks.contains(id)) {
* @param co the checkable. continue;
*/ }
public void executeChecksForCheckable(Checkable co) { CheckPrototype depProto = Checks.getCheckPrototypeForId(id);
// throw away old results collectDependencyChecks(depProto, enabledChecks);
co.clearAllContainedCheckResults(); }
if (logger.isDebugEnabled()) { }
logger.debug(Localization.getText("Checker.checkFeature"), co);
} private void checkCityModel(CityDoctorModel model, ProgressListener l){
List<GmlId> features = model.getFeatureIds();
AtomicInteger checkedCount = new AtomicInteger(0);
// clear global errors
model.getGlobalErrors().removeIf(e -> !e.isParsingError());
CityObjectCache cache = model.getCache();
logger.trace("Setting up ThreadPool");
int threadCount = Runtime.getRuntime().availableProcessors() * 5;
ExecutorService exec = new ThreadPoolExecutor
(threadCount, threadCount, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
try{
long startTime = System.nanoTime();
List<Future<GmlId>> futures = runChecksOnFeatures(exec, cache, features, checkedCount, l);
List<GmlId> missedFeatures = getMissedFeatures(futures);
if (!missedFeatures.isEmpty()){
logger.error(Localization.getText("Checker.dbUnresponsive"));
if (logger.isDebugEnabled()){
logger.debug(missedFeatures.toString());
}
}
long endTime = System.nanoTime();
if (logger.isInfoEnabled()){
long totalTime = (endTime - startTime) / 1_000_000; // Convert ns to ms
String messageString = Localization.getText("Checker.checksFinished");
String timeString = TimeFormatter.format(totalTime);
messageString = messageString.replace("{0}", timeString);
logger.info(messageString);
}
} catch (InterruptedException e){
// No interrupts implemented, so this block should actually never be entered
logger.error(Localization.getText("Checker.validationInterrupted"), e);
Thread.currentThread().interrupt();
} finally {
exec.shutdown();
try {
if(!exec.awaitTermination(1, TimeUnit.MINUTES)){
exec.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error(Localization.getText("Checker.shutdownInterrupted"));
}
}
}
/**
* Runs the validation on a list of Features. The checks are run asynchronously and multithreaded. Returns a list
* of {@link Future Futures} for evaluation if Features failed to be checked due to exceptions or failing to load from
* the cache. If failed, the respective Future will return the affected Feature's GmlID.
* <p>Checks finding an error in a Feature, or not having their dependencies met, are not a check-failure in this
* context.<p/>
* @param exec the threadpool for the tasks
* @param cache the cache of the model
* @param ids the list of GmlIds to check
* @param checkedCount the number of already checked Features for tracking the total progress of validation across retries
* @param l a listener for the progress of the validation, can be null
* @return a list of Futures containing the GmlId of Features that failed to execute all checks, and contain null otherwise
* @throws InterruptedException if interrupted while invoking the tasks for checking
*/
private List<Future<GmlId>> runChecksOnFeatures(ExecutorService exec, CityObjectCache cache, List<GmlId> ids, AtomicInteger checkedCount,
@Nullable ProgressListener l)
throws InterruptedException {
float featureSum = ids.size() + (float) checkedCount.get();
logger.trace("Queueing up Checker tasks");
List<Callable<GmlId>> tasks = new ArrayList<>();
for (GmlId id : ids) {
tasks.add(()->{
CityObject co = cache.get(id);
if (co == null) {
return id;
}
if(Thread.interrupted()){
Thread.currentThread().interrupt();
return id;
}
executeChecksForCityObject(co);
cache.put(co);
checkedCount.incrementAndGet();
if (l!=null){
l.updateProgress((checkedCount.get()) / featureSum);
}
return null;
});
}
logger.trace("Queueing up finished, invoking all tasks");
return exec.invokeAll(tasks);
}
private List<GmlId> getMissedFeatures(List<Future<GmlId>> futures) throws InterruptedException{
List<GmlId> missedList = new ArrayList<>();
Set<String> errors = new HashSet<>();
for (Future<GmlId> future : futures) {
try{
GmlId gmlId = future.get();
if (gmlId != null) {
missedList.add(gmlId);
}
} catch (ExecutionException e){
logger.debug("A Task failed due to an unexpected exception", e);
logger.debug(e.getCause());
errors.add(e.getCause().toString());
}
}
if (!errors.isEmpty()) {
logger.error(Localization.getText("Checker.futuresCheckFailures"));
logger.error(errors.toString());
}
return missedList;
}
private boolean filterObject(CityObject co) {
return isObjectIncluded(co, includeFilters, excludeFilters);
}
private boolean isObjectIncluded(CityObject co, List<Filter> includeFilters, List<Filter> excludeFilters) {
if (!includeFilters.isEmpty()) {
boolean include = false;
for (Filter f : includeFilters) {
if (f.matches(co)) {
include = true;
break;
}
}
if (!include) {
// not included, ignore
return false;
}
}
// check if object is excluded
for (Filter f : excludeFilters) {
if (f.matches(co)) {
// exclude object
return false;
}
}
return true;
}
/**
* Checks the city object if it has not been removed by the filters. The check
* result are stored into the city object itself.
*
* @param co the city object that is going to be checked
*/
private void executeChecksForCityObject(CityObject co) {
if (!filterObject(co)) {
return;
}
co.prepareForChecking();
executeChecksForCheckable(co);
co.clearMetaInformation();
}
/**
* Executes all checks for the checkable. This will bypass the filters. This
* will clear the old check results
*
* @param co the checkable.
*/
public void executeChecksForCheckable(Checkable co) {
// throw away old results
co.clearAllContainedCheckResults();
if (logger.isDebugEnabled()) {
logger.debug(Localization.getText("Checker.checkFeature"), co);
}
for (List<Check> execLayer : execLayers) { for (List<Check> execLayer : execLayers) {
for (Check check : execLayer) { for (Check check : execLayer) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
...@@ -699,133 +870,144 @@ public class Checker { ...@@ -699,133 +870,144 @@ public class Checker {
co.accept(check); co.accept(check);
} }
} }
} }
public static List<List<Check>> buildExecutionLayers(List<Check> checks) { public static List<List<Check>> buildExecutionLayers(List<Check> checks) {
List<List<Check>> result = new ArrayList<>(); List<List<Check>> result = new ArrayList<>();
Set<Check> availableChecks = new HashSet<>(checks); Set<Check> availableChecks = new HashSet<>(checks);
Set<CheckId> usedChecks = new HashSet<>(); Set<CheckId> usedChecks = new HashSet<>();
while (!availableChecks.isEmpty()) { while (!availableChecks.isEmpty()) {
List<Check> layer = new ArrayList<>(); List<Check> layer = new ArrayList<>();
Iterator<Check> iterator = availableChecks.iterator(); Iterator<Check> iterator = availableChecks.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Check c = iterator.next(); Check c = iterator.next();
boolean hasUnusedDependency = searchForUnusedDependency(usedChecks, c); boolean hasUnusedDependency = searchForUnusedDependency(usedChecks, c);
if (!hasUnusedDependency) { if (!hasUnusedDependency) {
iterator.remove(); iterator.remove();
layer.add(new CheckContainer(c)); layer.add(new CheckContainer(c));
}
}
if (layer.isEmpty()) {
throw new IllegalStateException(
"There are checks that have dependencies that are not executed or are unknown, aborting");
}
result.add(layer);
for (Check c : layer) {
usedChecks.add(c.getCheckId());
}
}
return result;
}
private static boolean searchForUnusedDependency(Set<CheckId> usedChecks, Check c) {
boolean hasUnusedDependency = false;
for (CheckId id : c.getDependencies()) {
if (!usedChecks.contains(id)) {
hasUnusedDependency = true;
break;
}
}
return hasUnusedDependency;
}
public static void streamCheck(File inputFile, String xmlOutput, String pdfOutput, ValidationConfiguration config,
String outputFile) throws IOException, CityGmlParseException {
streamCheck(inputFile, xmlOutput, pdfOutput, config, null, outputFile);
}
public static void streamCheck(File inputFile, String xmlOutput, String pdfOutput, ValidationConfiguration config,
FeatureCheckedListener l, String outputFile) throws IOException, CityGmlParseException {
try (BufferedOutputStream xmlBos = getXmlOutputMaybe(xmlOutput);
BufferedOutputStream pdfBos = getPdfOutputMaybe(pdfOutput)) {
Checker c = new Checker(config, null);
String fileName = inputFile.getName();
// create reporter if available
XmlStreamReporter xmlReporter = getXmlReporter(config, xmlBos, fileName);
PdfStreamReporter pdfReporter = getPdfReporter(config, pdfBos, fileName);
// XML validation
if (config.isXmlValidation() && (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);
}
} }
} }
if (layer.isEmpty()) {
throw new IllegalStateException(
"There are checks that have dependencies that are not executed or are unknown, aborting");
}
result.add(layer);
for (Check c : layer) {
usedChecks.add(c.getCheckId());
}
}
return result;
}
private static boolean searchForUnusedDependency(Set<CheckId> usedChecks, Check c) {
boolean hasUnusedDependency = false;
for (CheckId id : c.getDependencies()) {
if (!usedChecks.contains(id)) {
hasUnusedDependency = true;
break;
}
}
return hasUnusedDependency;
}
public static void streamCheck(File inputFile, String xmlOutput, String pdfOutput, ValidationConfiguration config,
String outputFile) throws IOException, CityGmlParseException {
streamCheck(inputFile, xmlOutput, pdfOutput, config, null, outputFile);
}
public static void streamCheck(File inputFile, String xmlOutput, String pdfOutput, ValidationConfiguration config,
FeatureCheckedListener l, String outputFile) throws IOException, CityGmlParseException {
try (BufferedOutputStream xmlBos = getXmlOutputMaybe(xmlOutput);
BufferedOutputStream pdfBos = getPdfOutputMaybe(pdfOutput)) {
Checker c = new Checker(config, null);
String fileName = inputFile.getName();
// create reporter if available
XmlStreamReporter xmlReporter = getXmlReporter(config, xmlBos, fileName);
PdfStreamReporter pdfReporter = getPdfReporter(config, pdfBos, fileName);
// execute schematron first
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile);
CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l);
// parse and validate
CityGmlParser.streamCityGml(inputFile.getAbsolutePath(), config.getParserConfiguration(), con, outputFile);
// write reports if available
writeReport(xmlReporter);
writeReport(pdfReporter);
} catch (CheckReportWriteException e) {
logger.error(Localization.getText("Checker.failReports"), e);
}
}
private static XmlStreamReporter getXmlReporter(ValidationConfiguration config, BufferedOutputStream xmlBos,
String fileName) {
XmlStreamReporter xmlReporter;
if (xmlBos != null) {
xmlReporter = new XmlStreamReporter(xmlBos, fileName, config);
} else {
xmlReporter = null;
}
return xmlReporter;
}
private static PdfStreamReporter getPdfReporter(ValidationConfiguration config, BufferedOutputStream pdfBos,
String fileName) {
PdfStreamReporter pdfReporter;
if (pdfBos != null) {
pdfReporter = new PdfStreamReporter(pdfBos, fileName, config);
} else {
pdfReporter = null;
}
return pdfReporter;
}
public static void writeReport(StreamReporter reporter) throws CheckReportWriteException { // execute schematron first
if (reporter != null) { SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile);
reporter.finishReport(); CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l);
} // parse and validate
} CityGmlParser.streamCityGml(inputFile, config.getParserConfiguration(), con, outputFile);
public static BufferedOutputStream getPdfOutputMaybe(String pdfOutput) throws FileNotFoundException { // write reports if available
return pdfOutput != null ? new BufferedOutputStream(new FileOutputStream(pdfOutput)) : null; writeReport(xmlReporter);
} writeReport(pdfReporter);
} catch (CheckReportWriteException e) {
public static BufferedOutputStream getXmlOutputMaybe(String xmlOutput) throws FileNotFoundException { logger.error(Localization.getText("Checker.failReports"), e);
return xmlOutput != null ? new BufferedOutputStream(new FileOutputStream(xmlOutput)) : null; }
} }
private static XmlStreamReporter getXmlReporter(ValidationConfiguration config, BufferedOutputStream xmlBos,
String fileName) {
XmlStreamReporter xmlReporter;
if (xmlBos != null) {
xmlReporter = new XmlStreamReporter(xmlBos, fileName, config);
} else {
xmlReporter = null;
}
return xmlReporter;
}
private static PdfStreamReporter getPdfReporter(ValidationConfiguration config, BufferedOutputStream pdfBos,
String fileName) {
PdfStreamReporter pdfReporter;
if (pdfBos != null) {
pdfReporter = new PdfStreamReporter(pdfBos, fileName, config);
} else {
pdfReporter = null;
}
return pdfReporter;
}
/** public static void writeReport(StreamReporter reporter) throws CheckReportWriteException {
* Checks the city object and writes report information into reporters. Clears if (reporter != null) {
* old check results. If the city object would be filtered by the configured reporter.finishReport();
* filters it is ignored and old check results are not cleared. }
* }
* @param xmlReporter a xml reporter
* @param pdfReporter a pdf reporter public static BufferedOutputStream getPdfOutputMaybe(String pdfOutput) throws FileNotFoundException {
* @param co the city object to be checked return pdfOutput != null ? new BufferedOutputStream(new FileOutputStream(pdfOutput)) : null;
*/ }
public void checkFeature(XmlStreamReporter xmlReporter, PdfStreamReporter pdfReporter, CityObject co) {
if (logger.isDebugEnabled()) { public static BufferedOutputStream getXmlOutputMaybe(String xmlOutput) throws FileNotFoundException {
logger.debug(Localization.getText("Checker.checkFeature"), co); return xmlOutput != null ? new BufferedOutputStream(new FileOutputStream(xmlOutput)) : null;
} }
executeChecksForCityObject(co);
if (xmlReporter != null) { /**
xmlReporter.report(co); * Checks the city object and writes report information into reporters. Clears
} * old check results. If the city object would be filtered by the configured
if (pdfReporter != null) { * filters it is ignored and old check results are not cleared.
pdfReporter.report(co); *
} * @param xmlReporter a xml reporter
} * @param pdfReporter a pdf reporter
* @param co the city object to be checked
*/
public void checkFeature(XmlStreamReporter xmlReporter, PdfStreamReporter pdfReporter, CityObject co) {
executeChecksForCityObject(co);
if (xmlReporter != null) {
xmlReporter.report(co);
}
if (pdfReporter != null) {
pdfReporter.report(co);
}
}
} }
...@@ -29,8 +29,6 @@ import java.util.Set; ...@@ -29,8 +29,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.AbstractFeatureProperty; import org.citygml4j.core.model.core.AbstractFeatureProperty;
import org.citygml4j.core.model.core.CityModel; import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.core.util.geometry.GeometryFactory; import org.citygml4j.core.util.geometry.GeometryFactory;
...@@ -39,9 +37,12 @@ import org.xmlobjects.gml.model.base.Reference; ...@@ -39,9 +37,12 @@ import org.xmlobjects.gml.model.base.Reference;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError; import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler; import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler;
import de.hft.stuttgart.citydoctor2.checks.util.FeatureCheckedListener; import de.hft.stuttgart.citydoctor2.checks.util.FeatureCheckedListener;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractTunnel;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject; import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.Building; import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityFurniture;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GenericCityObject;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject; import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject; import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation; import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
...@@ -61,8 +62,6 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan; ...@@ -61,8 +62,6 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan;
public class StreamCityGmlConsumer implements CityGmlConsumer { public class StreamCityGmlConsumer implements CityGmlConsumer {
private static final Logger logger = LogManager.getLogger(StreamCityGmlConsumer.class);
private final Checker c; private final Checker c;
private final XmlStreamReporter xmlReporter; private final XmlStreamReporter xmlReporter;
private final PdfStreamReporter pdfReporter; private final PdfStreamReporter pdfReporter;
...@@ -79,7 +78,7 @@ public class StreamCityGmlConsumer implements CityGmlConsumer { ...@@ -79,7 +78,7 @@ public class StreamCityGmlConsumer implements CityGmlConsumer {
private final FeatureStatistics waterStatistics; private final FeatureStatistics waterStatistics;
private final Validation val; private final Validation val;
private final FeatureCheckedListener l; private final FeatureCheckedListener l;
public StreamCityGmlConsumer(Checker c, XmlStreamReporter xmlReporter, PdfStreamReporter pdfReporter, public StreamCityGmlConsumer(Checker c, XmlStreamReporter xmlReporter, PdfStreamReporter pdfReporter,
SvrlContentHandler handler, ValidationConfiguration config, FeatureCheckedListener l) { SvrlContentHandler handler, ValidationConfiguration config, FeatureCheckedListener l) {
this.c = c; this.c = c;
...@@ -113,10 +112,6 @@ public class StreamCityGmlConsumer implements CityGmlConsumer { ...@@ -113,10 +112,6 @@ public class StreamCityGmlConsumer implements CityGmlConsumer {
@Override @Override
public void accept(CityObject co) { public void accept(CityObject co) {
if (logger.isDebugEnabled()) {
logger.debug(Localization.getText("Checker.checkFeature"), co);
}
// check without reporters, nothing will be put into the reports like that // check without reporters, nothing will be put into the reports like that
// this will also clear old check results if any (though in stream mode there // this will also clear old check results if any (though in stream mode there
// won't be any) // won't be any)
...@@ -139,8 +134,12 @@ public class StreamCityGmlConsumer implements CityGmlConsumer { ...@@ -139,8 +134,12 @@ public class StreamCityGmlConsumer implements CityGmlConsumer {
pdfReporter.report(co); pdfReporter.report(co);
} }
// store quality ade datastructures in cityobject // TODO: Update QualityADE to be compatible with CityGML 3.0, unhandled top-level feature types are just skipped for now
QualityADEUtils.writeQualityAde(co, val); if (!(co instanceof AbstractTunnel || co instanceof CityFurniture || co instanceof GenericCityObject)) {
// store quality ade datastructures in cityobject
QualityADEUtils.writeQualityAde(co, val);
}
// recreate geometry // recreate geometry
co.reCreateGeometries(gmlFactory, config.getParserConfiguration()); co.reCreateGeometries(gmlFactory, config.getParserConfiguration());
......
...@@ -53,7 +53,6 @@ public class ValidationConfiguration implements Serializable { ...@@ -53,7 +53,6 @@ public class ValidationConfiguration implements Serializable {
private static final long serialVersionUID = -8020055032177740646L; private static final long serialVersionUID = -8020055032177740646L;
private static final Logger logger = LogManager.getLogger(ValidationConfiguration.class); private static final Logger logger = LogManager.getLogger(ValidationConfiguration.class);
private Map<String, String> globalParameters = new HashMap<>(); private Map<String, String> globalParameters = new HashMap<>();
private boolean xmlValidation = false; private boolean xmlValidation = false;
private boolean useStreaming = false; private boolean useStreaming = false;
...@@ -62,9 +61,16 @@ public class ValidationConfiguration implements Serializable { ...@@ -62,9 +61,16 @@ public class ValidationConfiguration implements Serializable {
private ParserConfiguration parserConfig; private ParserConfiguration parserConfig;
public static ValidationConfiguration loadValidationConfig(String validationFile) throws FileNotFoundException { public static ValidationConfiguration loadValidationConfigFile(String validationFile) throws FileNotFoundException {
return loadValidationConfig(new FileInputStream(validationFile)); return loadValidationConfig(new FileInputStream(validationFile));
} }
public static ValidationConfiguration loadValidationConfigFromString(String validationString) throws FileNotFoundException {
Yaml yaml = new Yaml(new Constructor(ValidationConfiguration.class, new LoaderOptions()));
ValidationConfiguration config = yaml.load(validationString);
config.validateConfiguration();
return config;
}
public static ValidationConfiguration loadValidationConfig(InputStream stream) { public static ValidationConfiguration loadValidationConfig(InputStream stream) {
Yaml yaml = new Yaml(new Constructor(ValidationConfiguration.class, new LoaderOptions())); Yaml yaml = new Yaml(new Constructor(ValidationConfiguration.class, new LoaderOptions()));
...@@ -91,20 +97,30 @@ public class ValidationConfiguration implements Serializable { ...@@ -91,20 +97,30 @@ public class ValidationConfiguration implements Serializable {
config.setSchematronFilePathInGlobalParameters(CHECK_FOR_SOLID_XML); config.setSchematronFilePathInGlobalParameters(CHECK_FOR_SOLID_XML);
return config; return config;
} }
private ValidationConfiguration() { private ValidationConfiguration() {
} }
public void saveAs(File f) throws IOException { public void saveAs(File f) throws IOException {
Yaml yaml = prepareDumpingOfConfig();
try (BufferedWriter bw = new BufferedWriter(new FileWriter(f))) {
yaml.dump(this, bw);
}
}
public String saveAsString() throws IOException {
Yaml yaml = prepareDumpingOfConfig();
return yaml.dump(this);
}
private Yaml prepareDumpingOfConfig() {
DumperOptions options = new DumperOptions(); DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true); options.setPrettyFlow(true);
Representer rep = new ValidationConfigurationRepresenter(options); Representer rep = new ValidationConfigurationRepresenter(options);
rep.addClassTag(ValidationConfiguration.class, Tag.MAP); rep.addClassTag(ValidationConfiguration.class, Tag.MAP);
Yaml yaml = new Yaml(rep); Yaml yaml = new Yaml(rep);
try (BufferedWriter bw = new BufferedWriter(new FileWriter(f))) { return yaml;
yaml.dump(this, bw);
}
} }
public boolean isXmlValidation() { public boolean isXmlValidation() {
...@@ -141,7 +157,7 @@ public class ValidationConfiguration implements Serializable { ...@@ -141,7 +157,7 @@ public class ValidationConfiguration implements Serializable {
public Map<String, String> getGlobalParameters() { public Map<String, String> getGlobalParameters() {
return globalParameters; return globalParameters;
} }
public void setGlobalParameters(Map<String, String> globalParameters) { public void setGlobalParameters(Map<String, String> globalParameters) {
this.globalParameters = globalParameters; this.globalParameters = globalParameters;
} }
...@@ -174,8 +190,7 @@ public class ValidationConfiguration implements Serializable { ...@@ -174,8 +190,7 @@ public class ValidationConfiguration implements Serializable {
}); });
} }
globalParameters.putIfAbsent(GlobalParameters.MIN_VERTEX_DISTANCE, MIN_VERTEX_DISTANCE_DEFAULT); globalParameters.putIfAbsent(GlobalParameters.MIN_VERTEX_DISTANCE, MIN_VERTEX_DISTANCE_DEFAULT);
globalParameters.putIfAbsent(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, globalParameters.putIfAbsent(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, NUMBER_OF_ROUNDING_PLACES_DEFAULT);
NUMBER_OF_ROUNDING_PLACES_DEFAULT);
} }
public int getNumberOfRoundingPlaces() { public int getNumberOfRoundingPlaces() {
...@@ -220,7 +235,7 @@ public class ValidationConfiguration implements Serializable { ...@@ -220,7 +235,7 @@ public class ValidationConfiguration implements Serializable {
public void setSchematronFilePathInGlobalParameters(String string) { public void setSchematronFilePathInGlobalParameters(String string) {
globalParameters.put(GlobalParameters.SCHEMATRON_PATH, string); globalParameters.put(GlobalParameters.SCHEMATRON_PATH, string);
} }
public void setRequirements(Map<String, RequirementConfiguration> requirements) { public void setRequirements(Map<String, RequirementConfiguration> requirements) {
this.requirements = requirements; this.requirements = requirements;
} }
......
...@@ -62,7 +62,7 @@ public class CheckContainer extends Check { ...@@ -62,7 +62,7 @@ public class CheckContainer extends Check {
} }
@Override @Override
public void init(Map<String, String> parameters, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
check.init(parameters, config); check.init(parameters, config);
} }
...@@ -230,6 +230,24 @@ public class CheckContainer extends Check { ...@@ -230,6 +230,24 @@ public class CheckContainer extends Check {
} }
} }
@Override
public void check(TinObject to) {
try {
check.check(to);
}catch (Exception e) {
handleException(e, to);
}
}
@Override
public void check(ReliefObject ro) {
try {
check.check(ro);
}catch (Exception e) {
handleException(e, ro);
}
}
@Override @Override
public void check(CityFurniture cf){ public void check(CityFurniture cf){
try { try {
...@@ -248,6 +266,15 @@ public class CheckContainer extends Check { ...@@ -248,6 +266,15 @@ public class CheckContainer extends Check {
} }
} }
@Override
public void check(OtherConstructionObject oco) {
try {
check.check(oco);
}catch (Exception e) {
handleException(e, oco);
}
}
@Override @Override
public RequirementType getType() { public RequirementType getType() {
return check.getType(); return check.getType();
......
...@@ -106,12 +106,12 @@ public class SvrlContentHandler implements ContentHandler { ...@@ -106,12 +106,12 @@ public class SvrlContentHandler implements ContentHandler {
throw new IllegalStateException( throw new IllegalStateException(
"Schematron File is not formed according to specification for CityDoctor."); "Schematron File is not formed according to specification for CityDoctor.");
} }
String gmlId = split[0]; String gmlId = split[0].strip();
String childId = split[1]; String childId = split[1];
String errorId = split[2]; String errorId = split[2];
String nameOfAttribute = split[3]; String nameOfAttribute = split[3];
SchematronError err = new SchematronError(errorId, gmlId, childId, nameOfAttribute); SchematronError err = new SchematronError(errorId, gmlId, childId, nameOfAttribute);
if (gmlId == null || gmlId.isEmpty()) { if (gmlId == null || gmlId.isBlank()) {
// general error // general error
generalErrors.add(err); generalErrors.add(err);
} else { } else {
......
...@@ -35,6 +35,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; ...@@ -35,6 +35,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation;
import de.hft.stuttgart.citydoctor2.math.Ray; import de.hft.stuttgart.citydoctor2.math.Ray;
import de.hft.stuttgart.citydoctor2.math.Triangle3d; import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.math.Vector3d;
...@@ -76,6 +77,10 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -76,6 +77,10 @@ public class AllPolygonsWrongOrientationCheck extends Check {
return; return;
} }
boolean isOutwardOriented = geometryIsOutwardOrientedJava(g); boolean isOutwardOriented = geometryIsOutwardOrientedJava(g);
// invert outward direction for inward facing geometries
if (g.getOrientation() == Orientation.INWARD) {
isOutwardOriented = !isOutwardOriented;
}
CheckResult cr; CheckResult cr;
if (isOutwardOriented) { if (isOutwardOriented) {
cr = new CheckResult(this, ResultStatus.OK, null); cr = new CheckResult(this, ResultStatus.OK, null);
...@@ -113,13 +118,15 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -113,13 +118,15 @@ public class AllPolygonsWrongOrientationCheck extends Check {
// find the centroid of a triangle // find the centroid of a triangle
Vector3d centroid = t.getCentroid(); Vector3d centroid = t.getCentroid();
// create a point outside of the geometry // create a point outside of the geometry
Vector3d outsidePoint = bbox[0].minus(Vector3d.X, 5).minus(Vector3d.Y, 5).minus(Vector3d.Z, 5); Vector3d outsideDif1 = new Vector3d(5, 5, 5);
Vector3d outsidePoint = bbox[0].minus(outsideDif1);
// create a second point outside of the geometry // create a second point outside of the geometry
// the check can fail if the building is exactly oriented so that the ray is // the check can fail if the building is exactly oriented so that the ray is
// parallel to a side of the building // parallel to a side of the building
// in order to avoid this we check two rays, if one of those says it is oriented // in order to avoid this we check two rays, if one of those says it is oriented
// correctly it is oriented correctly // correctly it is oriented correctly
Vector3d secondOutsidePoint = bbox[0].minus(Vector3d.X, 5).minus(Vector3d.Y, 10).minus(Vector3d.Z, 5); Vector3d outsideDif2 = new Vector3d(7, 10, 5);
Vector3d secondOutsidePoint = bbox[0].minus(outsideDif2);
return checkIfGeometryIsWrongOriented(tessPolygons, centroid, outsidePoint) return checkIfGeometryIsWrongOriented(tessPolygons, centroid, outsidePoint)
|| checkIfGeometryIsWrongOriented(tessPolygons, centroid, secondOutsidePoint); || checkIfGeometryIsWrongOriented(tessPolygons, centroid, secondOutsidePoint);
} }
...@@ -159,8 +166,8 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -159,8 +166,8 @@ public class AllPolygonsWrongOrientationCheck extends Check {
} }
private Triangle3d findSuitableTriangle(TesselatedPolygon p1) { private Triangle3d findSuitableTriangle(TesselatedPolygon p1) {
Triangle3d t = p1.getTriangles().get(0); Triangle3d t = null;
double maxArea = t.getArea(); double maxArea = Double.NEGATIVE_INFINITY;
for (Triangle3d tri : p1.getTriangles()) { for (Triangle3d tri : p1.getTriangles()) {
double area = tri.getArea(); double area = tri.getArea();
if (area > maxArea) { if (area > maxArea) {
...@@ -168,6 +175,9 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -168,6 +175,9 @@ public class AllPolygonsWrongOrientationCheck extends Check {
t = tri; t = tri;
} }
} }
if (t == null) {
throw new IllegalStateException("No suitable triangle found");
}
return t; return t;
} }
......
...@@ -30,10 +30,12 @@ import de.hft.stuttgart.citydoctor2.check.CheckId; ...@@ -30,10 +30,12 @@ import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.RequirementType; import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.Checkable; import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.GlobalParameters;
import de.hft.stuttgart.citydoctor2.check.Requirement; import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError; import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
import de.hft.stuttgart.citydoctor2.check.error.RingDuplicatePointError; import de.hft.stuttgart.citydoctor2.check.error.RingDuplicatePointError;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
...@@ -52,8 +54,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -52,8 +54,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/ */
public class DuplicatePointsCheck extends Check { public class DuplicatePointsCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
static { static {
...@@ -66,11 +66,16 @@ public class DuplicatePointsCheck extends Check { ...@@ -66,11 +66,16 @@ public class DuplicatePointsCheck extends Check {
classes.add(LinearRing.class); classes.add(LinearRing.class);
} }
private double epsilon = 0.0001; private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
@Override @Override
public void init(Map<String, String> params, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME); Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) { if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString); epsilon = Double.parseDouble(epsilonString);
} }
......
...@@ -29,10 +29,12 @@ import de.hft.stuttgart.citydoctor2.check.Check; ...@@ -29,10 +29,12 @@ import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId; import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.GlobalParameters;
import de.hft.stuttgart.citydoctor2.check.RequirementType; import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.Requirement; import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.PolygonInteriorDisconnectedError; import de.hft.stuttgart.citydoctor2.check.error.PolygonInteriorDisconnectedError;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
...@@ -49,8 +51,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -49,8 +51,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/ */
public class InteriorDisconnectedCheck extends Check { public class InteriorDisconnectedCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
static { static {
...@@ -63,11 +63,16 @@ public class InteriorDisconnectedCheck extends Check { ...@@ -63,11 +63,16 @@ public class InteriorDisconnectedCheck extends Check {
dependencies = Collections.unmodifiableList(deps); dependencies = Collections.unmodifiableList(deps);
} }
private double epsilon = 0.0001; private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
@Override @Override
public void init(Map<String, String> params, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME); Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) { if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString); epsilon = Double.parseDouble(epsilonString);
} }
......
...@@ -44,9 +44,10 @@ import de.hft.stuttgart.citydoctor2.tesselation.TesselatedRing; ...@@ -44,9 +44,10 @@ import de.hft.stuttgart.citydoctor2.tesselation.TesselatedRing;
public class NullAreaCheck extends Check { public class NullAreaCheck extends Check {
private static final String DELTA_NAME = "delta"; private static final String DELTA_NAME = "delta";
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
private double delta = 0.0001;
static { static {
ArrayList<CheckId> deps = new ArrayList<>(); ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS); deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
...@@ -55,12 +56,16 @@ public class NullAreaCheck extends Check { ...@@ -55,12 +56,16 @@ public class NullAreaCheck extends Check {
dependencies = Collections.unmodifiableList(deps); dependencies = Collections.unmodifiableList(deps);
} }
private double delta = 0.0001;
@Override @Override
public void init(Map<String, String> parameters, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
if (parameters.containsKey(DELTA_NAME)) { Map<String, String> localParameters = params.get(getCheckId());
delta = Double.parseDouble(parameters.get(DELTA_NAME)); if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(DELTA_NAME);
if (epsilonString != null) {
delta = Double.parseDouble(epsilonString);
} }
} }
......
...@@ -57,10 +57,12 @@ import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon; ...@@ -57,10 +57,12 @@ import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
*/ */
public class PlanarCheck extends Check { public class PlanarCheck extends Check {
private static final String DISTANCE = "distance"; public static final String ANGLE = "angle";
private static final String DISTANCE_TOLERANCE = "distanceTolerance"; public static final String DISTANCE = "distance";
public static final String BOTH = "both";
public static final String DISTANCE_TOLERANCE = "distanceTolerance";
private static final String ANGLE_TOLERANCE = "angleTolerance"; private static final String ANGLE_TOLERANCE = "angleTolerance";
private static final String TYPE = "type"; public static final String TYPE = "type";
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
...@@ -79,17 +81,22 @@ public class PlanarCheck extends Check { ...@@ -79,17 +81,22 @@ public class PlanarCheck extends Check {
private double delta = 0.01; private double delta = 0.01;
@Override @Override
public void init(Map<String, String> parameters, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
if (parameters.containsKey(TYPE)) { Map<String, String> localParameters = parameters.get(getCheckId());
planarCheckType = parameters.get(TYPE).toLowerCase(); if (localParameters == null) {
// no parameters
return;
}
if (localParameters.containsKey(TYPE)) {
planarCheckType = localParameters.get(TYPE).toLowerCase();
} else { } else {
throw new IllegalStateException("Parameter " + TYPE + " is missing from parameters"); throw new IllegalStateException("Parameter " + TYPE + " is missing from parameters");
} }
if (parameters.containsKey(ANGLE_TOLERANCE)) { if (localParameters.containsKey(ANGLE_TOLERANCE)) {
rad = Math.toRadians(Double.parseDouble(parameters.get(ANGLE_TOLERANCE))); rad = Math.toRadians(Double.parseDouble(localParameters.get(ANGLE_TOLERANCE)));
} }
if (parameters.containsKey(DISTANCE_TOLERANCE)) { if (localParameters.containsKey(DISTANCE_TOLERANCE)) {
delta = Double.parseDouble(parameters.get(DISTANCE_TOLERANCE)); delta = Double.parseDouble(localParameters.get(DISTANCE_TOLERANCE));
} }
} }
...@@ -97,7 +104,7 @@ public class PlanarCheck extends Check { ...@@ -97,7 +104,7 @@ public class PlanarCheck extends Check {
public void check(Polygon p) { public void check(Polygon p) {
if (DISTANCE.equals(planarCheckType)) { if (DISTANCE.equals(planarCheckType)) {
planarDistance(p); planarDistance(p);
} else if ("angle".equals(planarCheckType)) { } else if (ANGLE.equals(planarCheckType)) {
planarNormalDeviation(p); planarNormalDeviation(p);
} else if ("both".equals(planarCheckType)) { } else if ("both".equals(planarCheckType)) {
planarDistance(p); planarDistance(p);
......
...@@ -29,6 +29,7 @@ import de.hft.stuttgart.citydoctor2.check.Check; ...@@ -29,6 +29,7 @@ import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId; import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.GlobalParameters;
import de.hft.stuttgart.citydoctor2.check.Requirement; import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType; import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
...@@ -59,15 +60,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -59,15 +60,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/ */
public class RingSelfIntCheck extends Check { public class RingSelfIntCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance"; private static final List<CheckId> dependencies;
// check requirement class for default parameters // check requirement class for default parameters
private double degeneratedRingTolerance = 0.01; private double degeneratedRingTolerance = 0.01;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT; private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private static final List<CheckId> dependencies;
static { static {
ArrayList<CheckId> deps = new ArrayList<>(); ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS); deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
...@@ -78,13 +76,18 @@ public class RingSelfIntCheck extends Check { ...@@ -78,13 +76,18 @@ public class RingSelfIntCheck extends Check {
@Override @Override
public void init(Map<String, String> parameters, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
String epsilonString = parameters.get(EPSILON_NAME); Map<String, String> localParameters = parameters.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) { if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString); epsilon = Double.parseDouble(epsilonString);
} }
if (parameters.containsKey(Requirement.DEGENERATED_RING_TOLERANCE)) { if (localParameters.containsKey(Requirement.DEGENERATED_RING_TOLERANCE)) {
degeneratedRingTolerance = Double.parseDouble(parameters.get(Requirement.DEGENERATED_RING_TOLERANCE)); degeneratedRingTolerance = Double.parseDouble(localParameters.get(Requirement.DEGENERATED_RING_TOLERANCE));
} }
} }
......
...@@ -21,13 +21,13 @@ package de.hft.stuttgart.citydoctor2.checks.geometry; ...@@ -21,13 +21,13 @@ package de.hft.stuttgart.citydoctor2.checks.geometry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import de.hft.stuttgart.citydoctor2.check.Check; import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId; import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.GeometrySelfIntersection;
import de.hft.stuttgart.citydoctor2.check.Requirement; import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType; import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
...@@ -36,6 +36,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils; ...@@ -36,6 +36,7 @@ import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
/** /**
...@@ -47,7 +48,9 @@ import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; ...@@ -47,7 +48,9 @@ import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
public class SolidSelfIntCheck extends Check { public class SolidSelfIntCheck extends Check {
private static final List<CheckId> dependencies; private static final List<CheckId> dependencies;
private double delta = 0.01;
static { static {
ArrayList<CheckId> deps = new ArrayList<>(); ArrayList<CheckId> deps = new ArrayList<>();
dependencies = Collections.unmodifiableList(deps); dependencies = Collections.unmodifiableList(deps);
...@@ -68,6 +71,19 @@ public class SolidSelfIntCheck extends Check { ...@@ -68,6 +71,19 @@ public class SolidSelfIntCheck extends Check {
deps.add(CheckId.C_GE_S_NON_MANIFOLD_VERTEX); deps.add(CheckId.C_GE_S_NON_MANIFOLD_VERTEX);
deps.add(CheckId.C_GE_S_POLYGON_WRONG_ORIENTATION); deps.add(CheckId.C_GE_S_POLYGON_WRONG_ORIENTATION);
} }
@Override
public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
Map<String, String> planarParameters = parameters.get(CheckId.C_GE_P_NON_PLANAR);
if (planarParameters == null) {
// no parameters
return;
}
planarParameters.computeIfPresent(PlanarCheck.DISTANCE_TOLERANCE, (k, v) -> {
delta = Double.parseDouble(v);
return v;
});
}
@Override @Override
public void check(Geometry g) { public void check(Geometry g) {
...@@ -75,7 +91,7 @@ public class SolidSelfIntCheck extends Check { ...@@ -75,7 +91,7 @@ public class SolidSelfIntCheck extends Check {
return; return;
} }
CheckResult cr; CheckResult cr;
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g); List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta);
if (intersections.isEmpty()) { if (intersections.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null); cr = new CheckResult(this, ResultStatus.OK, null);
} else { } else {
...@@ -85,18 +101,6 @@ public class SolidSelfIntCheck extends Check { ...@@ -85,18 +101,6 @@ public class SolidSelfIntCheck extends Check {
g.addCheckResult(cr); g.addCheckResult(cr);
} }
@SuppressWarnings("unused")
private CheckResult oldIntersectionAlgorithm(Geometry g) {
CheckResult cr;
GeometrySelfIntersection intersect = SelfIntersectionUtil.doesSolidSelfIntersect(g);
if (intersect != null) {
cr = new CheckResult(this, ResultStatus.ERROR, null);
} else {
cr = new CheckResult(this, ResultStatus.OK, null);
}
return cr;
}
@Override @Override
public List<CheckId> getDependencies() { public List<CheckId> getDependencies() {
return dependencies; return dependencies;
......
...@@ -75,13 +75,18 @@ public class IsWallCheck extends Check { ...@@ -75,13 +75,18 @@ public class IsWallCheck extends Check {
private double upperAngleCos = Math.cos(135 * Math.PI / 180); private double upperAngleCos = Math.cos(135 * Math.PI / 180);
@Override @Override
public void init(Map<String, String> params, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
String lowerAngleString = params.get(LOWER_ANGLE_NAME); Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String lowerAngleString = localParameters.get(LOWER_ANGLE_NAME);
if (lowerAngleString != null) { if (lowerAngleString != null) {
lowerAngleCos = Double.parseDouble(lowerAngleString); lowerAngleCos = Double.parseDouble(lowerAngleString);
lowerAngleCos = Math.cos(lowerAngleCos * Math.PI / 180); lowerAngleCos = Math.cos(lowerAngleCos * Math.PI / 180);
} }
String upperAngleString = params.get(UPPER_ANGLE_NAME); String upperAngleString = localParameters.get(UPPER_ANGLE_NAME);
if (upperAngleString != null) { if (upperAngleString != null) {
upperAngleCos = Double.parseDouble(upperAngleString); upperAngleCos = Double.parseDouble(upperAngleString);
upperAngleCos = Math.cos(upperAngleCos * Math.PI / 180); upperAngleCos = Math.cos(upperAngleCos * Math.PI / 180);
......
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