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
./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,6 +23,7 @@ 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;
......@@ -70,6 +71,8 @@ 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(inputFile, xmlOutput, pdfOutput, config, outputFile);
......@@ -81,18 +84,42 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
logger.error(Localization.getText("CityDoctorValidation.noOutputFile"));
logger.fatal(Localization.getText("CityDoctorValidation.noOutputFile"));
System.exit(11);
}
List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile"));
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.
*
......@@ -202,7 +229,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("pdfreport")) {
List<String> reportFiles = argParser.getValues("pdfreport");
if (reportFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
System.exit(6);
}
return reportFiles.get(0);
......@@ -214,7 +241,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("xmlreport")) {
List<String> reportFiles = argParser.getValues("xmlreport");
if (reportFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
System.exit(5);
}
return reportFiles.get(0);
......@@ -231,7 +258,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("config")) {
List<String> configFiles = argParser.getValues("config");
if (configFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
System.exit(4);
}
return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0));
......@@ -240,7 +267,7 @@ public class CityDoctorValidation {
logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig"));
return ValidationConfiguration.loadStandardValidationConfig();
} else {
logger.error(Localization.getText("CityDoctorValidation.noConfig"));
logger.fatal(Localization.getText("CityDoctorValidation.noConfig"));
System.exit(7);
return null;
}
......@@ -252,7 +279,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("out")) {
List<String> outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
logger.fatal(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
System.exit(3);
}
return outFiles.get(0);
......@@ -271,12 +298,12 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
logger.error(Localization.getText("CityDoctorValidation.noInputFile"));
logger.fatal(Localization.getText("CityDoctorValidation.noInputFile"));
System.exit(1);
}
List<String> inFiles = argParser.getValues("in");
if (inFiles.size() != 1) {
logger.error(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
System.exit(2);
}
inputFile = inFiles.get(0);
......
......@@ -2,6 +2,7 @@ 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;
......@@ -26,20 +27,30 @@ public class CityDoctorValidationCLI implements Runnable {
private File config;
@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)
private String xmlReport;
@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)
private String pdfReport;
@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)
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 {
......@@ -49,6 +60,12 @@ public class CityDoctorValidationCLI implements Runnable {
} 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);
......
......@@ -47,6 +47,7 @@ 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;
......@@ -77,7 +78,6 @@ 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.datastructure.ImplicitGeometryHolder;
import de.hft.stuttgart.citydoctor2.parser.CityGmlConsumer;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
......@@ -150,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()))) {
......@@ -166,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);
......@@ -243,7 +244,6 @@ public class Checker {
private void handleSchematronResults(SvrlContentHandler handler) {
CityObjectCache cache = model.getCache();
handleSchematronErrorsGlobal(handler.getGeneralErrors());
Map<String, CityObject> featureMap = new HashMap<>();
boolean onlySchematron = execLayers.isEmpty();
if (onlySchematron) {
CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() {
......@@ -252,12 +252,7 @@ public class Checker {
checkable.setValidated(true);
}
};
model.createFeatureStream().forEach(f -> {
featureMap.put(f.getGmlId().getGmlString(), f);
f.accept(visitor);
});
} else {
model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f));
model.createFeatureStream().forEach(co -> co.accept(visitor));
}
handler.getFeatureErrors().forEach((k, v) -> {
String trimmedId = k.trim();
......@@ -641,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;
......@@ -657,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());
......@@ -709,12 +703,11 @@ public class Checker {
(threadCount, threadCount, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
try{
long startTime = System.nanoTime();
ImplicitGeometryHolder.preparePrototypeGeometriesForChecking();
List<Future<GmlId>> futures = runChecksOnFeatures(exec, cache, features, checkedCount, l);
List<GmlId> missedFeatures = getMissedFeatures(futures);
ImplicitGeometryHolder.clearMetaDataOfPrototypeGeometries();
if (!missedFeatures.isEmpty()){
logger.error(Localization.getText("Checker.dbUnresponsive"));
if (logger.isDebugEnabled()){
......@@ -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,
ProgressListener l)
@Nullable ProgressListener l)
throws InterruptedException {
float featureSum = ids.size() + (float) checkedCount.get();
......@@ -763,11 +770,9 @@ public class Checker {
}
if(Thread.interrupted()){
Thread.currentThread().interrupt();
return null;
return id;
}
co.prepareForChecking();
executeChecksForCityObject(co);
co.clearMetaInformation();
cache.put(co);
checkedCount.incrementAndGet();
if (l!=null){
......@@ -782,7 +787,7 @@ public class Checker {
private List<GmlId> getMissedFeatures(List<Future<GmlId>> futures) throws InterruptedException{
List<GmlId> missedList = new ArrayList<>();
Set<ExecutionException> errors = new HashSet<>();
Set<String> errors = new HashSet<>();
for (Future<GmlId> future : futures) {
try{
GmlId gmlId = future.get();
......@@ -792,7 +797,7 @@ public class Checker {
} catch (ExecutionException e){
logger.debug("A Task failed due to an unexpected exception", e);
logger.debug(e.getCause());
errors.add(e);
errors.add(e.getCause().toString());
}
}
if (!errors.isEmpty()) {
......@@ -840,7 +845,9 @@ public class Checker {
if (!filterObject(co)) {
return;
}
co.prepareForChecking();
executeChecksForCheckable(co);
co.clearMetaInformation();
}
/**
......@@ -939,7 +946,7 @@ public class Checker {
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);
......
......@@ -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);
}
......
......@@ -118,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);
}
......@@ -164,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) {
......@@ -173,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);
}
}
......
......@@ -81,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));
}
}
......
......@@ -30,6 +30,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;
......@@ -74,13 +75,11 @@ 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;
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);
......@@ -139,13 +138,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));
}
}
......
......@@ -108,10 +108,16 @@ public class SolidSelfIntCheck extends Check {
}
@Override
public void init(Map<String, String> parameters, ParserConfiguration config) {
if (parameters.containsKey(PlanarCheck.DISTANCE_TOLERANCE)) {
delta = Double.parseDouble(parameters.get(PlanarCheck.DISTANCE_TOLERANCE));
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;
});
}
public SolidSelfIntCheck() {
......
......@@ -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);
......
......@@ -29,6 +29,7 @@ import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Requirement;
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.UnfragmentedCheck;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
......@@ -60,8 +61,13 @@ public class RoofSurfaceUnfragmentedCheck extends Check {
}
@Override
public void init(Map<String, String> params, ParserConfiguration config) {
String maxAngleString = params.get(MAX_ANGLE_DEVIATION);
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 maxAngleString = localParameters.get(MAX_ANGLE_DEVIATION);
if (maxAngleString != null) {
maxAngleDeviation = Math.toRadians(Double.parseDouble(maxAngleString));
}
......@@ -75,6 +81,10 @@ public class RoofSurfaceUnfragmentedCheck extends Check {
}
// only use lod1 and lod2 polygons
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);
}
......
......@@ -20,7 +20,9 @@ package de.hft.stuttgart.citydoctor2.checks.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
public class CollectionUtils {
......@@ -40,4 +42,11 @@ public class CollectionUtils {
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;
import org.locationtech.jts.operation.overlay.OverlayOp;
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.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
......@@ -55,10 +53,8 @@ import de.hft.stuttgart.citydoctor2.edge.EdgePolygon;
import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons;
import de.hft.stuttgart.citydoctor2.edge.MeshSurface;
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.MovedRing;
import de.hft.stuttgart.citydoctor2.math.OrthogonalRegressionPlane;
import de.hft.stuttgart.citydoctor2.math.Plane;
import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection;
import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection.Type;
......@@ -90,46 +86,80 @@ public class SelfIntersectionUtil {
private SelfIntersectionUtil() {
}
// Tesselation and filtering degenerated triangles
// Tesselation and filtering degenerated triangles
private static List<TesselatedPolygon> tesselateAndFilter(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
for (Iterator<Triangle3d> iterator = tessPolygon.getTriangles().iterator(); iterator.hasNext();) {
Triangle3d t = iterator.next();
if (!t.hasMinExtent(delta)) {
iterator.remove();
}
}
tesselatedPolygons.add(tessPolygon);
}
return tesselatedPolygons;
}
for (Polygon p : g.getPolygons()) {
TesselatedPolygon tessPolygon = EarcutTesselator.tesselatePolygon(p);
/*
* @ Baris Numanoglu
*
* Version with IdentityHashmap tree query
*/
public static List<PolygonIntersection> calculateSolidSelfIntersection(Geometry g, double delta,
BoundingVolumeHierarchyTree<Polygon> tree) {
for (Iterator<Triangle3d> it = tessPolygon.getTriangles().iterator(); it.hasNext();) {
Triangle3d t = it.next();
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<Vector3d> vertices = new ArrayList<>(3);
vertices.add(t.getP1());
vertices.add(t.getP2());
vertices.add(t.getP3());
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>();
for (int i = 0; i < tesselatedPolygons.size(); i++) {
Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon
ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
indexByOriginal.put(orig, i);
}
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = OrthogonalRegressionPlane.decompose(vertices, centroid);
List<PolygonIntersection> intersections = new ArrayList<>();
Matrix eigenValues = ed.getD();
double ev1 = eigenValues.get(1, 1);
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
if (ev1 < delta) {
it.remove();
}
}
// Query AABB based on original polygon (consistent with tree construction)
ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon
AABB q = AABB.of(p1Orig);
tesselatedPolygons.add(tessPolygon);
}
List<Polygon> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
continue; // no candidate ergo 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();
if (j <= i) {
continue; // avoids double pairwise checks and self-pair
}
TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO may be later a further tree in here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return tesselatedPolygons;
return intersections;
}
/*
* @ Baris Numanoglu
*
* New Version with tree query
* */
public static List<PolygonIntersection> calculateSolidSelfIntersectionWithTree(
Geometry g,
double delta,
......@@ -188,105 +218,27 @@ public class SelfIntersectionUtil {
);
}
/*
* @ 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);
// Map: original ConcretePolygon (identity) -> index in tesselatedPolygons
IdentityHashMap<ConcretePolygon, Integer> indexByOriginal = new IdentityHashMap<>();
for (int i = 0; i < tesselatedPolygons.size(); i++) {
Polygon p = tesselatedPolygons.get(i).getOriginal(); // returns Polygon
ConcretePolygon orig = p.getOriginal(); // returns ConcretePolygon
indexByOriginal.put(orig, i);
}
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
// Query AABB based on original polygon (consistent with tree construction)
ConcretePolygon p1Orig = p1.getOriginal().getOriginal(); // p1:teselatedPoly->Polygon->Concrete Polygon
AABB q = AABB.of(p1Orig);
List<Polygon> candidates = tree.getAllIntersectingElements(q);
if (candidates.isEmpty()) {
continue; // no candidate ergo 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();
if (j <= i) {
continue; // avoids double pairwise checks and self-pair
}
TesselatedPolygon p2 = tesselatedPolygons.get(j);
// TODO may be later a further tree in here
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return intersections;
}
// 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) {
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
for (int j = i + 1; j < tesselatedPolygons.size(); j++) {
TesselatedPolygon p2 = tesselatedPolygons.get(j);
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return intersections;
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
TesselatedPolygon p1 = tesselatedPolygons.get(i);
for (int j = i + 1; j < tesselatedPolygons.size(); j++) {
TesselatedPolygon p2 = tesselatedPolygons.get(j);
GeometrySelfIntersection inter = doPolygonsIntersect(p1, p2, delta);
if (inter != null) {
intersections.add(PolygonIntersection.triangles(inter.t1(), inter.t2()));
}
}
}
return intersections;
}
// The very oldest Version Without Tree and internal tesselation
public static List<PolygonIntersection> calculateSolidSelfIntersection0(Geometry g, double delta) {
List<TesselatedPolygon> tesselatedPolygons = new ArrayList<>();
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<TesselatedPolygon> tesselatedPolygons = tesselateAndFilter(g, delta);
List<PolygonIntersection> intersections = new ArrayList<>();
for (int i = 0; i < tesselatedPolygons.size() - 1; i++) {
......@@ -301,7 +253,7 @@ public class SelfIntersectionUtil {
}
return intersections;
}
public static GeometrySelfIntersection doesSolidSelfIntersect(Geometry g, double epsilon) {
return selfIntersectionJava(g, epsilon);
}
......@@ -621,10 +573,11 @@ public class SelfIntersectionUtil {
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++) {
Triangle3d t1 = p1.getTriangles().get(p1Index);
for (int p2Index = 0; p2Index < p2.getTriangles().size(); p2Index++) {
Triangle3d t1 = p1.getTriangles().get(p1Index);
Triangle3d t2 = p2.getTriangles().get(p2Index);
if (t1.doesIntersect(t2, epsilon)) {
logger.trace("{} intersects {}", t1, t2);
......
......@@ -24,7 +24,7 @@ import java.util.List;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
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.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
......@@ -75,7 +75,7 @@ public class UnfragmentedCheck {
double dot = averageNormal.dot(n);
double acos = Math.acos(dot);
if (acos > delta) {
SurfaceUnfragmentedError err = new SurfaceUnfragmentedError(bs, acos);
SurfaceFragmentedError err = new SurfaceFragmentedError(bs, acos);
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