Commit 6d0d664e authored by Matthias Betz's avatar Matthias Betz
Browse files

Merge remote-tracking branch 'remotes/origin/dev' into dev_baris_aabb_auto_mergeable

parents a7ed4b64 cc4fe260
Pipeline #12443 passed with stage
in 2 minutes and 42 seconds
#!/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,6 +23,7 @@ import java.io.FileNotFoundException; ...@@ -23,6 +23,7 @@ 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 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;
...@@ -70,6 +71,8 @@ public class CityDoctorValidation { ...@@ -70,6 +71,8 @@ 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(inputFile, xmlOutput, pdfOutput, config, outputFile); startValidationProcess(inputFile, xmlOutput, pdfOutput, config, outputFile);
...@@ -81,18 +84,42 @@ public class CityDoctorValidation { ...@@ -81,18 +84,42 @@ public class CityDoctorValidation {
if (optional) { if (optional) {
return null; return null;
} }
logger.error(Localization.getText("CityDoctorValidation.noOutputFile")); 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(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile")); 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.
* *
...@@ -202,7 +229,7 @@ public class CityDoctorValidation { ...@@ -202,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(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput")); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
System.exit(6); System.exit(6);
} }
return reportFiles.get(0); return reportFiles.get(0);
...@@ -214,7 +241,7 @@ public class CityDoctorValidation { ...@@ -214,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(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput")); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
System.exit(5); System.exit(5);
} }
return reportFiles.get(0); return reportFiles.get(0);
...@@ -231,7 +258,7 @@ public class CityDoctorValidation { ...@@ -231,7 +258,7 @@ 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(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile")); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
System.exit(4); System.exit(4);
} }
return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0)); return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0));
...@@ -240,7 +267,7 @@ public class CityDoctorValidation { ...@@ -240,7 +267,7 @@ public class CityDoctorValidation {
logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig")); logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig"));
return ValidationConfiguration.loadStandardValidationConfig(); return ValidationConfiguration.loadStandardValidationConfig();
} else { } else {
logger.error(Localization.getText("CityDoctorValidation.noConfig")); logger.fatal(Localization.getText("CityDoctorValidation.noConfig"));
System.exit(7); System.exit(7);
return null; return null;
} }
...@@ -252,7 +279,7 @@ public class CityDoctorValidation { ...@@ -252,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(Localization.getText("CityDoctorValidation.multipleOutputFiles")); logger.fatal(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
System.exit(3); System.exit(3);
} }
return outFiles.get(0); return outFiles.get(0);
...@@ -271,12 +298,12 @@ public class CityDoctorValidation { ...@@ -271,12 +298,12 @@ public class CityDoctorValidation {
if (optional) { if (optional) {
return null; return null;
} }
logger.error(Localization.getText("CityDoctorValidation.noInputFile")); 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(Localization.getText("CityDoctorValidation.notExactlyOneInputFile")); logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
System.exit(2); System.exit(2);
} }
inputFile = inFiles.get(0); inputFile = inFiles.get(0);
......
...@@ -2,6 +2,7 @@ package de.hft.stuttgart.citydoctor2; ...@@ -2,6 +2,7 @@ package de.hft.stuttgart.citydoctor2;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration; 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.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
...@@ -26,20 +27,30 @@ public class CityDoctorValidationCLI implements Runnable { ...@@ -26,20 +27,30 @@ public class CityDoctorValidationCLI implements Runnable {
private File config; private File config;
@Option(names = {"-x" , "--xml" , "--xmlReport"}, @Option(names = {"-x" , "--xml" , "--xmlReport"},
description = "Create a XML validation report at target location", description = "Creates a XML validation report at target location",
defaultValue = Option.NULL_VALUE) defaultValue = Option.NULL_VALUE)
private String xmlReport; private String xmlReport;
@Option(names = {"-p" , "--pdf" , "--pdfReport"}, @Option(names = {"-p" , "--pdf" , "--pdfReport"},
description = "Create a PDF validation report at target location", description = "Creates a PDF validation report at target location",
defaultValue = Option.NULL_VALUE) defaultValue = Option.NULL_VALUE)
private String pdfReport; private String pdfReport;
@Option(names = {"-o" , "--out" , "--output"}, @Option(names = {"-o" , "--out" , "--output"},
description = "Create a validated copy of the CityGML input with QualityADE at target location.", description = "Creates a validated copy of the CityGML input with QualityADE at target location.",
defaultValue = Option.NULL_VALUE) defaultValue = Option.NULL_VALUE)
private String output; 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 @Override
public void run(){ public void run(){
try { try {
...@@ -49,6 +60,12 @@ public class CityDoctorValidationCLI implements Runnable { ...@@ -49,6 +60,12 @@ public class CityDoctorValidationCLI implements Runnable {
} else{ } else{
valConfig=ValidationConfiguration.loadValidationConfigFile(config.getAbsolutePath()); 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); CityDoctorValidation.startValidationProcess(input, xmlReport, pdfReport, valConfig,output);
} catch (CityDoctorWriteException | CityGmlParseException | IOException | InvalidGmlFileException e) { } catch (CityDoctorWriteException | CityGmlParseException | IOException | InvalidGmlFileException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
......
...@@ -47,6 +47,7 @@ import java.util.concurrent.ThreadPoolExecutor; ...@@ -47,6 +47,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; 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;
...@@ -77,7 +78,6 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel; ...@@ -77,7 +78,6 @@ 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.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
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;
...@@ -150,8 +150,8 @@ public class Checker { ...@@ -150,8 +150,8 @@ public class Checker {
return; return;
} }
File xmlFile = new File(xmlOutput); File xmlFile = new File(xmlOutput);
if (xmlFile.getParentFile() != null) { if (xmlFile.getParentFile() != null && xmlFile.getParentFile().mkdirs()) {
xmlFile.getParentFile().mkdirs(); logger.trace("Xml dir created");
} }
Reporter reporter = new XmlValidationReporter(); Reporter reporter = new XmlValidationReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) {
...@@ -166,9 +166,10 @@ public class Checker { ...@@ -166,9 +166,10 @@ public class Checker {
return; return;
} }
File pdfFile = new File(pdfOutput); File pdfFile = new File(pdfOutput);
if (pdfFile.getParentFile() != null) { if (pdfFile.getParentFile() != null && pdfFile.getParentFile().mkdirs()){
pdfFile.getParentFile().mkdirs(); logger.trace("Pdf dir created");
} }
Reporter reporter = new PdfReporter(); Reporter reporter = new PdfReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config); reporter.writeReport(checkConfig, bos, model, config);
...@@ -243,7 +244,6 @@ public class Checker { ...@@ -243,7 +244,6 @@ public class Checker {
private void handleSchematronResults(SvrlContentHandler handler) { private void handleSchematronResults(SvrlContentHandler handler) {
CityObjectCache cache = model.getCache(); CityObjectCache cache = model.getCache();
handleSchematronErrorsGlobal(handler.getGeneralErrors()); handleSchematronErrorsGlobal(handler.getGeneralErrors());
Map<String, CityObject> featureMap = new HashMap<>();
boolean onlySchematron = execLayers.isEmpty(); boolean onlySchematron = execLayers.isEmpty();
if (onlySchematron) { if (onlySchematron) {
CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() { CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() {
...@@ -252,12 +252,7 @@ public class Checker { ...@@ -252,12 +252,7 @@ public class Checker {
checkable.setValidated(true); checkable.setValidated(true);
} }
}; };
model.createFeatureStream().forEach(f -> { model.createFeatureStream().forEach(co -> co.accept(visitor));
featureMap.put(f.getGmlId().getGmlString(), f);
f.accept(visitor);
});
} else {
model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f));
} }
handler.getFeatureErrors().forEach((k, v) -> { handler.getFeatureErrors().forEach((k, v) -> {
String trimmedId = k.trim(); String trimmedId = k.trim();
...@@ -641,7 +636,7 @@ public class Checker { ...@@ -641,7 +636,7 @@ public class Checker {
ArrayList<Check> checkList = new ArrayList<>(); ArrayList<Check> checkList = new ArrayList<>();
for (CheckId id : enabledCheck) { for (CheckId id : enabledCheck) {
Check c = checkConfig.getCheckForId(id); Check c = checkConfig.getCheckForId(id);
c.init(parameterMap.get(id), parserConfig); c.init(parameterMap, parserConfig);
checkList.add(c); checkList.add(c);
} }
return checkList; return checkList;
...@@ -657,7 +652,6 @@ public class Checker { ...@@ -657,7 +652,6 @@ public class Checker {
parameterMap.compute(proto.getCheckId(), (k, v) -> { parameterMap.compute(proto.getCheckId(), (k, v) -> {
if (v == null) { if (v == null) {
v = new HashMap<>(); v = new HashMap<>();
v.put(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, config.getNumberOfRoundingPlacesAsString());
v.put(GlobalParameters.MIN_VERTEX_DISTANCE, config.getMinVertexDistanceAsString()); v.put(GlobalParameters.MIN_VERTEX_DISTANCE, config.getMinVertexDistanceAsString());
} }
v.putAll(e.getValue().getParameters()); v.putAll(e.getValue().getParameters());
...@@ -709,12 +703,11 @@ public class Checker { ...@@ -709,12 +703,11 @@ public class Checker {
(threadCount, threadCount, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); (threadCount, threadCount, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
try{ try{
long startTime = System.nanoTime(); long startTime = System.nanoTime();
ImplicitGeometryHolder.preparePrototypeGeometriesForChecking();
List<Future<GmlId>> futures = runChecksOnFeatures(exec, cache, features, checkedCount, l); List<Future<GmlId>> futures = runChecksOnFeatures(exec, cache, features, checkedCount, l);
List<GmlId> missedFeatures = getMissedFeatures(futures); List<GmlId> missedFeatures = getMissedFeatures(futures);
ImplicitGeometryHolder.clearMetaDataOfPrototypeGeometries();
if (!missedFeatures.isEmpty()){ if (!missedFeatures.isEmpty()){
logger.error(Localization.getText("Checker.dbUnresponsive")); logger.error(Localization.getText("Checker.dbUnresponsive"));
if (logger.isDebugEnabled()){ if (logger.isDebugEnabled()){
...@@ -748,8 +741,22 @@ public class Checker { ...@@ -748,8 +741,22 @@ public class Checker {
} }
} }
/**
* 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, private List<Future<GmlId>> runChecksOnFeatures(ExecutorService exec, CityObjectCache cache, List<GmlId> ids, AtomicInteger checkedCount,
ProgressListener l) @Nullable ProgressListener l)
throws InterruptedException { throws InterruptedException {
float featureSum = ids.size() + (float) checkedCount.get(); float featureSum = ids.size() + (float) checkedCount.get();
...@@ -763,11 +770,9 @@ public class Checker { ...@@ -763,11 +770,9 @@ public class Checker {
} }
if(Thread.interrupted()){ if(Thread.interrupted()){
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return null; return id;
} }
co.prepareForChecking();
executeChecksForCityObject(co); executeChecksForCityObject(co);
co.clearMetaInformation();
cache.put(co); cache.put(co);
checkedCount.incrementAndGet(); checkedCount.incrementAndGet();
if (l!=null){ if (l!=null){
...@@ -782,7 +787,7 @@ public class Checker { ...@@ -782,7 +787,7 @@ public class Checker {
private List<GmlId> getMissedFeatures(List<Future<GmlId>> futures) throws InterruptedException{ private List<GmlId> getMissedFeatures(List<Future<GmlId>> futures) throws InterruptedException{
List<GmlId> missedList = new ArrayList<>(); List<GmlId> missedList = new ArrayList<>();
Set<ExecutionException> errors = new HashSet<>(); Set<String> errors = new HashSet<>();
for (Future<GmlId> future : futures) { for (Future<GmlId> future : futures) {
try{ try{
GmlId gmlId = future.get(); GmlId gmlId = future.get();
...@@ -792,7 +797,7 @@ public class Checker { ...@@ -792,7 +797,7 @@ public class Checker {
} catch (ExecutionException e){ } catch (ExecutionException e){
logger.debug("A Task failed due to an unexpected exception", e); logger.debug("A Task failed due to an unexpected exception", e);
logger.debug(e.getCause()); logger.debug(e.getCause());
errors.add(e); errors.add(e.getCause().toString());
} }
} }
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
...@@ -840,7 +845,9 @@ public class Checker { ...@@ -840,7 +845,9 @@ public class Checker {
if (!filterObject(co)) { if (!filterObject(co)) {
return; return;
} }
co.prepareForChecking();
executeChecksForCheckable(co); executeChecksForCheckable(co);
co.clearMetaInformation();
} }
/** /**
...@@ -939,7 +946,7 @@ public class Checker { ...@@ -939,7 +946,7 @@ public class Checker {
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile); SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile);
CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l); CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l);
// parse and validate // parse and validate
CityGmlParser.streamCityGml(inputFile.getAbsolutePath(), config.getParserConfiguration(), con, outputFile); CityGmlParser.streamCityGml(inputFile, config.getParserConfiguration(), con, outputFile);
// write reports if available // write reports if available
writeReport(xmlReporter); writeReport(xmlReporter);
......
...@@ -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);
} }
......
...@@ -118,13 +118,15 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -118,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);
} }
...@@ -164,8 +166,8 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -164,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) {
...@@ -173,6 +175,9 @@ public class AllPolygonsWrongOrientationCheck extends Check { ...@@ -173,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);
} }
} }
......
...@@ -81,17 +81,22 @@ public class PlanarCheck extends Check { ...@@ -81,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));
} }
} }
......
...@@ -30,6 +30,7 @@ import de.hft.stuttgart.citydoctor2.check.Check; ...@@ -30,6 +30,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;
...@@ -74,13 +75,11 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -74,13 +75,11 @@ 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;
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);
...@@ -139,13 +138,18 @@ public class RingSelfIntCheck extends Check { ...@@ -139,13 +138,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));
} }
} }
......
...@@ -108,10 +108,16 @@ public class SolidSelfIntCheck extends Check { ...@@ -108,10 +108,16 @@ public class SolidSelfIntCheck extends Check {
} }
@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(PlanarCheck.DISTANCE_TOLERANCE)) { Map<String, String> planarParameters = parameters.get(CheckId.C_GE_P_NON_PLANAR);
delta = Double.parseDouble(parameters.get(PlanarCheck.DISTANCE_TOLERANCE)); if (planarParameters == null) {
// no parameters
return;
} }
planarParameters.computeIfPresent(PlanarCheck.DISTANCE_TOLERANCE, (k, v) -> {
delta = Double.parseDouble(v);
return v;
});
} }
public SolidSelfIntCheck() { public SolidSelfIntCheck() {
......
...@@ -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);
......
...@@ -29,6 +29,7 @@ import de.hft.stuttgart.citydoctor2.check.CheckId; ...@@ -29,6 +29,7 @@ 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.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.checks.util.CollectionUtils; import de.hft.stuttgart.citydoctor2.checks.util.CollectionUtils;
import de.hft.stuttgart.citydoctor2.checks.util.UnfragmentedCheck; import de.hft.stuttgart.citydoctor2.checks.util.UnfragmentedCheck;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface; import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
...@@ -60,8 +61,13 @@ public class RoofSurfaceUnfragmentedCheck extends Check { ...@@ -60,8 +61,13 @@ public class RoofSurfaceUnfragmentedCheck extends Check {
} }
@Override @Override
public void init(Map<String, String> params, ParserConfiguration config) { public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
String maxAngleString = params.get(MAX_ANGLE_DEVIATION); Map<String, String> localParameters = params.get(getCheckId());
if (localParameters == null) {
// no parameters
return;
}
String maxAngleString = localParameters.get(MAX_ANGLE_DEVIATION);
if (maxAngleString != null) { if (maxAngleString != null) {
maxAngleDeviation = Math.toRadians(Double.parseDouble(maxAngleString)); maxAngleDeviation = Math.toRadians(Double.parseDouble(maxAngleString));
} }
...@@ -75,6 +81,10 @@ public class RoofSurfaceUnfragmentedCheck extends Check { ...@@ -75,6 +81,10 @@ public class RoofSurfaceUnfragmentedCheck extends Check {
} }
// only use lod1 and lod2 polygons // only use lod1 and lod2 polygons
CheckResult cr = UnfragmentedCheck.checkForFragmentedBoundarySurfaces(this, bs, maxAngleDeviation); CheckResult cr = UnfragmentedCheck.checkForFragmentedBoundarySurfaces(this, bs, maxAngleDeviation);
if (cr.getResultStatus() == ResultStatus.OK) {
// check if neighboring roof surfaces have the same average normal
// they should be merged
}
bs.addCheckResult(cr); bs.addCheckResult(cr);
} }
......
...@@ -20,7 +20,9 @@ package de.hft.stuttgart.citydoctor2.checks.util; ...@@ -20,7 +20,9 @@ package de.hft.stuttgart.citydoctor2.checks.util;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.IntStream;
public class CollectionUtils { public class CollectionUtils {
...@@ -40,4 +42,11 @@ public class CollectionUtils { ...@@ -40,4 +42,11 @@ public class CollectionUtils {
return set; return set;
} }
public static <T> List<List<T>> partition(List<T> list, int chunkSize){
return IntStream.iterate(0, i-> i < list.size(), i-> i+ chunkSize)
.mapToObj(i -> list.subList(i, Math.min(i+chunkSize, list.size())))
.toList();
}
} }
...@@ -40,8 +40,6 @@ import org.locationtech.jts.geom.impl.CoordinateArraySequence; ...@@ -40,8 +40,6 @@ import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.operation.overlay.OverlayOp; import org.locationtech.jts.operation.overlay.OverlayOp;
import org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp; import org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp;
import Jama.EigenvalueDecomposition;
import Jama.Matrix;
import de.hft.stuttgart.citydoctor2.check.GeometrySelfIntersection; import de.hft.stuttgart.citydoctor2.check.GeometrySelfIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon; import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
...@@ -55,10 +53,8 @@ import de.hft.stuttgart.citydoctor2.edge.EdgePolygon; ...@@ -55,10 +53,8 @@ import de.hft.stuttgart.citydoctor2.edge.EdgePolygon;
import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons; import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons;
import de.hft.stuttgart.citydoctor2.edge.MeshSurface; import de.hft.stuttgart.citydoctor2.edge.MeshSurface;
import de.hft.stuttgart.citydoctor2.edge.PolygonPolygonIntersection; import de.hft.stuttgart.citydoctor2.edge.PolygonPolygonIntersection;
import de.hft.stuttgart.citydoctor2.math.CovarianceMatrix;
import de.hft.stuttgart.citydoctor2.math.MovedPolygon; import de.hft.stuttgart.citydoctor2.math.MovedPolygon;
import de.hft.stuttgart.citydoctor2.math.MovedRing; import de.hft.stuttgart.citydoctor2.math.MovedRing;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane;
import de.hft.stuttgart.citydoctor2.math.Plane; import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection; import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection;
import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection.Type; import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection.Type;
...@@ -94,80 +90,66 @@ public class SelfIntersectionUtil { ...@@ -94,80 +90,66 @@ public class SelfIntersectionUtil {
// Tesselation and filtering degenerated triangles // Tesselation and filtering degenerated triangles
private static List<TesselatedPolygon> tesselateAndFilter(Geometry g, double delta) { private static List<TesselatedPolygon> tesselateAndFilter(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>(); List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
for (Polygon p : g.getPolygons()) { for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p); TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
for (Iterator<Triangle3d> iterator = tessPolygon.getTriangles().iterator(); iterator.hasNext();) {
for (Iterator<Triangle3d> it = tessPolygon.getTriangles().iterator(); it.hasNext();) { Triangle3d t = iterator.next();
Triangle3d t = it.next(); if (!t.hasMinExtent(delta)) {
iterator.remove();
List<Vector3d> vertices = new ArrayList<>(3);
vertices.add(t.getP1());
vertices.add(t.getP2());
vertices.add(t.getP3());
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
Matrix eigenValues = ed.getD();
double ev1 = eigenValues.get(1, 1);
if (ev1 < delta) {
it.remove();
} }
} }
tesselatedPolygons.add(tessPolygon); tesselatedPolygons.add(tessPolygon);
} }
return tesselatedPolygons; return tesselatedPolygons;
} }
/* /*
* @ Baris Numanoglu * @ Baris Numanoglu
* *
* New Version with tree query * Version with IdentityHashmap tree query
* */ */
public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta,
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree( BoundingVolumeHierarchyTree<Polygon> tree) {
Geometry g,
double delta,
BoundingVolumeHierarchyTree.Builder<Integer> treeConfig) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta); List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<Integer> indices = new ArrayList<>(tesselatedPolygons.size());
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>();
for (int i = 0; i < tesselatedPolygons.size(); i++) { for (int i = 0; i < tesselatedPolygons.size(); i++) {
indices.add(i); Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon
ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
indexByOriginal.put(orig, i);
} }
treeConfig.elements(indices).aabbFunction(index -> AABB.of(tesselatedPolygons.get(index).getOriginal()));
// Build BVH on polygon indices, while computing AABBs from the original polygons
BoundingVolumeHierarchyTree<Integer> tree = treeConfig.build();
List<PolygonIntersection> intersections = new ArrayList<>(); List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) { for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i); TesselatedPolygon p1 = tesselatedPolygons.get(i);
// Query AABB based on original polygon (consistent with tree construction) // Query AABB based on original polygon (consistent with tree construction)
AABB q = AABB.of(p1.getOriginal()); ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon
AABB q = AABB.of(p1Orig);
List<Integer> candidates = tree.getAllIntersectingElements(q); List<Polygon> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
continue; // no candidate => no overlap continue; // no candidate ergo no overlap
} }
for (Integer j : candidates) { for (Polygon cand : candidates) {
ConcretePolygon candOrig = cand.getOriginal();
Integer jObj = indexByOriginal.get(candOrig);
if (jObj == null) {
continue; // candidate not in tesselated-Liste (RE: edge case)
}
int j = jObj.intValue();
if (j <= i) { if (j <= i) {
// avoids double checks and self-pair continue; // avoids double pairwise checks and self-pair
continue;
} }
TesselatedPolygon p2 = tesselatedPolygons.get(j); TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO maybe later use another tree here // TODO may be later a further tree in here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta); GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) { if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2())); intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
...@@ -178,63 +160,44 @@ public class SelfIntersectionUtil { ...@@ -178,63 +160,44 @@ public class SelfIntersectionUtil {
return intersections; return intersections;
} }
// older signature with two arguments provisionally
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree( public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g, double delta) { Geometry g,
return calculateSolidSelfIntersectionWithTree( double delta,
g, BoundingVolumeHierarchyTree.Builder<Integer> treeConfig) {
delta,
BoundingVolumeHierarchyTree.<Integer>octonaryBuilder()
);
}
/*
* @ Baris Numanoglu
*
* Version with IdentityHashmap tree query
* */
public static List<PolygonIntersection> calculateSolidSelfIntersection(
Geometry g, double delta, BoundingVolumeHierarchyTree<Polygon> tree) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta); List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<Integer> indices = new ArrayList<>(tesselatedPolygons.size());
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>();
for (int i = 0; i < tesselatedPolygons.size(); i++) { for (int i = 0; i < tesselatedPolygons.size(); i++) {
Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon indices.add(i);
ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
indexByOriginal.put(orig, i);
} }
treeConfig.elements(indices).aabbFunction(index -> AABB.of(tesselatedPolygons.get(index).getOriginal()));
// Build BVH on polygon indices, while computing AABBs from the original polygons
BoundingVolumeHierarchyTree<Integer> tree = treeConfig.build();
List<PolygonIntersection> intersections = new ArrayList<>(); List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) { for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i); TesselatedPolygon p1 = tesselatedPolygons.get(i);
// Query AABB based on original polygon (consistent with tree construction) // Query AABB based on original polygon (consistent with tree construction)
ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon AABB q = AABB.of(p1.getOriginal());
AABB q = AABB.of(p1Orig);
List<Polygon> candidates = tree.getAllIntersectingElements(q); List<Integer> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
continue; // no candidate ergo no overlap continue; // no candidate => no overlap
}
for (Polygon cand : candidates) {
ConcretePolygon candOrig = cand.getOriginal();
Integer jObj = indexByOriginal.get(candOrig);
if (jObj == null) {
continue; // candidate not in tesselated-Liste (RE: edge case)
} }
int j = jObj.intValue(); for (Integer j : candidates) {
if (j <= i) { if (j <= i) {
continue; // avoids double pairwise checks and self-pair // avoids double checks and self-pair
continue;
} }
TesselatedPolygon p2 = tesselatedPolygons.get(j); TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO may be later a further tree in here // TODO maybe later use another tree here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta); GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) { if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2())); intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
...@@ -245,6 +208,16 @@ public class SelfIntersectionUtil { ...@@ -245,6 +208,16 @@ public class SelfIntersectionUtil {
return intersections; return intersections;
} }
// older signature with two arguments provisionally
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g, double delta) {
return calculateSolidSelfIntersectionWithTree(
g,
delta,
BoundingVolumeHierarchyTree.<Integer>octonaryBuilder()
);
}
// Older Version without Tree: going to be used for comparison // Older Version without Tree: going to be used for comparison
public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta) { public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta); List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
...@@ -265,28 +238,7 @@ public class SelfIntersectionUtil { ...@@ -265,28 +238,7 @@ public class SelfIntersectionUtil {
// The very oldest Version Without Tree and internal tesselation // The very oldest Version Without Tree and internal tesselation
public static List<PolygonIntersection> calculateSolidSelfIntersection0(Geometry g, double delta) { public static List<PolygonIntersection> calculateSolidSelfIntersection0(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>(); List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
for (Iterator<Triangle3d> iterator = tessPolygon.getTriangles().iterator(); iterator.hasNext();) {
Triangle3d t = iterator.next();
List<Vector3d> vertices = new ArrayList<>(3);
vertices.add(t.getP1());
vertices.add(t.getP2());
vertices.add(t.getP3());
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
Matrix eigenValues = ed.getD();
double[] eigenValuesArray = new double[3];
eigenValuesArray[0] = eigenValues.get(0, 0);
eigenValuesArray[1] = eigenValues.get(1, 1);
eigenValuesArray[2] = eigenValues.get(2, 2);
if (eigenValuesArray[1] < delta) {
iterator.remove();
}
}
tesselatedPolygons.add(tessPolygon);
}
List<PolygonIntersection> intersections = new ArrayList<>(); List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) { for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
...@@ -621,10 +573,11 @@ public class SelfIntersectionUtil { ...@@ -621,10 +573,11 @@ public class SelfIntersectionUtil {
return null; return null;
} }
private static GeometrySelfIntersection doPolygonsIntersect(TesselatedPolygon p1, TesselatedPolygon p2, double epsilon) { private static GeometrySelfIntersection doPolygonsIntersect(TesselatedPolygon p1, TesselatedPolygon p2,
double epsilon) {
for (int p1Index = 0; p1Index < p1.getTriangles().size(); p1Index++) { for (int p1Index = 0; p1Index < p1.getTriangles().size(); p1Index++) {
for (int p2Index = 0; p2Index < p2.getTriangles().size(); p2Index++) {
Triangle3d t1 = p1.getTriangles().get(p1Index); Triangle3d t1 = p1.getTriangles().get(p1Index);
for (int p2Index = 0; p2Index < p2.getTriangles().size(); p2Index++) {
Triangle3d t2 = p2.getTriangles().get(p2Index); Triangle3d t2 = p2.getTriangles().get(p2Index);
if (t1.doesIntersect(t2, epsilon)) { if (t1.doesIntersect(t2, epsilon)) {
logger.trace("{} intersects {}", t1, t2); logger.trace("{} intersects {}", t1, t2);
......
...@@ -24,7 +24,7 @@ import java.util.List; ...@@ -24,7 +24,7 @@ import java.util.List;
import de.hft.stuttgart.citydoctor2.check.Check; import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError; import de.hft.stuttgart.citydoctor2.check.error.SurfaceFragmentedError;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface; import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
...@@ -75,7 +75,7 @@ public class UnfragmentedCheck { ...@@ -75,7 +75,7 @@ public class UnfragmentedCheck {
double dot = averageNormal.dot(n); double dot = averageNormal.dot(n);
double acos = Math.acos(dot); double acos = Math.acos(dot);
if (acos > delta) { if (acos > delta) {
SurfaceUnfragmentedError err = new SurfaceUnfragmentedError(bs, acos); SurfaceFragmentedError err = new SurfaceFragmentedError(bs, acos);
return new CheckResult(c, ResultStatus.ERROR, err); return new CheckResult(c, ResultStatus.ERROR, err);
} }
} }
......
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