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 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.16.0</version>
<version>3.18.3</version>
</parent>
<artifactId>CityDoctorValidation</artifactId>
<name>CityDoctorValidation</name>
......
#!/bin/sh
./runtime/bin/java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml
\ 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
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
\ No newline at end of file
#!/bin/sh
java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml
\ No newline at end of file
java -classpath app/*:plugin/* de.hft.stuttgart.citydoctor2.CityDoctorValidation -in REKaiserwall.gml -config testConfigWithStreaming.yml -xmlReport output.xml -db_location #!/bin/sh
./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
\ No newline at end of file
......@@ -23,9 +23,10 @@ import java.io.FileNotFoundException;
import java.io.IOException;
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.Logger;
import org.citygml4j.core.ade.ADEException;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
......@@ -70,9 +71,11 @@ public class CityDoctorValidation {
String xmlOutput = getXmlOutput(argParser);
String pdfOutput = getPdfOutput(argParser);
String outputFile = getOutputFile(argParser, true);
lookForDbConfigFileParameter(argParser);
lookForDbLocationParameter(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) {
......@@ -81,18 +84,42 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
logger.error("No output file specified. (-out [FILE])");
logger.fatal(Localization.getText("CityDoctorValidation.noOutputFile"));
System.exit(11);
}
List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
logger.error("Specify exactly one file as output.");
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile"));
System.exit(12);
}
outputFile = outFiles.get(0);
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.
*
......@@ -133,7 +160,7 @@ public class CityDoctorValidation {
config = ValidationConfiguration.loadStandardValidationConfig();
config.setUseStreaming(true);
} else {
config = ValidationConfiguration.loadValidationConfig(validationConfigFile.getAbsolutePath());
config = ValidationConfiguration.loadValidationConfigFile(validationConfigFile.getAbsolutePath());
}
String xmlOutputPath = null;
if (xmlOutput != null) {
......@@ -144,13 +171,29 @@ public class CityDoctorValidation {
pdfOutputPath = pdfOutput.getAbsolutePath();
}
try {
startValidationProcess(inputFile, xmlOutputPath, pdfOutputPath, config, null);
startFileValidation(inputFile, xmlOutputPath, pdfOutputPath, config, null);
} catch (CityDoctorWriteException e) {
// this does not happen as no output file is specified
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
*
......@@ -159,12 +202,13 @@ public class CityDoctorValidation {
* @param pdfOutput the output path for the pdf report (optional)
* @param config the configuration path for the validation plan
* @param outputFile storing the validated gml file with quality ade
* @throws CityDoctorWriteException if something goes wrong while writing the gml
* file
* @throws CityGMLBuilderException
* @throws ADEException
*
* @throws IOException When an exception while writing the report files happens.
* @throws CityGmlParseException When an error while parsing the cityGML file happens.
* @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,
InvalidGmlFileException, CityDoctorWriteException {
......@@ -185,7 +229,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("pdfreport")) {
List<String> reportFiles = argParser.getValues("pdfreport");
if (reportFiles.size() != 1) {
logger.error("Specify exactly one file as pdfReport");
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
System.exit(6);
}
return reportFiles.get(0);
......@@ -197,7 +241,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("xmlreport")) {
List<String> reportFiles = argParser.getValues("xmlreport");
if (reportFiles.size() != 1) {
logger.error("Specify exactly one file as xmlReport");
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
System.exit(5);
}
return reportFiles.get(0);
......@@ -214,16 +258,16 @@ public class CityDoctorValidation {
if (argParser.containsOption("config")) {
List<String> configFiles = argParser.getValues("config");
if (configFiles.size() != 1) {
logger.error("Specify exactly one file as configuration");
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
System.exit(4);
}
return ValidationConfiguration.loadValidationConfig(configFiles.get(0));
return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0));
} else {
if (optional) {
logger.warn("No configuration file given, loading default configuration");
logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig"));
return ValidationConfiguration.loadStandardValidationConfig();
} else {
logger.error("No configuration file specified with -config [FILE]");
logger.fatal(Localization.getText("CityDoctorValidation.noConfig"));
System.exit(7);
return null;
}
......@@ -235,7 +279,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("out")) {
List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
logger.error("Specify exactly one file as output.");
logger.fatal(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
System.exit(3);
}
return outFiles.get(0);
......@@ -254,12 +298,12 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
logger.error("No input file specified. (-in [FILE])");
logger.fatal(Localization.getText("CityDoctorValidation.noInputFile"));
System.exit(1);
}
List<String> inFiles = argParser.getValues("in");
if (inFiles.size() != 1) {
logger.error("Specify exactly one file as input.");
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
System.exit(2);
}
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);
}
}
......@@ -26,6 +26,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
......@@ -35,8 +37,17 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.transform.Result;
import javax.xml.transform.Source;
......@@ -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.AttributeValueWrongError;
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.checks.CheckContainer;
import de.hft.stuttgart.citydoctor2.checks.CheckPrototype;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler;
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.CityObject;
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.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
......@@ -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.PdfStreamReporter;
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.TopLevelFeatureType;
import de.hft.stuttgart.quality.model.properties.CheckingProperty;
......@@ -92,7 +107,6 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan;
* as well as contains the state of the checks performed.
*
* @author Matthias Betz
*
*/
public class Checker {
......@@ -136,8 +150,8 @@ public class Checker {
return;
}
File xmlFile = new File(xmlOutput);
if (xmlFile.getParentFile() != null) {
xmlFile.getParentFile().mkdirs();
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()))) {
......@@ -152,9 +166,10 @@ public class Checker {
return;
}
File pdfFile = new File(pdfOutput);
if (pdfFile.getParentFile() != null) {
pdfFile.getParentFile().mkdirs();
if (pdfFile.getParentFile() != null && pdfFile.getParentFile().mkdirs()){
logger.trace("Pdf dir created");
}
Reporter reporter = new PdfReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config);
......@@ -188,38 +203,87 @@ public class Checker {
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 getSchematronResults(Future<SvrlContentHandler> handlerFuture, ProgressListener l) {
if (!handlerFuture.isDone()) {
if (logger.isInfoEnabled()) {
logger.info(Localization.getText("Checker.checksFinished"));
logger.info(Localization.getText("Checker.schematronStillRunning"));
}
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, model.getFile());
if(l !=null){
l.updateProgress(-0.5f);
}
}
try {
SvrlContentHandler handler = handlerFuture.get();
if (handler != null) {
handleSchematronResults(handler);
}
model.setValidated(createValidationPlan());
} catch (ExecutionException e) {
logger.error(e);
} catch (InterruptedException e) {
logger.error(Localization.getText("Checker.schematronInterrupted"), e);
Thread.currentThread().interrupt();
}
}
private void handleSchematronResults(SvrlContentHandler handler) {
model.addGlobalErrors(handler.getGeneralErrors());
Map<String, CityObject> featureMap = new HashMap<>();
model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f));
handler.getFeatureErrors().forEach((k, v) -> {
if (k.trim().isEmpty()) {
// missing gml id, ignore?
return;
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));
}
CityObject co = featureMap.get(k);
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
for (SchematronError se : v) {
model.addGlobalError(se);
}
handleSchematronErrorsGlobal(v);
} else {
handleSchematronErrorsForCityObject(v, co);
co.setValidated(true);
}
});
}
private void handleSchematronErrorsGlobal(List<SchematronError> v) {
for (SchematronError se : v) {
CheckError err;
if (AttributeMissingError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeMissingError(null, se.getChildId(), se.getNameOfAttribute());
} else if (AttributeValueWrongError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeValueWrongError(null, se.getChildId(), se.getNameOfAttribute());
} else if (AttributeInvalidError.ID.getIdString().equals(se.getErrorIdString())) {
err = new AttributeInvalidError(null, se.getChildId(), se.getNameOfAttribute());
} else {
throw new IllegalStateException(
"Unknown error ID was given in schematron file: " + se.getErrorIdString());
}
model.addGlobalError(err);
}
}
public static void handleSchematronErrorsForCityObject(List<SchematronError> v, CityObject co) {
int count = 0;
for (SchematronError se : v) {
......@@ -261,6 +325,8 @@ public class Checker {
DefaultParameter defaultP = getDefaultParameter(e.getKey(), reqs, param.getKey());
if (defaultP != null) {
p.setUom(defaultP.getUnitType().getGmlRepresentation());
} else {
p.setUom("");
}
p.setName(param.getKey());
p.setValue(param.getValue());
......@@ -278,21 +344,28 @@ public class Checker {
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());
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;
}
......@@ -423,13 +496,16 @@ public class Checker {
}
}
@SuppressWarnings("resource")
public static SvrlContentHandler executeSchematronValidationIfAvailable(ValidationConfiguration config,
InputStream in) {
if (config.getSchematronFilePath() != null && !config.getSchematronFilePath().isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info(Localization.getText("Checker.schematronValidation"));
}
if (!Files.exists(Path.of(config.getSchematronFilePath()))) {
logger.warn(Localization.getText("Checker.schematronFileNotFound"), config.getSchematronFilePath());
return null;
}
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", Checker.class.getClassLoader());
......@@ -547,7 +623,7 @@ public class Checker {
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",
logger.warn(Localization.getText("Checker.noCheckSatisfiesRequirement"),
e.getKey());
} else {
if (e.getValue().isEnabled()) {
......@@ -560,7 +636,7 @@ public class Checker {
ArrayList<Check> checkList = new ArrayList<>();
for (CheckId id : enabledCheck) {
Check c = checkConfig.getCheckForId(id);
c.init(parameterMap.get(id), parserConfig);
c.init(parameterMap, parserConfig);
checkList.add(c);
}
return checkList;
......@@ -576,7 +652,6 @@ public class Checker {
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());
......@@ -614,29 +689,123 @@ public class Checker {
}
}
private void checkCityModel(CityDoctorModel model, ProgressListener l) {
Stream<CityObject> features = model.createFeatureStream();
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();
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());
}
// check every feature
executeChecksForCityObject(co);
}
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();
if (config.getParserConfiguration().useLowMemoryConsumption()) {
// low memory consumption, remove edges again
co.clearMetaInformation();
} 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 (l != null) {
currentFeature[0]++;
l.updateProgress(currentFeature[0] / featureSum);
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);
......@@ -676,7 +845,9 @@ public class Checker {
if (!filterObject(co)) {
return;
}
co.prepareForChecking();
executeChecksForCheckable(co);
co.clearMetaInformation();
}
/**
......@@ -757,11 +928,25 @@ public class Checker {
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);
}
}
}
// 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);
CityGmlParser.streamCityGml(inputFile, config.getParserConfiguration(), con, outputFile);
// write reports if available
writeReport(xmlReporter);
......@@ -817,9 +1002,6 @@ public class Checker {
* @param co the city object to be checked
*/
public void checkFeature(XmlStreamReporter xmlReporter, PdfStreamReporter pdfReporter, CityObject co) {
if (logger.isDebugEnabled()) {
logger.debug(Localization.getText("Checker.checkFeature"), co);
}
executeChecksForCityObject(co);
if (xmlReporter != null) {
xmlReporter.report(co);
......
......@@ -29,8 +29,6 @@ import java.util.Set;
import java.util.UUID;
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.CityModel;
import org.citygml4j.core.util.geometry.GeometryFactory;
......@@ -39,9 +37,12 @@ import org.xmlobjects.gml.model.base.Reference;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.checks.SvrlContentHandler;
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.Building;
import de.hft.stuttgart.citydoctor2.datastructure.CityFurniture;
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.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
......@@ -61,8 +62,6 @@ import de.hft.stuttgart.quality.model.types.ValidationPlan;
public class StreamCityGmlConsumer implements CityGmlConsumer {
private static final Logger logger = LogManager.getLogger(StreamCityGmlConsumer.class);
private final Checker c;
private final XmlStreamReporter xmlReporter;
private final PdfStreamReporter pdfReporter;
......@@ -113,10 +112,6 @@ public class StreamCityGmlConsumer implements CityGmlConsumer {
@Override
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
// this will also clear old check results if any (though in stream mode there
// won't be any)
......@@ -139,8 +134,12 @@ public class StreamCityGmlConsumer implements CityGmlConsumer {
pdfReporter.report(co);
}
// TODO: Update QualityADE to be compatible with CityGML 3.0, unhandled top-level feature types are just skipped for now
if (!(co instanceof AbstractTunnel || co instanceof CityFurniture || co instanceof GenericCityObject)) {
// store quality ade datastructures in cityobject
QualityADEUtils.writeQualityAde(co, val);
}
// recreate geometry
co.reCreateGeometries(gmlFactory, config.getParserConfiguration());
......
......@@ -53,7 +53,6 @@ public class ValidationConfiguration implements Serializable {
private static final long serialVersionUID = -8020055032177740646L;
private static final Logger logger = LogManager.getLogger(ValidationConfiguration.class);
private Map<String, String> globalParameters = new HashMap<>();
private boolean xmlValidation = false;
private boolean useStreaming = false;
......@@ -62,10 +61,17 @@ public class ValidationConfiguration implements Serializable {
private ParserConfiguration parserConfig;
public static ValidationConfiguration loadValidationConfig(String validationFile) throws FileNotFoundException {
public static ValidationConfiguration loadValidationConfigFile(String validationFile) throws FileNotFoundException {
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) {
Yaml yaml = new Yaml(new Constructor(ValidationConfiguration.class, new LoaderOptions()));
ValidationConfiguration config = yaml.load(stream);
......@@ -96,15 +102,25 @@ public class ValidationConfiguration implements Serializable {
}
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();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
Representer rep = new ValidationConfigurationRepresenter(options);
rep.addClassTag(ValidationConfiguration.class, Tag.MAP);
Yaml yaml = new Yaml(rep);
try (BufferedWriter bw = new BufferedWriter(new FileWriter(f))) {
yaml.dump(this, bw);
}
return yaml;
}
public boolean isXmlValidation() {
......@@ -174,8 +190,7 @@ public class ValidationConfiguration implements Serializable {
});
}
globalParameters.putIfAbsent(GlobalParameters.MIN_VERTEX_DISTANCE, MIN_VERTEX_DISTANCE_DEFAULT);
globalParameters.putIfAbsent(GlobalParameters.NUMBER_OF_ROUNDING_PLACES,
NUMBER_OF_ROUNDING_PLACES_DEFAULT);
globalParameters.putIfAbsent(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, NUMBER_OF_ROUNDING_PLACES_DEFAULT);
}
public int getNumberOfRoundingPlaces() {
......
......@@ -62,7 +62,7 @@ public class CheckContainer extends Check {
}
@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);
}
......@@ -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
public void check(CityFurniture cf){
try {
......@@ -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
public RequirementType getType() {
return check.getType();
......
......@@ -106,12 +106,12 @@ public class SvrlContentHandler implements ContentHandler {
throw new IllegalStateException(
"Schematron File is not formed according to specification for CityDoctor.");
}
String gmlId = split[0];
String gmlId = split[0].strip();
String childId = split[1];
String errorId = split[2];
String nameOfAttribute = split[3];
SchematronError err = new SchematronError(errorId, gmlId, childId, nameOfAttribute);
if (gmlId == null || gmlId.isEmpty()) {
if (gmlId == null || gmlId.isBlank()) {
// general error
generalErrors.add(err);
} else {
......
......@@ -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.GeometryType;
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.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
......@@ -76,6 +77,10 @@ public class AllPolygonsWrongOrientationCheck extends Check {
return;
}
boolean isOutwardOriented = geometryIsOutwardOrientedJava(g);
// invert outward direction for inward facing geometries
if (g.getOrientation() == Orientation.INWARD) {
isOutwardOriented = !isOutwardOriented;
}
CheckResult cr;
if (isOutwardOriented) {
cr = new CheckResult(this, ResultStatus.OK, null);
......@@ -113,13 +118,15 @@ public class AllPolygonsWrongOrientationCheck extends Check {
// find the centroid of a triangle
Vector3d centroid = t.getCentroid();
// 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
// the check can fail if the building is exactly oriented so that the ray is
// parallel to a side of the building
// in order to avoid this we check two rays, if one of those says it is oriented
// 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)
|| checkIfGeometryIsWrongOriented(tessPolygons, centroid, secondOutsidePoint);
}
......@@ -159,8 +166,8 @@ public class AllPolygonsWrongOrientationCheck extends Check {
}
private Triangle3d findSuitableTriangle(TesselatedPolygon p1) {
Triangle3d t = p1.getTriangles().get(0);
double maxArea = t.getArea();
Triangle3d t = null;
double maxArea = Double.NEGATIVE_INFINITY;
for (Triangle3d tri : p1.getTriangles()) {
double area = tri.getArea();
if (area > maxArea) {
......@@ -168,6 +175,9 @@ public class AllPolygonsWrongOrientationCheck extends Check {
t = tri;
}
}
if (t == null) {
throw new IllegalStateException("No suitable triangle found");
}
return t;
}
......
......@@ -30,10 +30,12 @@ import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
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.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.ConsecutivePointSameError;
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.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
......@@ -52,8 +54,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/
public class DuplicatePointsCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies;
static {
......@@ -66,11 +66,16 @@ public class DuplicatePointsCheck extends Check {
classes.add(LinearRing.class);
}
private double epsilon = 0.0001;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME);
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
......
......@@ -29,10 +29,12 @@ import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
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.Requirement;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
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.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
......@@ -49,8 +51,6 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/
public class InteriorDisconnectedCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies;
static {
......@@ -63,11 +63,16 @@ public class InteriorDisconnectedCheck extends Check {
dependencies = Collections.unmodifiableList(deps);
}
private double epsilon = 0.0001;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String epsilonString = params.get(EPSILON_NAME);
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
......
......@@ -44,9 +44,10 @@ import de.hft.stuttgart.citydoctor2.tesselation.TesselatedRing;
public class NullAreaCheck extends Check {
private static final String DELTA_NAME = "delta";
private static final List<CheckId> dependencies;
private double delta = 0.0001;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
......@@ -55,12 +56,16 @@ public class NullAreaCheck extends Check {
dependencies = Collections.unmodifiableList(deps);
}
private double delta = 0.0001;
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
if (parameters.containsKey(DELTA_NAME)) {
delta = Double.parseDouble(parameters.get(DELTA_NAME));
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
Map<String, String> localParameters = params.get(getCheckId());
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;
*/
public class PlanarCheck extends Check {
private static final String DISTANCE = "distance";
private static final String DISTANCE_TOLERANCE = "distanceTolerance";
public static final String ANGLE = "angle";
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 TYPE = "type";
public static final String TYPE = "type";
private static final List<CheckId> dependencies;
......@@ -79,17 +81,22 @@ public class PlanarCheck extends Check {
private double delta = 0.01;
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
if (parameters.containsKey(TYPE)) {
planarCheckType = parameters.get(TYPE).toLowerCase();
public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
Map<String, String> localParameters = parameters.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
if (localParameters.containsKey(TYPE)) {
planarCheckType = localParameters.get(TYPE).toLowerCase();
} else {
throw new IllegalStateException("Parameter " + TYPE + " is missing from parameters");
}
if (parameters.containsKey(ANGLE_TOLERANCE)) {
rad = Math.toRadians(Double.parseDouble(parameters.get(ANGLE_TOLERANCE)));
if (localParameters.containsKey(ANGLE_TOLERANCE)) {
rad = Math.toRadians(Double.parseDouble(localParameters.get(ANGLE_TOLERANCE)));
}
if (parameters.containsKey(DISTANCE_TOLERANCE)) {
delta = Double.parseDouble(parameters.get(DISTANCE_TOLERANCE));
if (localParameters.containsKey(DISTANCE_TOLERANCE)) {
delta = Double.parseDouble(localParameters.get(DISTANCE_TOLERANCE));
}
}
......@@ -97,7 +104,7 @@ public class PlanarCheck extends Check {
public void check(Polygon p) {
if (DISTANCE.equals(planarCheckType)) {
planarDistance(p);
} else if ("angle".equals(planarCheckType)) {
} else if (ANGLE.equals(planarCheckType)) {
planarNormalDeviation(p);
} else if ("both".equals(planarCheckType)) {
planarDistance(p);
......
......@@ -29,6 +29,7 @@ import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
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.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
......@@ -59,15 +60,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*/
public class RingSelfIntCheck extends Check {
private static final String EPSILON_NAME = "minVertexDistance";
private static final List<CheckId> dependencies;
// check requirement class for default parameters
private double degeneratedRingTolerance = 0.01;
private double epsilon = Checks.MIN_VERTEX_DISTANCE_DEFAULT;
private static final List<CheckId> dependencies;
static {
ArrayList<CheckId> deps = new ArrayList<>();
deps.add(CheckId.C_GE_R_TOO_FEW_POINTS);
......@@ -78,13 +76,18 @@ public class RingSelfIntCheck extends Check {
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
String epsilonString = parameters.get(EPSILON_NAME);
public void init(Map<CheckId, Map<String, String>> parameters, ParserConfiguration config) {
Map<String, String> localParameters = parameters.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String epsilonString = localParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
if (epsilonString != null) {
epsilon = Double.parseDouble(epsilonString);
}
if (parameters.containsKey(Requirement.DEGENERATED_RING_TOLERANCE)) {
degeneratedRingTolerance = Double.parseDouble(parameters.get(Requirement.DEGENERATED_RING_TOLERANCE));
if (localParameters.containsKey(Requirement.DEGENERATED_RING_TOLERANCE)) {
degeneratedRingTolerance = Double.parseDouble(localParameters.get(Requirement.DEGENERATED_RING_TOLERANCE));
}
}
......
......@@ -21,13 +21,13 @@ package de.hft.stuttgart.citydoctor2.checks.geometry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
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.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
......@@ -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.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
/**
......@@ -48,6 +49,8 @@ public class SolidSelfIntCheck extends Check {
private static final List<CheckId> dependencies;
private double delta = 0.01;
static {
ArrayList<CheckId> deps = new ArrayList<>();
dependencies = Collections.unmodifiableList(deps);
......@@ -69,13 +72,26 @@ public class SolidSelfIntCheck extends Check {
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
public void check(Geometry g) {
if (g.getType() != GeometryType.SOLID && g.getType() != GeometryType.COMPOSITE_SURFACE) {
return;
}
CheckResult cr;
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g);
List<PolygonIntersection> intersections = SelfIntersectionUtil.calculateSolidSelfIntersection(g, delta);
if (intersections.isEmpty()) {
cr = new CheckResult(this, ResultStatus.OK, null);
} else {
......@@ -85,18 +101,6 @@ public class SolidSelfIntCheck extends Check {
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
public List<CheckId> getDependencies() {
return dependencies;
......
......@@ -75,13 +75,18 @@ public class IsWallCheck extends Check {
private double upperAngleCos = Math.cos(135 * Math.PI / 180);
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String lowerAngleString = params.get(LOWER_ANGLE_NAME);
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String lowerAngleString = localParameters.get(LOWER_ANGLE_NAME);
if (lowerAngleString != null) {
lowerAngleCos = Double.parseDouble(lowerAngleString);
lowerAngleCos = Math.cos(lowerAngleCos * Math.PI / 180);
}
String upperAngleString = params.get(UPPER_ANGLE_NAME);
String upperAngleString = localParameters.get(UPPER_ANGLE_NAME);
if (upperAngleString != null) {
upperAngleCos = Double.parseDouble(upperAngleString);
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