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

Add design spec for OpenGL 3D view



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

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