queuedObjects = new ArrayList<>();
- while (reader.hasNext()) {
- AbstractFeature chunk = reader.next();
- version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
- if (chunk instanceof CityModel cModel) {
- cModel.setCityObjectMembers(null);
- mapper.setCityModel(cModel);
- mapper.setCityGMLVersion(version);
- cgmlModel = cModel;
-
- if (!queuedObjects.isEmpty()) {
- for (AbstractCityObject aco : queuedObjects) {
- if (!cdModel.containsGmlId(aco.getId())) {
- cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
- }
- }
- }
- } else if (chunk instanceof AbstractCityObject aco) {
- acos.add(aco); // getid
- String id = aco.getId();
- aco.accept(mapper);
- if (cgmlModel == null) {
- // Add object to queue till the CityModel has been parsed
- queuedObjects.add(aco);
- }else if (!cdModel.containsGmlId(id)){
- cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
- }
- }
- }
-
- if (mapper.getModel().getCityModel() == null) {
- // file does not contain a city model?
- // create it for now
- logger.warn(Localization.getText("CityGmlParser.missingCityModel"));
- mapper.setCityModel(new CityModel());
- for (AbstractCityObject aco : queuedObjects) {
- if (!cdModel.containsGmlId(aco.getId())) {
- mapper.getModel().getCityModel().getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
- }
- }
- }
-
- ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache());
- if (logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.parsedObjects"),
- mapper.getModel().getNumberOfFeatures());
- }
- mapper.setCityGMLVersion(version);
- return mapper.getModel();
- } catch (Exception e) {
- throw new CityGMLReadException(e);
- }
-
- }
- }
-
- private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose) throws CityGmlParseException {
- try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
- parseEpsgCodeFromStream(bis, config, verbose);
- } catch (ParserConfigurationException | SAXException | IOException e) {
- throw new CityGmlParseException("Failed to read CityGML file", e);
- }
- }
-
- private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose)
- throws ParserConfigurationException, SAXException {
- SAXParser parser = FACTORY.newSAXParser();
- CityGmlHandler handler = new CityGmlHandler();
- try {
- parser.parse(new InputSource(is), handler);
- } catch (EnvelopeFoundException e) {
- try {
- parseCoordinateSystem(config, handler, verbose);
- } catch (Exception e2) {
- logEpsgParseError(e2);
- }
- } catch (Exception e) {
- logEpsgParseError(e);
- }
- if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
- }
-
- }
-
- private static void logEpsgParseError(Exception e) {
- logger.debug("Exception while parsing for EPSG code", e);
- if (logger.isWarnEnabled()) {
- logger.warn(Localization.getText("CityGmlParser.failedEPSGParse"));
- }
- }
-
- private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
- if (handler.getEpsg() == null) {
- return;
- }
- CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
- if (crs == null) {
- // could not find a coordinate system for srsName
- // assuming metric system
- if (logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
- }
- return;
- }
- if (crs.getProjection().getUnits() == Units.METRES) {
- // coordinate system is in meters, do not convert
- if (logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.noConversionNeeded"));
- }
- return;
- }
- parseMeterConversion(config, crs);
- Vector3d low = handler.getLowerCorner();
- Vector3d up = handler.getUpperCorner();
- double centerLong = low.getX() + ((up.getX() - low.getX()) / 2);
- double centerLat = low.getY() + ((up.getY() - low.getY()) / 2);
- if (!crs.getName().equals(WGS_84)) {
- // need to convert coordinates first to WGS84, then find UTM Zone
- CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
- ProjCoordinate p1 = new ProjCoordinate();
- p1.setValue(centerLong, centerLat);
- ProjCoordinate p2 = new ProjCoordinate();
- BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
- bct.transform(p1, p2);
- centerLong = p2.x;
- centerLat = p2.y;
- }
- int zone = (int) (31 + Math.round(centerLong / 6));
- CoordinateReferenceSystem utm;
- if (centerLat < 0) {
- // south
- if (logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone);
- }
- utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
- } else {
- // north
- if (logger.isInfoEnabled() && verbose) {
- logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone);
- }
- utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
- }
- config.setCoordinateSystem(crs, utm);
- }
-
- private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
- Projection projection = crs.getProjection();
- double fromMetres = projection.getFromMetres();
- if (fromMetres > 0) {
- // also transform height information
- config.setFromMeters(fromMetres);
- } else {
- config.setFromMeters(1.0);
- }
- }
-
- /**
- * The srsName (The name by which this reference system is identified) inside
- * the CityGML file can have multiple formats. This method tries to parse the
- * string and detect the corresponding reference system. If it is found, it
- * returns a proj4j.CoordinateReferenceSystem. It throws an
- * IllegalArgumentException otherwise.
- *
- * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
- * Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
- * those are also included. It isn't guaranteed that those formats are correctly
- * parsed, though.
- *
- * The EPSG ids and parameters are defined in resources ('nad/epsg') inside
- * proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
- *
- * @param srsName
- * @return CoordinateReferenceSystem
- */
- private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
- srsName = srsName.trim();
- Matcher mEPSG = P_EPSG.matcher(srsName);
- if (mEPSG.find()) {
- if ("EPSG:4979".contentEquals(srsName)) {
- srsName = "EPSG:4236";
- } else if ("EPSG:7415".contentEquals(srsName)) {
- return CRS_FACTORY.createFromParameters("EPSG:7415",
- "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs");
- }
- return CRS_FACTORY.createFromName(srsName);
- }
-
- Matcher mOGC = P_OGC.matcher(srsName);
- if (mOGC.find()) {
- return getCrsFromOGCSrsCode(mOGC.group(1));
- }
- Matcher mOGC2 = P_OGC2.matcher(srsName);
- if (mOGC2.find()) {
- return getCrsFromOGCSrsCode(mOGC2.group(1));
-
- }
- Matcher mURN = P_URN.matcher(srsName);
- // NOTE: Could use a HashMap if the switch/case becomes too long.
- if (mURN.find()) {
- return switch (mURN.group(1)) {
- case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
- case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
- case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
- case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
- case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
- default -> null;
- };
- }
- if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) {
- return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
- }
- return null;
- }
-
- private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) {
- String crsName = "EPSG:" + srsCode;
- List params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList());
- params.removeIf(P_VUNITS.asPredicate());
- params.removeIf(P_GEOIDGRID.asPredicate());
- return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
- }
-
- private static List validateFile(CityGMLContext context, GMLValidationHandler handler, Path file)
- throws CityGmlParseException {
- if (handler == null) {
- handler = new GMLValidationHandler();
- }
- try {
- SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
- readAdditionalSchemaDefinitions(context, file, schemaHandler);
- Source[] schemas = schemaHandler.getSchemas();
- SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
- schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
- Schema schema = schemaFactory.newSchema(schemas);
- Validator validator = schema.newValidator();
- validator.setErrorHandler(handler);
- validator.validate(new StreamSource(file.toFile()));
- return handler.getMessages();
- } catch (SchemaHandlerException | SAXException | IOException e) {
- throw new CityGmlParseException("Failed to validate CityGML file", e);
- }
- }
-
- private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
- throws CityGmlParseException {
- try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects())
- .withSchemaHandler(schemaHandler)
- .createReader(file)) {
- reader.nextTag();
- } catch (Exception e) {
- throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
- }
- }
-
- private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
- List id = new ArrayList<>();
- model.createFeatureStream().forEach(co -> {
- cityObjectConsumer.accept(co);
- id.add(co.getGmlId());
- });
- id.forEach(model::removeFeature);
-
- }
-
+ private static final String CITY_OBJECT_MEMBER = "cityObjectMember";
+
+ private static final String WGS_84 = "EPSG:4326";
+
+ private static final Logger logger = LogManager.getLogger(CityGmlParser.class);
+
+ private static final CRSFactory CRS_FACTORY = new CRSFactory();
+ // EPSG:31467
+ private static final Pattern P_EPSG = Pattern.compile("^(EPSG:\\d+)$");
+ // urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
+ // or
+ // urn:ogc:def:crs,crs:EPSG::28992
+ private static final Pattern P_OGC = Pattern.compile("urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
+
+ private static final Pattern P_OGC2 = Pattern.compile("urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*");
+
+ // urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
+ // urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
+ private static final Pattern P_URN = Pattern.compile("urn:adv:crs:([^\\*]+)");
+
+ private static final SAXParserFactory FACTORY;
+
+ private static Proj4FileReader csReader = new Proj4FileReader();
+ private static final Pattern P_VUNITS = Pattern.compile("^\\+vunits=.+");
+ private static final Pattern P_GEOIDGRID = Pattern.compile("^\\+geoidgrid=.+");
+
+ private static CityGMLContext context;
+ private static List chunkProperties = new ArrayList<>();
+
+ static {
+ System.setProperty("javax.xml.transform.TransformerFactory",
+ "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
+ FACTORY = SAXParserFactory.newInstance();
+ try {
+ FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+ } catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
+ logger.catching(e);
+ }
+
+ chunkProperties.add(new QName(CityGMLConstants.CITYGML_1_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
+ chunkProperties.add(new QName(CityGMLConstants.CITYGML_2_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
+ chunkProperties.add(new QName(CityGMLConstants.CITYGML_3_0_CORE_NAMESPACE, CITY_OBJECT_MEMBER));
+ }
+
+ private CityGmlParser() {
+ }
+
+ public static synchronized CityGMLContext setupContext() {
+ if (context == null) {
+ try {
+ context = CityGMLContext.newInstance(CityGmlParser.class.getClassLoader());
+ // also setup ades
+ ADERegistry adeRegistry = ADERegistry.getInstance();
+ adeRegistry.loadADE(new QualityADEContext());
+ } catch (CityGMLContextException e) {
+ logger.fatal(Localization.getText("CityGmlParser.creating4jContextFailed"), e);
+ throw new IllegalStateException("Unable to create citygml4j context");
+ } catch (ADEException e) {
+ logger.fatal(Localization.getText("CityGmlParser.creatingAdePluginsFailed"), e);
+ throw new IllegalStateException("Unable to add ADE plugins to citygml4j");
+ }
+ }
+ return context;
+ }
+
+ /**
+ * Parses a given CityGML file and collects the contained CityObject Features in
+ * a {@link CityDoctorModel}.
+ *
+ * The nested parse uses an
+ * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
+ * UnconnectedCache}, which is not connected to the database.
+ *
+ * Nested parsing should only be used for accessing Features in auxiliary, small
+ * CityGML files, e.g. parsing of
+ * {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject
+ * LibraryObject} files.
+ *
+ * @param filePath String path to the file
+ * @param config Configuration of the parser
+ * @return A {@link CityDoctorModel} object with the parsed Features
+ * @throws CityGmlParseException if parsing of the CityGML-file failed
+ * @throws InvalidGmlFileException if the GMLValidationHandler found an error
+ */
+ public static CityDoctorModel nestedCityGmlFileParse(String filePath, ParserConfiguration config)
+ throws CityGmlParseException, InvalidGmlFileException {
+ return parseCityGmlFile(filePath, config, null, null, false, true);
+ }
+
+ /**
+ * Parses a given CityGML file and collects the contained CityObject Features in
+ * a {@link CityDoctorModel}.
+ *
+ * Features are held in a non-persistent, embedded Database with an in-memory
+ * upstream cache. Thus, strong references to {@link CityObject} are to be
+ * avoided, to allow for the memory management of the cache.
+ *
+ * @param filePath String path to the file
+ * @param config Configuration of the parser
+ * @return A {@link CityDoctorModel} object with the parsed Features
+ * @throws CityGmlParseException if parsing of the CityGML-file failed
+ * @throws InvalidGmlFileException if the GMLValidationHandler found an error
+ */
+ public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config)
+ throws CityGmlParseException, InvalidGmlFileException {
+ return parseCityGmlFile(filePath, config, null, null);
+ }
+
+ /**
+ * Parses a given CityGML file and collects the contained CityObject Features in
+ * a {@link CityDoctorModel}.
+ *
+ * Features are held in a non-persistent, embedded Database with an in-memory
+ * upstream cache. Thus, strong references to {@link CityObject} are to be
+ * avoided, to allow for the memory management of the cache.
+ *
+ * @param filePath String path to the file
+ * @param config Configuration of the parser
+ * @param l Listener hook for tracking of parsing progress
+ * @return A {@link CityDoctorModel} object with the parsed Features
+ * @throws CityGmlParseException if parsing of the CityGML-file failed
+ * @throws InvalidGmlFileException if the GMLValidationHandler found an error
+ */
+ public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l)
+ throws CityGmlParseException, InvalidGmlFileException {
+ return parseCityGmlFile(filePath, config, l, null);
+ }
+
+ /**
+ * Parses a given CityGML file and collects the contained CityObject Features in
+ * a {@link CityDoctorModel}.
+ *
+ * Features are held in a non-persistent, embedded Database with an in-memory
+ * upstream cache. Thus, strong references to {@link CityObject} are to be
+ * avoided, to allow for the memory management of the cache.
+ *
+ * @param filePath String path to the file
+ * @param config Configuration of the parser
+ * @param l Listener hook for tracking of parsing progress
+ * @param handler Validation handler for GML-file validation
+ * @return A {@link CityDoctorModel} object with the parsed Features
+ * @throws CityGmlParseException if parsing of the CityGML-file failed
+ * @throws InvalidGmlFileException if the GMLValidationHandler found an error
+ */
+ public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
+ GMLValidationHandler handler) throws CityGmlParseException, InvalidGmlFileException {
+ return parseCityGmlFile(filePath, config, l, handler, true, false);
+ }
+
+ /**
+ * Parses a given CityGML file and collects the contained CityObject Features in
+ * a {@link CityDoctorModel}.
+ *
+ * Features are held in a non-persistent, embedded Database with an in-memory
+ * upstream cache. Thus, strong references to {@link CityObject} are to be
+ * avoided, to allow for the memory management of the cache.
+ *
+ * @param filePath String path to the file
+ * @param config Configuration of the parser
+ * @param l Listener hook for tracking of parsing progress
+ * @param handler Validation handler for GML-file validation
+ * @param verbose If verbosity is enabled the parser will write info logs to
+ * the logger
+ * @param unconnected If true the parser will use an
+ * {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
+ * UnconnectedCache} to collect the parsed CityObjects
+ * @return A {@link CityDoctorModel} object with the parsed Features
+ * @throws CityGmlParseException if parsing of the CityGML-file failed
+ * @throws InvalidGmlFileException if the GMLValidationHandler found an error
+ */
+ private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
+ GMLValidationHandler handler, boolean verbose, boolean unconnected)
+ throws CityGmlParseException, InvalidGmlFileException {
+ CityGMLContext context = setupContext();
+ Path file = Paths.get(filePath);
+ List validationIssues = Collections.emptyList();
+ if (config.getValidate()) {
+ validationIssues = validateFile(handler, file);
+ }
+ try {
+ parseEpsgCodeFromFile(file, config, verbose);
+ CityGMLInputFactory in = context.createCityGMLInputFactory()
+ .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
+ try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
+ if (l != null) {
+ ois.addListener(l::updateProgress);
+ }
+ CityDoctorModel model = readAndKeepFeatures(config, file, in, ois, verbose, unconnected);
+ for (String xmlError : validationIssues) {
+ model.addGlobalError(new XMLValidationError(xmlError));
+ }
+ return model;
+ }
+ } catch (CityGMLReadException | IOException e) {
+ throw new CityGmlParseException("Failed to read CityGML file", e);
+ }
+ }
+
+ public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer,
+ String outputFile) throws CityGmlParseException {
+ streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile);
+ }
+
+ public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
+ CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
+ parseEpsgCodeFromFile(file, config, true);
+ startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile);
+ }
+
+ public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
+ try {
+ CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
+ .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
+ try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) {
+ while (reader.hasNext()) {
+ AbstractFeature chunk = reader.next();
+ if (chunk instanceof CityModel cModel) {
+ cModel.setCityObjectMembers(null);
+ return cModel;
+ }
+ }
+ }
+ } catch (CityGMLReadException e) {
+ throw new CityGmlParseException(e);
+ }
+ throw new CityGmlParseException("Did not find any CityModel in CityGML file");
+ }
+
+ private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l,
+ CityGmlConsumer cityObjectConsumer, String outputFile) {
+ try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
+ if (l != null) {
+ ois.addListener(l::updateProgress);
+ }
+ readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile);
+ } catch (IOException | CityGMLReadException e) {
+ logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
+ logger.catching(Level.ERROR, e);
+ }
+ }
+
+ private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
+ CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
+ Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
+ readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile, config, file);
+
+ }
+
+ private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
+ CityGmlConsumer cityObjectConsumer, String outputFile, ParserConfiguration config, Path file)
+ throws CityGMLReadException {
+ setupContext();
+ CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
+ .withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
+ CityGMLChunkWriter writer = null;
+ try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
+ CityDoctorModel model = mapper.getModel();
+ boolean isInitialized = false;
+ while (reader.hasNext()) {
+ AbstractFeature chunk = reader.next();
+ if (writer == null) {
+ writer = createCityModelWriter(outputFile, reader);
+ }
+ if (!isInitialized && writer != null && reader.getParentInfo() != null
+ && reader.getParentInfo().getTypeName().getLocalPart().equals("CityModel")) {
+ FeatureInfo parentInfo = reader.getParentInfo();
+ writer.withCityModelInfo(parentInfo);
+ isInitialized = true;
+ }
+ if (chunk instanceof AbstractCityObject ag) {
+ ag.accept(mapper);
+ drainCityModel(model, cityObjectConsumer);
+ writeAbstractCityObject(writer, ag);
+ } else if (chunk instanceof CityModel cModel) {
+ cModel.setCityObjectMembers(null);
+ mapper.setCityModel(cModel);
+ cityObjectConsumer.accept(cModel);
+ writeCityModel(writer, cModel);
+ } else if (writer != null) {
+ writer.writeMember(chunk);
+ }
+ }
+
+ // end of stream
+ logger.debug("End of gml file stream");
+ } catch (CityGMLReadException e) {
+ logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e);
+ } catch (CityGMLWriteException e) {
+ logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (CityGMLWriteException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException {
+ if (writer != null) {
+ for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) {
+ writer.writeMember(featureProp.getObject());
+ }
+ }
+ }
+
+ private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag)
+ throws CityGMLWriteException {
+ if (writer != null) {
+ writer.writeMember(ag);
+ }
+ }
+
+ private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader)
+ throws CityGMLWriteException {
+ if (outputFile == null) {
+ return null;
+ }
+ CityGMLContext gmlContext = CityGmlParser.setupContext();
+ CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
+ CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
+ CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile),
+ StandardCharsets.UTF_8.name());
+ writer.withPrefix("qual", QualityADEModule.NAMESPACE_URI);
+ writer.withSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "/qualityAde.xsd");
+ writer.withIndent(" ");
+ writer.withDefaultPrefixes();
+ writer.withDefaultSchemaLocations();
+ return writer;
+ }
+
+ private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
+ CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose, boolean unconnected)
+ throws CityGMLReadException {
+ return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
+ }
+
+ private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory,
+ ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
+ try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
+ CityGMLVersion version = null;
+ // model is read in chunked mode
+ // object members are replaced by href in model
+ // need to remove the refs and re-add unparsed objects
+ List acos = new ArrayList<>();
+ CityModel cgmlModel = null;
+ CityDoctorModel cdModel = mapper.getModel();
+ AutoCloseable parsingContext = cdModel.getCache().startParsingMode();
+ try (parsingContext) {
+ List queuedObjects = new ArrayList<>();
+ while (reader.hasNext()) {
+ AbstractFeature chunk = reader.next();
+ version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
+ if (chunk instanceof CityModel cModel) {
+ cModel.setCityObjectMembers(null);
+ mapper.setCityModel(cModel);
+ mapper.setCityGMLVersion(version);
+ cgmlModel = cModel;
+
+ if (!queuedObjects.isEmpty()) {
+ for (AbstractCityObject aco : queuedObjects) {
+ if (!cdModel.containsGmlId(aco.getId())) {
+ cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
+ }
+ }
+ }
+ } else if (chunk instanceof AbstractCityObject aco) {
+ acos.add(aco); // getid
+ String id = aco.getId();
+ aco.accept(mapper);
+ if (cgmlModel == null) {
+ // Add object to queue till the CityModel has been parsed
+ queuedObjects.add(aco);
+ } else if (!cdModel.containsGmlId(id)) {
+ cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
+ }
+ }
+ }
+
+ if (mapper.getModel().getCityModel() == null) {
+ // file does not contain a city model?
+ // create it for now
+ logger.warn(Localization.getText("CityGmlParser.missingCityModel"));
+ mapper.setCityModel(new CityModel());
+ for (AbstractCityObject aco : queuedObjects) {
+ if (!cdModel.containsGmlId(aco.getId())) {
+ mapper.getModel().getCityModel().getCityObjectMembers()
+ .add(new AbstractCityObjectProperty(aco));
+ }
+ }
+ }
+
+ ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache());
+ if (logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.parsedObjects"),
+ mapper.getModel().getNumberOfFeatures());
+ }
+ mapper.checkUnresolvedReferences();
+ mapper.setCityGMLVersion(version);
+ return mapper.getModel();
+ } catch (Exception e) {
+ throw new CityGMLReadException(e);
+ }
+
+ }
+ }
+
+ private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose)
+ throws CityGmlParseException {
+ try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
+ parseEpsgCodeFromStream(bis, config, verbose);
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ throw new CityGmlParseException("Failed to read CityGML file", e);
+ }
+ }
+
+ private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose)
+ throws ParserConfigurationException, SAXException {
+ SAXParser parser = FACTORY.newSAXParser();
+ CityGmlHandler handler = new CityGmlHandler();
+ try {
+ parser.parse(new InputSource(is), handler);
+ } catch (EnvelopeFoundException e) {
+ try {
+ parseCoordinateSystem(config, handler, verbose);
+ } catch (Exception e2) {
+ logEpsgParseError(e2);
+ }
+ } catch (Exception e) {
+ logEpsgParseError(e);
+ }
+ if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
+ }
+
+ }
+
+ private static void logEpsgParseError(Exception e) {
+ logger.debug("Exception while parsing for EPSG code", e);
+ if (logger.isWarnEnabled()) {
+ logger.warn(Localization.getText("CityGmlParser.failedEPSGParse"));
+ }
+ }
+
+ private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
+ if (handler.getEpsg() == null) {
+ return;
+ }
+ CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
+ if (crs == null) {
+ // could not find a coordinate system for srsName
+ // assuming metric system
+ if (logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
+ }
+ return;
+ }
+ if (crs.getProjection().getUnits() == Units.METRES) {
+ // coordinate system is in meters, do not convert
+ if (logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.noConversionNeeded"));
+ }
+ return;
+ }
+ parseMeterConversion(config, crs);
+ Vector3d low = handler.getLowerCorner();
+ Vector3d up = handler.getUpperCorner();
+ double centerLong = low.getX() + ((up.getX() - low.getX()) / 2);
+ double centerLat = low.getY() + ((up.getY() - low.getY()) / 2);
+ if (!crs.getName().equals(WGS_84)) {
+ // need to convert coordinates first to WGS84, then find UTM Zone
+ CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
+ ProjCoordinate p1 = new ProjCoordinate();
+ p1.setValue(centerLong, centerLat);
+ ProjCoordinate p2 = new ProjCoordinate();
+ BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
+ bct.transform(p1, p2);
+ centerLong = p2.x;
+ centerLat = p2.y;
+ }
+ int zone = (int) (31 + Math.round(centerLong / 6));
+ CoordinateReferenceSystem utm;
+ if (centerLat < 0) {
+ // south
+ if (logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone);
+ }
+ utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
+ } else {
+ // north
+ if (logger.isInfoEnabled() && verbose) {
+ logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone);
+ }
+ utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
+ }
+ config.setCoordinateSystem(crs, utm);
+ }
+
+ private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
+ Projection projection = crs.getProjection();
+ double fromMetres = projection.getFromMetres();
+ if (fromMetres > 0) {
+ // also transform height information
+ config.setFromMeters(fromMetres);
+ } else {
+ config.setFromMeters(1.0);
+ }
+ }
+
+ /**
+ * The srsName (The name by which this reference system is identified) inside
+ * the CityGML file can have multiple formats. This method tries to parse the
+ * string and detect the corresponding reference system. If it is found, it
+ * returns a proj4j.CoordinateReferenceSystem. It throws an
+ * IllegalArgumentException otherwise.
+ *
+ * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
+ * Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
+ * those are also included. It isn't guaranteed that those formats are correctly
+ * parsed, though.
+ *
+ * The EPSG ids and parameters are defined in resources ('nad/epsg') inside
+ * proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
+ *
+ * @param srsName
+ * @return CoordinateReferenceSystem
+ */
+ private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
+ srsName = srsName.trim();
+ Matcher mEPSG = P_EPSG.matcher(srsName);
+ if (mEPSG.find()) {
+ if ("EPSG:4979".contentEquals(srsName)) {
+ srsName = "EPSG:4236";
+ } else if ("EPSG:7415".contentEquals(srsName)) {
+ return CRS_FACTORY.createFromParameters("EPSG:7415",
+ "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs");
+ }
+ return CRS_FACTORY.createFromName(srsName);
+ }
+
+ Matcher mOGC = P_OGC.matcher(srsName);
+ if (mOGC.find()) {
+ return getCrsFromOGCSrsCode(mOGC.group(1));
+ }
+ Matcher mOGC2 = P_OGC2.matcher(srsName);
+ if (mOGC2.find()) {
+ return getCrsFromOGCSrsCode(mOGC2.group(1));
+
+ }
+ Matcher mURN = P_URN.matcher(srsName);
+ // NOTE: Could use a HashMap if the switch/case becomes too long.
+ if (mURN.find()) {
+ return switch (mURN.group(1)) {
+ case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
+ case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
+ case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
+ case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
+ case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
+ default -> null;
+ };
+ }
+ if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) {
+ return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
+ }
+ return null;
+ }
+
+ private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) {
+ String crsName = "EPSG:" + srsCode;
+ List params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList());
+ params.removeIf(P_VUNITS.asPredicate());
+ params.removeIf(P_GEOIDGRID.asPredicate());
+ return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
+ }
+
+ public static List validateFile(GMLValidationHandler handler, Path file)
+ throws CityGmlParseException {
+ if (handler == null) {
+ handler = new GMLValidationHandler();
+ }
+ setupContext();
+ try {
+ SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
+ readAdditionalSchemaDefinitions(context, file, schemaHandler);
+ Source[] schemas = schemaHandler.getSchemas();
+ SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ Schema schema = schemaFactory.newSchema(schemas);
+ Validator validator = schema.newValidator();
+ validator.setErrorHandler(handler);
+ validator.validate(new StreamSource(file.toFile()));
+ return handler.getMessages();
+ } catch (SchemaHandlerException | SAXException | IOException e) {
+ throw new CityGmlParseException("Failed to validate CityGML file", e);
+ }
+ }
+
+ private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
+ throws CityGmlParseException {
+ try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()).withSchemaHandler(schemaHandler)
+ .createReader(file)) {
+ reader.nextTag();
+ } catch (Exception e) {
+ throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
+ }
+ }
+
+ private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
+ List id = new ArrayList<>();
+ model.createFeatureStream().forEach(co -> {
+ cityObjectConsumer.accept(co);
+ id.add(co.getGmlId());
+ });
+ id.forEach(model::removeFeature);
+
+ }
}
diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/CityGmlUtils.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/CityGmlUtils.java
index 564e68df9d83174fa597c6d355d4a90a3a42d0e2..33a1e3bae7967734884eb28b620fe144084fa81f 100644
--- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/CityGmlUtils.java
+++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/CityGmlUtils.java
@@ -41,175 +41,174 @@ import java.util.Set;
*/
public final class CityGmlUtils {
-
- private CityGmlUtils() {
- // util class
- }
-
- public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
- Polygon cdPoly, ParserConfiguration config) {
- if (cdPoly.getExteriorRing() == null) {
- return null;
- }
- org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
- // exterior ring
- LinearRing extLr = cdPoly.getExteriorRing();
- if (extLr.getVertices().size() < 3) {
- // this ring does not have enough points in it
- // this leads to errors when exporting therefore ignore it
- return null;
- }
- org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
- gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
-
- // interior rings
- for (LinearRing lr : cdPoly.getInnerRings()) {
- gmlLr = createGmlRing(factory, config, lr);
- if (lr.getVertices().size() < 3) {
- // this ring does not have enough points in it
- // this leads to errors when exporting therefore ignore it
- return null;
- }
- gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
- }
- gmlPoly.setId(cdPoly.getGmlId().getGmlString());
- return gmlPoly;
- }
-
- public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp,
- ParserConfiguration config) {
- List surfaces = new ArrayList<>();
- for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) {
- surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config)));
- }
- for (CompositeCollection cc : comp.getChildComposites()) {
- surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config)));
- }
- if (surfaces.isEmpty()) {
- return null;
- }
- return new CompositeSurface(surfaces);
- }
-
-
- public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
- ParserConfiguration config, LinearRing lr) {
-
- ProjCoordinate p1 = new ProjCoordinate();
- ProjCoordinate p2 = new ProjCoordinate();
- List ringValues = new ArrayList<>();
- BasicCoordinateTransform trans = config.getOriginalTransform();
- for (Vertex v : lr.getVertices()) {
- double x = v.getX();
- double y = v.getY();
- double z = v.getZ();
- if (trans != null) {
- p1.x = x;
- p1.y = y;
- trans.transform(p1, p2);
- x = p2.x;
- y = p2.y;
- z = z * config.getFromMeters();
- }
- ringValues.add(x);
- ringValues.add(y);
- ringValues.add(z);
- }
-
- org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
- gmlLr.setId(lr.getGmlId().getGmlString());
- return gmlLr;
- }
-
- public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
- if (geom.getType() != GeometryType.SOLID) {
- throw new IllegalArgumentException("Only solids are allowed");
- }
-
- CompositeSurface comp = new CompositeSurface();
- List surfaceMember = comp.getSurfaceMembers();
- for (Polygon cdPoly : geom.getPolygons()) {
- if (!cdPoly.isLink()) {
- org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
- if (gmlPoly != null) {
- surfaceMember.add(new SurfaceProperty(gmlPoly));
- }
- } else {
- // add reference to polygon
- surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
- }
- }
-
- if (surfaceMember.isEmpty()) {
- return null;
- }
- Solid solid = new Solid();
- Shell shell = new Shell(surfaceMember);
- solid.setExterior(new ShellProperty(shell));
- solid.setId(geom.getGmlId().getGmlString());
- return solid;
- }
-
- public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory,
- ParserConfiguration config) {
- if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) {
- throw new IllegalArgumentException("This can only handle MultiSurfaces");
- }
- List surfaces = new ArrayList<>();
- Set compositeCollections = new HashSet<>();
- for (Polygon cdPoly : geom.getPolygons()) {
- if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) {
- compositeCollections.add(conc.getPartOfComposite());
- } else if (!cdPoly.isLink()) {
- // is not part of a boundary surface
- org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
- if (gmlPoly != null) {
- surfaces.add(new SurfaceProperty(gmlPoly));
- }
- } else {
- // add reference to polygon
- surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
- }
- }
- for (CompositeCollection cdPoly : compositeCollections) {
- surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config)));
- }
- if (surfaces.isEmpty()) {
- return null;
- }
- MultiSurface ms = new MultiSurface(surfaces);
- ms.setId(geom.getGmlId().getGmlString());
- return ms;
- }
-
- public static MultiSurface createMultiSurface(List polygons, GeometryFactory factory,
- ParserConfiguration config) {
- List surfaces = new ArrayList<>();
- for (Polygon cdPoly : polygons) {
- org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
- if (gmlPoly != null) {
- surfaces.add(new SurfaceProperty(gmlPoly));
- }
- }
- if (surfaces.isEmpty()) {
- return null;
- }
- return new MultiSurface(surfaces);
- }
-
- public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
- ParserConfiguration config) {
- List surfaces = new ArrayList<>();
- for (Polygon cdPoly : geom.getPolygons()) {
- org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
- if (gmlPoly != null) {
- surfaces.add(new SurfaceProperty(gmlPoly));
- }
- }
- if (surfaces.isEmpty()) {
- return null;
- }
- return new CompositeSurface(surfaces);
- }
+ private CityGmlUtils() {
+ // util class
+ }
+
+ public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
+ Polygon cdPoly, ParserConfiguration config) {
+ if (cdPoly.getExteriorRing() == null) {
+ return null;
+ }
+ org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
+ // exterior ring
+ LinearRing extLr = cdPoly.getExteriorRing();
+ if (extLr.getVertices().size() < 3) {
+ // this ring does not have enough points in it
+ // this leads to errors when exporting therefore ignore it
+ return null;
+ }
+ org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
+ gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
+
+ // interior rings
+ for (LinearRing lr : cdPoly.getInnerRings()) {
+ gmlLr = createGmlRing(factory, config, lr);
+ if (lr.getVertices().size() < 3) {
+ // this ring does not have enough points in it
+ // this leads to errors when exporting therefore ignore it
+ return null;
+ }
+ gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
+ }
+ gmlPoly.setId(cdPoly.getGmlId().getGmlString());
+ return gmlPoly;
+ }
+
+ public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp,
+ ParserConfiguration config) {
+ List surfaces = new ArrayList<>();
+ for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) {
+ surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config)));
+ }
+ for (CompositeCollection cc : comp.getChildComposites()) {
+ surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config)));
+ }
+ if (surfaces.isEmpty()) {
+ return null;
+ }
+ return new CompositeSurface(surfaces);
+ }
+
+ public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
+ ParserConfiguration config, LinearRing lr) {
+
+ ProjCoordinate p1 = new ProjCoordinate();
+ ProjCoordinate p2 = new ProjCoordinate();
+ List ringValues = new ArrayList<>();
+ BasicCoordinateTransform trans = config.getOriginalTransform();
+ for (Vertex v : lr.getVertices()) {
+ double x = v.getX();
+ double y = v.getY();
+ double z = v.getZ();
+ if (trans != null) {
+ p1.x = x;
+ p1.y = y;
+ trans.transform(p1, p2);
+ x = p2.x;
+ y = p2.y;
+ z = z * config.getFromMeters();
+ }
+ ringValues.add(x);
+ ringValues.add(y);
+ ringValues.add(z);
+ }
+
+ org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
+ gmlLr.setId(lr.getGmlId().getGmlString());
+ return gmlLr;
+ }
+
+ public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
+ if (geom.getType() != GeometryType.SOLID) {
+ throw new IllegalArgumentException("Only solids are allowed");
+ }
+
+ CompositeSurface comp = new CompositeSurface();
+ List surfaceMember = comp.getSurfaceMembers();
+ for (Polygon cdPoly : geom.getPolygons()) {
+ if (!cdPoly.isLink()) {
+ org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
+ config);
+ if (gmlPoly != null) {
+ surfaceMember.add(new SurfaceProperty(gmlPoly));
+ }
+ } else {
+ // add reference to polygon
+ surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
+ }
+ }
+
+ if (surfaceMember.isEmpty()) {
+ return null;
+ }
+ Solid solid = new Solid();
+ Shell shell = new Shell(surfaceMember);
+ solid.setExterior(new ShellProperty(shell));
+ solid.setId(geom.getGmlId().getGmlString());
+ return solid;
+ }
+
+ public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
+ if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) {
+ throw new IllegalArgumentException("This can only handle MultiSurfaces");
+ }
+ List surfaces = new ArrayList<>();
+ Set compositeCollections = new HashSet<>();
+ for (Polygon cdPoly : geom.getPolygons()) {
+ if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) {
+ compositeCollections.add(conc.getPartOfComposite());
+ } else if (!cdPoly.isLink()) {
+ // is not part of a boundary surface
+ org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
+ config);
+ if (gmlPoly != null) {
+ surfaces.add(new SurfaceProperty(gmlPoly));
+ }
+ } else {
+ // add reference to polygon
+ surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
+ }
+ }
+ for (CompositeCollection cdPoly : compositeCollections) {
+ surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config)));
+ }
+ if (surfaces.isEmpty()) {
+ return null;
+ }
+ MultiSurface ms = new MultiSurface(surfaces);
+ ms.setId(geom.getGmlId().getGmlString());
+ return ms;
+ }
+
+ public static MultiSurface createMultiSurface(List polygons, GeometryFactory factory,
+ ParserConfiguration config) {
+ List surfaces = new ArrayList<>();
+ for (Polygon cdPoly : polygons) {
+ org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
+ if (gmlPoly != null) {
+ surfaces.add(new SurfaceProperty(gmlPoly));
+ }
+ }
+ if (surfaces.isEmpty()) {
+ return null;
+ }
+ return new MultiSurface(surfaces);
+ }
+
+ public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
+ ParserConfiguration config) {
+ List surfaces = new ArrayList<>();
+ for (Polygon cdPoly : geom.getPolygons()) {
+ org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
+ if (gmlPoly != null) {
+ surfaces.add(new SurfaceProperty(gmlPoly));
+ }
+ }
+ if (surfaces.isEmpty()) {
+ return null;
+ }
+ return new CompositeSurface(surfaces);
+ }
}
diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearVisitorResultsVisitor.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearResultsVisitor.java
similarity index 84%
rename from CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearVisitorResultsVisitor.java
rename to CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearResultsVisitor.java
index 15436a7ea731ce461b70449795359e3314baebea..0aa8f06c0b5ef1a49861108015efe5dbabe14d66 100644
--- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearVisitorResultsVisitor.java
+++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/utils/visitors/ClearResultsVisitor.java
@@ -6,7 +6,7 @@ import de.hft.stuttgart.citydoctor2.check.Checkable;
/**
* This Visitor removes the check results from a CityObject and the objects in its datastructure.
*/
-public class ClearVisitorResultsVisitor extends CheckableUtilsVisitor {
+public class ClearResultsVisitor extends CheckableUtilsVisitor {
@Override
public void check(Checkable checkable) {
diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/writer/CityGMLWriterUtils.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/writer/CityGMLWriterUtils.java
index e3b9cbfb9cfba0e2eed6f3dbaa57e6a93cf15f71..b73c1d97041cec5935d90a8fc55de366528d966f 100644
--- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/writer/CityGMLWriterUtils.java
+++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/writer/CityGMLWriterUtils.java
@@ -65,7 +65,7 @@ public class CityGMLWriterUtils {
}
public static void writeCityModel(String file, CityDoctorModel model) throws CityDoctorWriteException {
- CityGMLContext gmlContext = CityGmlParser.getContext();
+ CityGMLContext gmlContext = CityGmlParser.setupContext();
CityModel cModel = model.getCityModel();
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(model.getCityGMLVersion());
try (CityGMLWriter writer = factory.createCityGMLWriter(new File(file))) {
diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/Attributes.java b/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/Attributes.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4460515e62e32cf50623f732ddb87ff99315132
--- /dev/null
+++ b/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/Attributes.java
@@ -0,0 +1,74 @@
+package org.xmlobjects.xml;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+
+public class Attributes implements Serializable {
+
+ private static final long serialVersionUID = 8942583366234714632L;
+
+ private final Map> attributes = new HashMap<>();
+
+ public boolean isEmpty() {
+ return attributes.isEmpty();
+ }
+
+ public void add(String namespaceURI, String localName, TextContent value) {
+ attributes.computeIfAbsent(namespaceURI, v -> new HashMap<>()).put(localName, value);
+ }
+
+ public void add(String namespaceURI, String localName, String value) {
+ add(namespaceURI, localName, TextContent.of(value));
+ }
+
+ public void add(String localName, TextContent value) {
+ add(XMLConstants.NULL_NS_URI, localName, value);
+ }
+
+ public void add(String localName, String value) {
+ add(localName, TextContent.of(value));
+ }
+
+ public void add(QName name, TextContent value) {
+ add(name.getNamespaceURI(), name.getLocalPart(), value);
+ }
+
+ public void add(QName name, String value) {
+ add(name, TextContent.of(value));
+ }
+
+ public void addAll(String namespaceURI, Map attributes) {
+ this.attributes.computeIfAbsent(namespaceURI, v -> new HashMap<>()).putAll(attributes);
+ }
+
+ public Map> get() {
+ return attributes;
+ }
+
+ public Map get(String namespaceURI) {
+ return attributes.getOrDefault(namespaceURI, Collections.emptyMap());
+ }
+
+ public TextContent getValue(String localName) {
+ return getValue(XMLConstants.NULL_NS_URI, localName);
+ }
+
+ public TextContent getValue(String namespaceURI, String localName) {
+ return get(namespaceURI).getOrDefault(localName, TextContent.empty());
+ }
+
+ public TextContent getValue(QName name) {
+ return getValue(name.getNamespaceURI(), name.getLocalPart());
+ }
+
+ public Attributes copy() {
+ Attributes copy = new Attributes();
+ copy.attributes.putAll(attributes);
+ return copy;
+ }
+}
diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/TextContent.java b/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/TextContent.java
new file mode 100644
index 0000000000000000000000000000000000000000..9537ddd8acdfd218947c256f943e64f558900616
--- /dev/null
+++ b/CityDoctorParent/CityDoctorModel/src/main/java/org/xmlobjects/xml/TextContent.java
@@ -0,0 +1,1049 @@
+package org.xmlobjects.xml;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.Duration;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+public class TextContent implements Serializable {
+
+ private static final long serialVersionUID = -3862132079185243841L;
+ private static final DatatypeFactory XML_TYPE_FACTORY;
+ private static final TextContent EMPTY = new TextContent("");
+
+ private static Function ZONE_OFFSET_PROVIDER = dateTime -> ZoneOffset.systemDefault().getRules().getOffset(dateTime);
+ private static boolean WITH_TIME_OFFSET = true;
+ private static boolean WITH_DATE_OFFSET = false;
+
+ private enum Fields { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, NANO, TIMEZONE }
+ private static final EnumSet DATE_TIME_FIELDS = EnumSet.allOf(Fields.class);
+ private static final EnumSet TIME_FIELDS = EnumSet.of(Fields.HOUR, Fields.MINUTE, Fields.SECOND, Fields.NANO, Fields.TIMEZONE);
+ private static final EnumSet DATE_FIELDS = EnumSet.of(Fields.YEAR, Fields.MONTH, Fields.DAY, Fields.TIMEZONE);
+ private static final EnumSet GYEAR_MONTH_FIELDS = EnumSet.of(Fields.YEAR, Fields.MONTH, Fields.TIMEZONE);
+ private static final EnumSet GMONTH_DAY_FIELDS = EnumSet.of(Fields.MONTH, Fields.DAY, Fields.TIMEZONE);
+ private static final EnumSet GDAY_FIELDS = EnumSet.of(Fields.DAY, Fields.TIMEZONE);
+ private static final EnumSet GMONTH_FIELDS = EnumSet.of(Fields.MONTH, Fields.TIMEZONE);
+ private static final EnumSet GYEAR_FIELDS = EnumSet.of(Fields.YEAR, Fields.TIMEZONE);
+
+ static {
+ try {
+ XML_TYPE_FACTORY = DatatypeFactory.newInstance();
+ } catch (DatatypeConfigurationException e) {
+ throw new RuntimeException("Failed to initialize datatype factory.", e);
+ }
+ }
+
+ private String content;
+ private String trimmedContent;
+ private String[] tokenizedContent;
+ private Object value;
+
+ private transient Function zoneOffsetProvider = ZONE_OFFSET_PROVIDER;
+
+ private TextContent(String content) {
+ this.content = Objects.requireNonNull(content, "Content must not be null.");
+ }
+
+ public static TextContent empty() {
+ return EMPTY;
+ }
+
+ public static TextContent of(String content) {
+ return ofObject(content);
+ }
+
+ public static TextContent ofList(List content) {
+ return ofObjectList(content);
+ }
+
+ public static TextContent ofBoolean(Boolean content) {
+ return ofObject(content);
+ }
+
+ public static TextContent ofBooleanList(List content) {
+ return ofObjectList(content);
+ }
+
+ public static TextContent ofDouble(Double content) {
+ return ofObject(content);
+ }
+
+ public static TextContent ofDoubleList(List content) {
+ return ofObjectList(content);
+ }
+
+ public static TextContent ofInteger(Integer content) {
+ return ofObject(content);
+ }
+
+ public static TextContent ofIntegerList(List content) {
+ return ofObjectList(content);
+ }
+
+ public static TextContent ofDuration(Duration content) {
+ return ofObject(content);
+ }
+
+ public static TextContent ofDurationList(List content) {
+ return ofObjectList(content);
+ }
+
+ public static TextContent ofDateTime(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, DATE_TIME_FIELDS, withOffset);
+ }
+
+ public static TextContent ofDateTime(OffsetDateTime content) {
+ return ofOffsetDateTime(content, DATE_TIME_FIELDS, WITH_TIME_OFFSET);
+ }
+
+ public static TextContent ofDateTimeList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, DATE_TIME_FIELDS, withOffset);
+ }
+
+ public static TextContent ofDateTimeList(List content) {
+ return ofOffsetDateTimeList(content, DATE_TIME_FIELDS, WITH_TIME_OFFSET);
+ }
+
+ public static TextContent ofTime(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, TIME_FIELDS, withOffset);
+ }
+
+ public static TextContent ofTime(OffsetDateTime content) {
+ return ofOffsetDateTime(content, TIME_FIELDS, WITH_TIME_OFFSET);
+ }
+
+ public static TextContent ofTimeList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, TIME_FIELDS, withOffset);
+ }
+
+ public static TextContent ofTimeList(List content) {
+ return ofOffsetDateTimeList(content, TIME_FIELDS, WITH_TIME_OFFSET);
+ }
+
+ public static TextContent ofDate(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, DATE_FIELDS, withOffset);
+ }
+
+ public static TextContent ofDate(OffsetDateTime content) {
+ return ofOffsetDateTime(content, DATE_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofDateList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, DATE_FIELDS, withOffset);
+ }
+
+ public static TextContent ofDateList(List content) {
+ return ofOffsetDateTimeList(content, DATE_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGYearMonth(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, GYEAR_MONTH_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGYearMonth(OffsetDateTime content) {
+ return ofOffsetDateTime(content, GYEAR_MONTH_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGYearMonthList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, GYEAR_MONTH_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGYearMonthList(List content) {
+ return ofOffsetDateTimeList(content, GYEAR_MONTH_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGMonthDay(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, GMONTH_DAY_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGMonthDay(OffsetDateTime content) {
+ return ofOffsetDateTime(content, GMONTH_DAY_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGMonthDayList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, GMONTH_DAY_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGMonthDayList(List content) {
+ return ofOffsetDateTimeList(content, GMONTH_DAY_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGDay(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, GDAY_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGDay(OffsetDateTime content) {
+ return ofOffsetDateTime(content, GDAY_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGDayList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, GDAY_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGDayList(List content) {
+ return ofOffsetDateTimeList(content, GDAY_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGMonth(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, GMONTH_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGMonth(OffsetDateTime content) {
+ return ofOffsetDateTime(content, GMONTH_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGMonthList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, GMONTH_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGMonthList(List content) {
+ return ofOffsetDateTimeList(content, GMONTH_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGYear(OffsetDateTime content, boolean withOffset) {
+ return ofOffsetDateTime(content, GYEAR_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGYear(OffsetDateTime content) {
+ return ofOffsetDateTime(content, GYEAR_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public static TextContent ofGYearList(List content, boolean withOffset) {
+ return ofOffsetDateTimeList(content, GYEAR_FIELDS, withOffset);
+ }
+
+ public static TextContent ofGYearList(List content) {
+ return ofOffsetDateTimeList(content, GYEAR_FIELDS, WITH_DATE_OFFSET);
+ }
+
+ public boolean isEmpty() {
+ return this == EMPTY;
+ }
+
+ public TextContent trim() {
+ content = trimmedContent();
+ return this;
+ }
+
+ public TextContent normalize() {
+ int length = content.length();
+ if (length != 0) {
+ StringBuilder normalized = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ char ch = content.charAt(i);
+ normalized.append(isWhiteSpace(ch) ? ' ' : ch);
+ }
+
+ content = normalized.toString();
+ }
+
+ return this;
+ }
+
+ public TextContent collapse() {
+ int length = content.length();
+ if (length != 0) {
+ char ch = 0;
+ int i = 0;
+
+ for (; i < length; i++) {
+ ch = content.charAt(i);
+ if (!isWhiteSpace(ch))
+ break;
+ }
+
+ if (i != length) {
+ StringBuilder collapsed = new StringBuilder(length - i).append(ch);
+ boolean isWhiteSpace = false;
+
+ for (i += 1; i < length; i++) {
+ ch = content.charAt(i);
+ if (isWhiteSpace(ch)) {
+ isWhiteSpace = true;
+ } else {
+ if (isWhiteSpace) {
+ collapsed.append(' ');
+ isWhiteSpace = false;
+ }
+
+ collapsed.append(ch);
+ }
+ }
+
+ content = trimmedContent = collapsed.toString();
+ } else
+ content = trimmedContent = "";
+ }
+
+ return this;
+ }
+
+ public TextContent filter(Predicate predicate) {
+ return predicate.test(this) ? this : EMPTY;
+ }
+
+ public String get() {
+ return isPresent() ? content : null;
+ }
+
+ public boolean isPresent() {
+ return this != EMPTY;
+ }
+
+ public void ifPresent(Consumer action) {
+ if (isPresent())
+ action.accept(content);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAsList() {
+ if (isListOfType(value, String.class)) {
+ return (List) value;
+ } else if (tokenizeContent() != 0) {
+ List strings = new ArrayList<>(tokenizedContent.length);
+ Collections.addAll(strings, tokenizedContent);
+ return setValue(strings);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isList() {
+ return getAsList() != null;
+ }
+
+ public void ifList(Consumer> action) {
+ List value = getAsList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public Boolean getAsBoolean() {
+ return value instanceof Boolean bool ? bool : setValue(toBoolean(trimmedContent()));
+ }
+
+ public boolean isBoolean() {
+ return getAsBoolean() != null;
+ }
+
+ public void ifBoolean(Consumer action) {
+ Boolean value = getAsBoolean();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAsBooleanList() {
+ if (isListOfType(value, Boolean.class)) {
+ return (List) value;
+ } else if (tokenizeContent() != 0) {
+ List booleans = new ArrayList<>(tokenizedContent.length);
+ for (String token : tokenizedContent) {
+ Boolean value = toBoolean(token);
+ if (value != null) {
+ booleans.add(value);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ return setValue(booleans);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isBooleanList() {
+ return getAsBooleanList() != null;
+ }
+
+ public void ifBooleanList(Consumer> action) {
+ List value = getAsBooleanList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public Double getAsDouble() {
+ if (value instanceof Double doubleValue) {
+ return doubleValue;
+ } else if (!isEmpty() && !trimmedContent().isEmpty()) {
+ try {
+ return setValue(Double.parseDouble(trimmedContent));
+ } catch (NumberFormatException e) {
+ return setValue(null);
+ }
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isDouble() {
+ return getAsDouble() != null;
+ }
+
+ public void ifDouble(Consumer action) {
+ Double value = getAsDouble();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAsDoubleList() {
+ if (isListOfType(value, Double.class)) {
+ return (List) value;
+ } else if (tokenizeContent() != 0) {
+ List doubles = new ArrayList<>(tokenizedContent.length);
+ for (String token : tokenizedContent) {
+ try {
+ doubles.add(Double.parseDouble(token));
+ } catch (NumberFormatException e) {
+ return setValue(null);
+ }
+ }
+
+ return setValue(doubles);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isDoubleList() {
+ return getAsDoubleList() != null;
+ }
+
+ public void ifDoubleList(Consumer> action) {
+ List value = getAsDoubleList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public Integer getAsInteger() {
+ if (value instanceof Integer intValue) {
+ return intValue;
+ } else if (!isEmpty() && !trimmedContent().isEmpty()) {
+ try {
+ return setValue(Integer.parseInt(trimmedContent));
+ } catch (NumberFormatException e) {
+ return setValue(null);
+ }
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isInteger() {
+ return getAsInteger() != null;
+ }
+
+ public void ifInteger(Consumer action) {
+ Integer value = getAsInteger();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAsIntegerList() {
+ if (isListOfType(value, Integer.class)) {
+ return (List) value;
+ } else if (tokenizeContent() != 0) {
+ List integers = new ArrayList<>(tokenizedContent.length);
+ for (String token : tokenizedContent) {
+ try {
+ integers.add(Integer.parseInt(token));
+ } catch (NumberFormatException e) {
+ return setValue(null);
+ }
+ }
+
+ return setValue(integers);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isIntegerList() {
+ return getAsIntegerList() != null;
+ }
+
+ public void ifIntegerList(Consumer> action) {
+ List value = getAsIntegerList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public Duration getAsDuration() {
+ if (value instanceof Duration duration) {
+ return duration;
+ } else if (!isEmpty() && !trimmedContent().isEmpty()) {
+ try {
+ return setValue(XML_TYPE_FACTORY.newDuration(trimmedContent));
+ } catch (Throwable e) {
+ return setValue(null);
+ }
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isDuration() {
+ return getAsDuration() != null;
+ }
+
+ public void ifDuration(Consumer action) {
+ Duration value = getAsDuration();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAsDurationList() {
+ if (isListOfType(value, Duration.class)) {
+ return (List) value;
+ } else if (tokenizeContent() != 0) {
+ List durations = new ArrayList<>(tokenizedContent.length);
+ for (String token : tokenizedContent) {
+ try {
+ durations.add(XML_TYPE_FACTORY.newDuration(token));
+ } catch (Throwable e) {
+ return setValue(null);
+ }
+ }
+
+ return setValue(durations);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ public boolean isDurationList() {
+ return getAsDurationList() != null;
+ }
+
+ public void ifDurationList(Consumer> action) {
+ List value = getAsDurationList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsDateTime() {
+ return getAsOffsetDateTime("dateTime");
+ }
+
+ public boolean isDateTime() {
+ return getAsDateTime() != null;
+ }
+
+ public void ifDateTime(Consumer action) {
+ OffsetDateTime value = getAsDateTime();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsDateTimeList() {
+ return getAsOffsetDateTimeList("dateTime");
+ }
+
+ public boolean isDateTimeList() {
+ return getAsDateTimeList() != null;
+ }
+
+ public void ifDateTimeList(Consumer> action) {
+ List value = getAsDateTimeList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsTime() {
+ return getAsOffsetDateTime("time");
+ }
+
+ public boolean isTime() {
+ return getAsTime() != null;
+ }
+
+ public void ifTime(Consumer action) {
+ OffsetDateTime value = getAsTime();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsTimeList() {
+ return getAsOffsetDateTimeList("time");
+ }
+
+ public boolean isTimeList() {
+ return getAsTimeList() != null;
+ }
+
+ public void ifTimeList(Consumer> action) {
+ List value = getAsTimeList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsDate() {
+ return getAsOffsetDateTime("date");
+ }
+
+ public boolean isDate() {
+ return getAsDate() != null;
+ }
+
+ public void ifDate(Consumer action) {
+ OffsetDateTime value = getAsDate();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsDateList() {
+ return getAsOffsetDateTimeList("date");
+ }
+
+ public boolean isDateList() {
+ return getAsDateList() != null;
+ }
+
+ public void ifDateList(Consumer> action) {
+ List value = getAsDateList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsGYearMonth() {
+ return getAsOffsetDateTime("gYearMonth");
+ }
+
+ public boolean isGYearMonth() {
+ return getAsGYearMonth() != null;
+ }
+
+ public void ifGYearMonth(Consumer action) {
+ OffsetDateTime value = getAsGYearMonth();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsGYearMonthList() {
+ return getAsOffsetDateTimeList("gYearMonth");
+ }
+
+ public boolean isGYearMonthList() {
+ return getAsGYearMonthList() != null;
+ }
+
+ public void ifGYearMonthList(Consumer> action) {
+ List value = getAsGYearMonthList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsGMonthDay() {
+ return getAsOffsetDateTime("gMonthDay");
+ }
+
+ public boolean isGMonthDay() {
+ return getAsGMonthDay() != null;
+ }
+
+ public void ifGMonthDay(Consumer action) {
+ OffsetDateTime value = getAsGMonthDay();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsGMonthDayList() {
+ return getAsOffsetDateTimeList("gMonthDay");
+ }
+
+ public boolean isGMonthDayList() {
+ return getAsGMonthDayList() != null;
+ }
+
+ public void ifGMonthDayList(Consumer> action) {
+ List value = getAsGMonthDayList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsGDay() {
+ return getAsOffsetDateTime("gDay");
+ }
+
+ public boolean isGDay() {
+ return getAsGDay() != null;
+ }
+
+ public void ifGDay(Consumer action) {
+ OffsetDateTime value = getAsGDay();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsGDayList() {
+ return getAsOffsetDateTimeList("gDay");
+ }
+
+ public boolean isGDayList() {
+ return getAsGDayList() != null;
+ }
+
+ public void ifGDayList(Consumer> action) {
+ List value = getAsGDayList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsGMonth() {
+ return getAsOffsetDateTime("gMonth");
+ }
+
+ public boolean isGMonth() {
+ return getAsGMonth() != null;
+ }
+
+ public void ifGMonth(Consumer action) {
+ OffsetDateTime value = getAsGMonth();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsGMonthList() {
+ return getAsOffsetDateTimeList("gMonth");
+ }
+
+ public boolean isGMonthList() {
+ return getAsGMonthList() != null;
+ }
+
+ public void ifGMonthList(Consumer> action) {
+ List value = getAsGMonthList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public OffsetDateTime getAsGYear() {
+ return getAsOffsetDateTime("gYear");
+ }
+
+ public boolean isGYear() {
+ return getAsGYear() != null;
+ }
+
+ public void ifGYear(Consumer action) {
+ OffsetDateTime value = getAsGYear();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public List getAsGYearList() {
+ return getAsOffsetDateTimeList("gYear");
+ }
+
+ public boolean isGYearList() {
+ return getAsGYearList() != null;
+ }
+
+ public void ifGYearList(Consumer> action) {
+ List value = getAsGYearList();
+ if (value != null) {
+ action.accept(value);
+ }
+ }
+
+ public TextContent withZoneOffsetProvider(Function zoneOffsetProvider) {
+ if (zoneOffsetProvider != null) {
+ this.zoneOffsetProvider = zoneOffsetProvider;
+ }
+
+ return this;
+ }
+
+ private OffsetDateTime getAsOffsetDateTime(String localName) {
+ if (value instanceof XMLGregorianCalendar calendar
+ && calendar.getXMLSchemaType().getLocalPart().equals(localName)) {
+ return toOffsetDateTime((XMLGregorianCalendar) value);
+ } else if (!isEmpty() && !trimmedContent().isEmpty()) {
+ XMLGregorianCalendar calendar = setValue(toCalendar(trimmedContent(), localName));
+ return calendar != null ? toOffsetDateTime(calendar) : null;
+ } else {
+ return setValue(null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List getAsOffsetDateTimeList(String localName) {
+ List calendars;
+ if (value instanceof List> list
+ && list.stream().allMatch(v -> v instanceof XMLGregorianCalendar calendar
+ && calendar.getXMLSchemaType().getLocalPart().equals(localName))) {
+ calendars = (List) value;
+ } else if (tokenizeContent() != 0) {
+ calendars = new ArrayList<>(tokenizedContent.length);
+ for (String token : tokenizedContent) {
+ XMLGregorianCalendar value = toCalendar(token, localName);
+ if (value != null) {
+ calendars.add(value);
+ } else {
+ return setValue(null);
+ }
+ }
+
+ setValue(calendars);
+ } else {
+ return setValue(null);
+ }
+
+ List result = new ArrayList<>(calendars.size());
+ for (XMLGregorianCalendar calendar : calendars) {
+ result.add(toOffsetDateTime(calendar));
+ }
+
+ return result;
+ }
+
+ private T setValue(T value) {
+ this.value = value;
+ return value;
+ }
+
+ private boolean isListOfType(Object value, Class> type) {
+ return value instanceof List> list && list.stream().allMatch(type::isInstance);
+ }
+
+ private Boolean toBoolean(String value) {
+ if (value.isEmpty()) {
+ return null;
+ } else if ("true".equals(value)
+ || "1".equals(value)) {
+ return Boolean.TRUE;
+ } else if ("false".equals(value)
+ || "0".equals(value)) {
+ return Boolean.FALSE;
+ } else {
+ return null;
+ }
+ }
+
+ private XMLGregorianCalendar toCalendar(String value, String localName) {
+ try {
+ XMLGregorianCalendar calendar = XML_TYPE_FACTORY.newXMLGregorianCalendar(value);
+ if (calendar.getXMLSchemaType().getLocalPart().equals(localName)) {
+ return calendar;
+ }
+ } catch (Throwable e) {
+ //
+ }
+
+ return null;
+ }
+
+ private OffsetDateTime toOffsetDateTime(XMLGregorianCalendar calendar) {
+ int day = calendar.getDay();
+ int month = calendar.getMonth();
+ int year = calendar.getYear();
+ int hour = calendar.getHour();
+ int minute = calendar.getMinute();
+ int second = calendar.getSecond();
+ int offset = calendar.getTimezone();
+
+ BigDecimal fractional = calendar.getFractionalSecond();
+ int nano = fractional != null ? (int) (fractional.doubleValue() * 1e+9) : DatatypeConstants.FIELD_UNDEFINED;
+
+ LocalDateTime dateTime = LocalDateTime.of(
+ year != DatatypeConstants.FIELD_UNDEFINED ? year : 0,
+ month != DatatypeConstants.FIELD_UNDEFINED ? month : 1,
+ day != DatatypeConstants.FIELD_UNDEFINED ? day : 1,
+ hour != DatatypeConstants.FIELD_UNDEFINED ? hour : 0,
+ minute != DatatypeConstants.FIELD_UNDEFINED ? minute : 0,
+ second != DatatypeConstants.FIELD_UNDEFINED ? second : 0,
+ nano != DatatypeConstants.FIELD_UNDEFINED ? nano : 0);
+
+ return OffsetDateTime.of(dateTime, offset != DatatypeConstants.FIELD_UNDEFINED ?
+ ZoneOffset.ofTotalSeconds(offset * 60) :
+ zoneOffsetProvider.apply(dateTime));
+ }
+
+ private static TextContent ofObject(Object content) {
+ return content != null ? new TextContent(content.toString()) : EMPTY;
+ }
+
+ private static TextContent ofObjectList(List> content) {
+ if (content != null && !content.isEmpty()) {
+ StringBuilder builder = new StringBuilder(content.size());
+ boolean first = true;
+
+ for (Object object : content) {
+ if (object != null) {
+ if (!first) {
+ builder.append(" ");
+ } else {
+ first = false;
+ }
+
+ builder.append(object);
+ }
+ }
+
+ return new TextContent(builder.toString());
+ } else {
+ return EMPTY;
+ }
+ }
+
+ private static TextContent ofOffsetDateTime(OffsetDateTime content, EnumSet fields, boolean withOffset) {
+ XMLGregorianCalendar calendar = toCalendar(content, fields, withOffset);
+ return calendar != null ? new TextContent(calendar.toXMLFormat()) : EMPTY;
+ }
+
+ private static TextContent ofOffsetDateTimeList(List content, EnumSet fields, boolean withOffset) {
+ if (content != null && !content.isEmpty()) {
+ StringBuilder builder = new StringBuilder(content.size());
+ boolean first = true;
+
+ for (OffsetDateTime dateTime : content) {
+ XMLGregorianCalendar calendar = toCalendar(dateTime, fields, withOffset);
+ if (calendar != null) {
+ if (!first) {
+ builder.append(" ");
+ } else {
+ first = false;
+ }
+
+ builder.append(calendar.toXMLFormat());
+ }
+ }
+
+ return new TextContent(builder.toString());
+ } else {
+ return EMPTY;
+ }
+ }
+
+ private static XMLGregorianCalendar toCalendar(OffsetDateTime dateTime, EnumSet fields, boolean withOffset) {
+ XMLGregorianCalendar calendar = null;
+ if (dateTime != null) {
+ calendar = XML_TYPE_FACTORY.newXMLGregorianCalendar(
+ fields.contains(Fields.YEAR) ? dateTime.getYear() : DatatypeConstants.FIELD_UNDEFINED,
+ fields.contains(Fields.MONTH) ? dateTime.getMonthValue() : DatatypeConstants.FIELD_UNDEFINED,
+ fields.contains(Fields.DAY) ? dateTime.getDayOfMonth() : DatatypeConstants.FIELD_UNDEFINED,
+ fields.contains(Fields.HOUR) ? dateTime.getHour() : DatatypeConstants.FIELD_UNDEFINED,
+ fields.contains(Fields.MINUTE) ? dateTime.getMinute() : DatatypeConstants.FIELD_UNDEFINED,
+ fields.contains(Fields.SECOND) ? dateTime.getSecond() : DatatypeConstants.FIELD_UNDEFINED,
+ DatatypeConstants.FIELD_UNDEFINED,
+ withOffset && fields.contains(Fields.TIMEZONE) ?
+ dateTime.getOffset().getTotalSeconds() / 60 :
+ DatatypeConstants.FIELD_UNDEFINED);
+
+ if (fields.contains(Fields.NANO) && dateTime.getNano() != 0) {
+ calendar.setFractionalSecond(BigDecimal.valueOf(dateTime.getNano(), 9).stripTrailingZeros());
+ }
+ }
+
+ return calendar;
+ }
+
+ public static void setZoneOffsetProvider(Function zoneOffsetProvider) {
+ if (zoneOffsetProvider != null) {
+ ZONE_OFFSET_PROVIDER = zoneOffsetProvider;
+ }
+ }
+
+ public static void serializeTimeWithOffset(boolean useTimeOffset) {
+ WITH_TIME_OFFSET = useTimeOffset;
+ }
+
+ public static void serializeDateWithOffset(boolean useDateOffset) {
+ WITH_DATE_OFFSET = useDateOffset;
+ }
+
+ private String trimmedContent() {
+ if (trimmedContent == null) {
+ trimmedContent = content.trim();
+ }
+
+ return trimmedContent;
+ }
+
+ private int tokenizeContent() {
+ if (tokenizedContent == null) {
+ int length = content.length();
+ String[] tokens = new String[(length / 2) + 1];
+ int noOfTokens = 0;
+ int current = -1;
+ int next;
+
+ do {
+ next = nextWhiteSpace(content, current + 1, length);
+ if (next != current + 1) {
+ tokens[noOfTokens++] = content.substring(current + 1, next);
+ }
+
+ current = next;
+ } while (next != length);
+
+ tokenizedContent = new String[noOfTokens];
+ System.arraycopy(tokens, 0, tokenizedContent, 0, noOfTokens);
+ }
+
+ return tokenizedContent.length;
+ }
+
+ private int nextWhiteSpace(String value, int pos, int length) {
+ for (int i = pos; i < length; i++) {
+ if (isWhiteSpace(value.charAt(i))) {
+ return i;
+ }
+ }
+
+ return length;
+ }
+
+ private boolean isWhiteSpace(char ch) {
+ return ch == ' ' || ch == '\n' || Character.isWhitespace(ch);
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+}
+
diff --git a/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization.properties b/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization.properties
index 59ecf4ef6cf2cbcb46cf92569781ccb940c2ff54..80316fb279b6ccf55c9f21c5934a525f97630618 100644
--- a/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization.properties
+++ b/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization.properties
@@ -6,15 +6,18 @@ CheckContainer.error=Unexpected exception while executing check: {}
CheckDialog.availableChecksLabel=Available Checks
CheckDialog.cancelBtn=Cancel
CheckDialog.checkBtn=Check
+CheckDialog.checkDescriptionsLabel=Explanations for the requirements are available on CityDoctor's homepage
CheckDialog.checkEnabled=Enabled
CheckDialog.checkName=Name
CheckDialog.checksDone=Checks done
CheckDialog.checksReenabledAlert=Some checks have been reenabled so that other wanted checks can be executed\nSee the log for more information.
CheckDialog.checksTab=Checks
+CheckDialog.clickHereToOpen=[Click here to open]
CheckDialog.failedChecks=Failed to execute checks
CheckDialog.filterTab=Filter
CheckDialog.geometricChecksLabel=Geometric Checks
CheckDialog.globalParametersLabel=Global Parameters
+CheckDialog.homepageHyperlink=https://transfer.hft-stuttgart.de/pages/citydoctor/citydoctorhomepage/en/requirements/
CheckDialog.noPathError=Output path is missing!
CheckDialog.outputPathBtn=Select
CheckDialog.outputPathLabel=Output directory (Required):
@@ -25,12 +28,14 @@ CheckDialog.schematronChooserTitle=Choose schematron file ...
CheckDialog.schematronFileLabel=Schematron File:
CheckDialog.schematronFileNotExisting=Schematron file does not exist, Schematron will be skipped
CheckDialog.schematronFiles=Schematron Files
+CheckDialog.selectAllChecksBtn=Select All
CheckDialog.selectBtn=Select
CheckDialog.semanticChecksLabel=Semantic Checks
CheckDialog.startingChecks=Starting checks
CheckDialog.streamCheckLabel=Citymodels are not loaded and kept in memory. Check results are saved to the QualityADE in copies of the input files.
CheckDialog.streamCheckOption=Use low memory mode
CheckDialog.title=Check Configuration
+CheckDialog.unselectAllChecksBtn=Unselect All
CheckDialog.validationConfiguration=Validation Configuration
CheckDialog.xmlReportsOption=Create XML reports
Checker.checkFeature=Checking feature: {}
@@ -70,6 +75,9 @@ CityDoctorValidation.notExactlyOneOutputFile=Specify exactly one file as output.
CityDoctorValidation.notExactlyOnePDFOutput=Specify exactly one file as pdfReport
CityDoctorValidation.notExactlyOneXMLOutput=Specify exactly one file as xmlReport
CityDoctorValidation.wrongInputFileFormat=Input file '{}' does not end with .gml or .xml
+CityGmlFeatureMapper.crossFeatureReferencesDetected=Model has Features containing cross-feature references without their own copies of the referenced polygons
+CityGmlFeatureMapper.emptyCrossFeatureReference=Polygon {} of Feature {} is being referenced in other Features: {}
+CityGmlFeatureMapper.unresolvedReferencesDetected=Model contains polygon references that could not be found
CityGmlParser.chunkReadFailed=Failed to read GML file in chunks, falling back to reading the complete file
CityGmlParser.convertCrsToUtmZoneN=Converting coordinate system to UTM zone {}N
CityGmlParser.convertCrsToUtmZoneS=Converting coordinate system to UTM zone {}S
@@ -99,6 +107,9 @@ DatabaseHandler.serializationFailure=Serialization of CityObject {} failed!
DatabaseHandler.setupFailure=FATAL ERROR: Creation of embedded database failed!
DatabaseHandler.tableFailure=FATAL ERROR: Creation of features table failed!
DatabaseHandler.unmarshallingFailure=Loading of CityObject {} from embedded database failed!
+DatabaseSettings.invalidPath=Properties for database directory and name do not combine to a valid path. Using default value instead
+DatabaseSettings.poolSizeLessThanOne=ConnectionPoolSize property was set to be less than 1. Using default value instead
+DatabaseSettings.poolSizeNaN=ConnectionPoolSize property is not an integer. Using default value instead
DistanceError.distanceFromPlane=distance from plane
ErrorItemVisitor.childId=Child id
ErrorItemVisitor.component=Component
diff --git a/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization_de.properties b/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization_de.properties
index e9c1f7b68d35cda5c4a329698fe8801e4d8f31ba..657b3ad2dd4c25a4593d75a79c61cbfb549397de 100644
--- a/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization_de.properties
+++ b/CityDoctorParent/CityDoctorModel/src/main/resources/CityDoctorLocalization_de.properties
@@ -6,15 +6,18 @@ CheckContainer.error=Unerwarteter Fehler bei der Ausf\u00fchrung von Pr\u00fcfun
CheckDialog.availableChecksLabel=Verf\u00fcgbare Pr\u00fcfungen
CheckDialog.cancelBtn=Abbrechen
CheckDialog.checkBtn=Pr\u00fcfen
+CheckDialog.checkDescriptionsLabel=Beschreibungen der Pr\u00fcfungen sind auf der CityDoctor Homepage verf\u00fcgbar
CheckDialog.checkEnabled=Aktiv
CheckDialog.checkName=Name
CheckDialog.checksDone=Pr\u00fcfung abgeschlossen
CheckDialog.checksReenabledAlert=Manche Pr\u00fcfungen wurden reaktiviert damit andere gewollte Pr\u00fcfungen durchgef\u00fchrt werden k\u00f6nnen\nMehr Details sind im Log geschrieben
CheckDialog.checksTab=Pr\u00fcfungen
+CheckDialog.clickHereToOpen=[Hier Klicken zum \u00d6ffnen]
CheckDialog.failedChecks=Konnte Pr\u00fcfungen nicht ausf\u00fchren
CheckDialog.filterTab=Filter
CheckDialog.geometricChecksLabel=Geometrische Pr\u00fcfungen
CheckDialog.globalParametersLabel=Globale Parameter
+CheckDialog.homepageHyperlink=https://transfer.hft-stuttgart.de/pages/citydoctor/citydoctorhomepage/de/requirements/
CheckDialog.noPathError=Ausgabepfad wurde nicht gesetzt!
CheckDialog.outputPathBtn=Ausw\u00e4hlen
CheckDialog.outputPathLabel=Ausgabeordner (Pflichtfeld):
@@ -25,12 +28,14 @@ CheckDialog.schematronChooserTitle=W\u00e4hle Schematron Datei ...
CheckDialog.schematronFileLabel=Schematron Datei:
CheckDialog.schematronFileNotExisting=Schematron Datei existiert nicht, Schematron-Pr\u00fcfung wird \u00fcbersprungen
CheckDialog.schematronFiles=Schematron Dateien
+CheckDialog.selectAllChecksBtn=Alle aktivieren
CheckDialog.selectBtn=Ausw\u00e4hlen
CheckDialog.semanticChecksLabel=Semantische Pr\u00fcfungen
CheckDialog.startingChecks=Starte Pr\u00fcfung
CheckDialog.streamCheckLabel=Stadtmodelle werden nicht in den Arbeitsspeicher geladen. Ergebnisse werden in der QualityADE in Kopien der Eingangsdateien gespeichert.
CheckDialog.streamCheckOption=Niedrigen Arbeitsspeicherverbrauchsmodus aktivieren
CheckDialog.title=Pr\u00fcfungskonfiguration
+CheckDialog.unselectAllChecksBtn=Alle deaktivieren
CheckDialog.validationConfiguration=Validierungskonfiguration
CheckDialog.xmlReportsOption=XML-Berichte erstellen
Checker.checkFeature=Pr\u00fcfe Feature: {}
@@ -70,6 +75,9 @@ CityDoctorValidation.notExactlyOneOutputFile=Es muss exakt ein Ausgabedateipfad
CityDoctorValidation.notExactlyOnePDFOutput=Es muss exakt ein Ausgabedateipfad f\u00fcr den PDF-Bericht angegeben werden.
CityDoctorValidation.notExactlyOneXMLOutput=Es muss exakt ein Ausgabedateipfad f\u00fcr den XML-Bericht angegeben werden.
CityDoctorValidation.wrongInputFileFormat=Eingabedatei '{}' ist keine .gml oder .xml Datei.
+CityGmlFeatureMapper.crossFeatureReferencesDetected=Modell enth\u00e4lt Features mit Feature\u00fcbergreifenden Polygonreferenzen ohne eigene Duplikate
+CityGmlFeatureMapper.emptyCrossFeatureReference=Polygon {} in Feature {} wird Feature\u00fcbergreifend referenziert durch Features: {}
+CityGmlFeatureMapper.unresolvedReferencesDetected=Modell enth\u00e4lt Features mit Polygonreferenzen welche nicht aufgel\u00f6st werden konnten
CityGmlParser.chunkReadFailed=Konnte Datei nicht in St\u00fccken lesen, versuche komplett zu lesen
CityGmlParser.convertCrsToUtmZoneN=Konvertiere Koordinatensystem zu UTM Zone {}N
CityGmlParser.convertCrsToUtmZoneS=Konvertiere Koordinatensystem zu UTM Zone {}S
@@ -99,6 +107,9 @@ DatabaseHandler.serializationFailure=Serialisierung des CityObject {} fehlgeschl
DatabaseHandler.setupFailure=KRITISCHER FEHLER: Erstellen der eingebetteten Datenbank fehlgeschlagen!
DatabaseHandler.tableFailure=KRITISCHER FEHLER: Erstellen der "features" Tabelle fehlgeschlagen!
DatabaseHandler.unmarshallingFailure=Laden des CityObject {} aus der Datenbank fehlgeschlagen!
+DatabaseSettings.invalidPath=Einstellungen f\u00fcr Directory und Name der Datenbank bilden keinen validen Pfad. Standardwerte werden stattdessen genutzt
+DatabaseSettings.poolSizeLessThanOne=ConnectionPoolSize Einstellung wurde auf unter 1 gesetzt. Standardwert wird stattdessen genutzt
+DatabaseSettings.poolSizeNaN=ConnectionPoolSize Einstellung ist kein Integer. Standardwert wird stattdessen genutzt
DistanceError.distanceFromPlane=Abstand von der Ebene
ErrorItemVisitor.childId=Kind ID
ErrorItemVisitor.component=Komponente
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/DatabaseSettingsTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/DatabaseSettingsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..00ca8ccb103ccc8a82428b9e0e3f3e8738ab8760
--- /dev/null
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/DatabaseSettingsTest.java
@@ -0,0 +1,11 @@
+package de.hft.stuttgart.citydoctor2.database;
+
+import org.junit.jupiter.api.Test;
+
+public class DatabaseSettingsTest {
+
+ public void testSetLocation(){
+ String tempdirectory = System.getProperty("java.io.tmpdir");
+ DatabaseSettings.setDBLocation(tempdirectory + "/citydoctor");
+ }
+}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java
index 06c0bb6e713a27cd1593503520cc2005905d776b..b795e50e584d039c182bc427493e4406fcf20b38 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java
@@ -16,7 +16,6 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import org.citygml4j.core.model.building.AbstractBuilding;
-import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@@ -25,25 +24,44 @@ import java.util.List;
import static de.hft.stuttgart.citydoctor2.database.SerializationTest.assertCityObjectEquality;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
public class EmbeddedDataBaseHandlerTest {
- private EmbeddedDatabaseHandler handler;
+ private final EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration());
+
+ @Test
+ public void testTempFileConfiguration(){
+ EmbeddedDatabaseConfiguration tempFileconfig = new EmbeddedDatabaseConfiguration("database",
+ "inTempConfig",1, false,false,
+ false,true);
+ EmbeddedDatabaseHandler tempHandler = new EmbeddedDatabaseHandler(tempFileconfig);
+ assertTrue(tempHandler.getTempFileDBLocation().exists());
- @Before
- public void resetDatabase() {
- try {
- handler = new EmbeddedDatabaseHandler();
- handler.setupFeaturesTable();
- } catch (Exception e) {
- fail(e.getMessage());
- }
}
+ @Test
+ public void testInMemoryConfiguration() throws CityGmlParseException, InvalidGmlFileException {
+ EmbeddedDatabaseConfiguration inMemoryConfig = new EmbeddedDatabaseConfiguration("database",
+ "irrelevant",1, true,false,
+ false,false);
+ EmbeddedDatabaseHandler memHandler = new EmbeddedDatabaseHandler(inMemoryConfig);
+ assertNull(memHandler.getTempFileDBLocation());
+ ParserConfiguration config = new ParserConfiguration(8, false);
+ CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse("src/test/resources/SimpleSolid_SrefBS.gml", config);
+ Building b = model.getBuildings().toList().get(0);
+ assertEquals(0, memHandler.getFeatureCount());
+ memHandler.marshallCityObject(b);
+ assertEquals(1, memHandler.getFeatureCount());
+ CityObject co = memHandler.unmarshallCityObject(b.getGmlId());
+ assertNotNull(co);
+ assertTrue(co instanceof Building);
+ }
@Test
public void testGetBboxIntersectingObjects() {
+ handler.setupFeaturesTable();
Building b = new Building();
Building b2 = new Building();
b2.setGmlObject(Mockito.mock(AbstractBuilding.class));
@@ -124,6 +142,7 @@ public class EmbeddedDataBaseHandlerTest {
@Test
public void testGetBboxBufferIntersectingObjects() {
+ handler.setupFeaturesTable();
Building b = new Building();
Building b2 = new Building();
b2.setGmlObject(Mockito.mock(AbstractBuilding.class));
@@ -210,6 +229,7 @@ public class EmbeddedDataBaseHandlerTest {
Building b = model.getBuildings().toList().get(0);
assertNotNull(b);
+ assertEquals(1, handler.getFeatureCount());
handler.marshallCityObject(b);
assertEquals(1, handler.getFeatureCount());
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BoundarySurfaceTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BoundarySurfaceTest.java
index c08b1ad7cede13dbb1522e5bcab5081630b9564d..d13efe11eca33a2ef2a9ef10c9412a39fa24ee59 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BoundarySurfaceTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BoundarySurfaceTest.java
@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-import de.hft.stuttgart.citydoctor2.utils.visitors.ClearVisitorResultsVisitor;
+import de.hft.stuttgart.citydoctor2.utils.visitors.ClearResultsVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.GmlGeometryRebuilder;
import de.hft.stuttgart.citydoctor2.utils.visitors.UnsetGeometriesVisitor;
import org.citygml4j.core.model.construction.AbstractConstructionSurface;
@@ -141,7 +141,7 @@ public class BoundarySurfaceTest {
assertFalse(bs.getAllCheckResults().isEmpty());
bs.addOpening(oMock);
bs.clearAllContainedCheckResults();
- verify(oMock).accept(any(ClearVisitorResultsVisitor.class));
+ verify(oMock).accept(any(ClearResultsVisitor.class));
assertTrue(bs.getAllCheckResults().isEmpty());
}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BridgeConstructiveElementTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BridgeConstructiveElementTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf7440e983add3b070003d13fc95dfd15b0d3cb3
--- /dev/null
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BridgeConstructiveElementTest.java
@@ -0,0 +1,30 @@
+package de.hft.stuttgart.citydoctor2.datastructure;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.citygml4j.core.model.construction.WallSurface;
+import org.citygml4j.core.util.geometry.GeometryFactory;
+import org.junit.jupiter.api.Test;
+
+import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
+
+class BridgeConstructiveElementTest {
+
+ @Test
+ void testBoundarySurfaceGeometryRecreation() {
+ WallSurface wallSurface = new WallSurface();
+ BoundarySurface surface = new BoundarySurface(wallSurface);
+ surface.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD2));
+
+ var gmlBce = new org.citygml4j.core.model.bridge.BridgeConstructiveElement();
+ BridgeConstructiveElement bce = new BridgeConstructiveElement(gmlBce);
+ bce.addBoundarySurface(surface);
+
+ assertNull(wallSurface.getLod2MultiSurface());
+ GeometryFactory factory = GeometryFactory.newInstance();
+ ParserConfiguration config = new ParserConfiguration(8, false);
+ bce.reCreateGeometries(factory, config);
+ assertNotNull(wallSurface.getLod2MultiSurface());
+ }
+
+}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BuildingTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BuildingTest.java
index 21d897faca604266bdf25a662d05ba0abc78a0ee..67eb587dbc29ce3116c6daf82e29ba5df238289f 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BuildingTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/BuildingTest.java
@@ -52,12 +52,12 @@ public class BuildingTest {
Vertex v2 = new Vertex(2, 0, 0);
LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
ConcretePolygon p = new ConcretePolygon();
+ Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD0, Orientation.OUTWARD);
+ geom.addPolygon(p);
p.setExteriorRing(lr);
lr.addVertex(v1);
lr.addVertex(v2);
lr.addVertex(v1);
- Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD0, Orientation.OUTWARD);
- geom.addPolygon(p);
geom.updateEdgesAndVertices();
b.addGeometry(geom);
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/GeometryTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/GeometryTest.java
index 5c8b97cff5c28a6c4b8b9e1308253988999b70f3..56e32c7ed68896a0569e9a5c56e35cf3f0885d9e 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/GeometryTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/GeometryTest.java
@@ -33,7 +33,7 @@ import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
-import de.hft.stuttgart.citydoctor2.utils.visitors.ClearVisitorResultsVisitor;
+import de.hft.stuttgart.citydoctor2.utils.visitors.ClearResultsVisitor;
import org.citygml4j.core.model.construction.AbstractConstructionSurface;
import org.citygml4j.core.model.construction.WallSurface;
import org.citygml4j.core.util.geometry.GeometryFactory;
@@ -105,9 +105,9 @@ public class GeometryTest {
ConcretePolygon p = new ConcretePolygon();
LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
+ geom.addPolygon(p);
lr.addVertex(new Vertex(0, 0, 0));
- geom.addPolygon(p);
p.setPartOfSurface(bs);
p.setPartOfInstallation(bi);
@@ -139,9 +139,9 @@ public class GeometryTest {
ConcretePolygon p = new ConcretePolygon();
LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
+ geom.addPolygon(p);
lr.addVertex(new Vertex(0, 0, 0));
- geom.addPolygon(p);
p.setPartOfSurface(bs);
bs.addGeometry(geom);
@@ -165,10 +165,10 @@ public class GeometryTest {
ConcretePolygon p = new ConcretePolygon();
LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
+ geom.addPolygon(p);
lr.addVertex(new Vertex(0, 0, 0));
bs.addGeometry(geom);
- geom.addPolygon(p);
p.setPartOfSurface(bs);
p.setPartOfInstallation(bi);
@@ -301,7 +301,7 @@ public class GeometryTest {
Polygon p = mock(Polygon.class);
geom.addPolygon(p);
geom.clearAllContainedCheckResults();
- verify(p).accept(any(ClearVisitorResultsVisitor.class));
+ verify(p).accept(any(ClearResultsVisitor.class));
assertFalse(geom.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/ImplicitGeometryTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/ImplicitGeometryTest.java
index 42d0f073594d0a797d0ba189759d6462cfc80113..0517b74c548ac28b55f52a757748e2a54fda808d 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/ImplicitGeometryTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/ImplicitGeometryTest.java
@@ -90,8 +90,8 @@ public class ImplicitGeometryTest {
assertFalse(lib.containsError(CheckId.C_GE_P_NON_PLANAR));
igh.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, mock(CheckError.class)));
- assertFalse("ImplicitGeometryHolder's errors should be ignored and delegate the call to the prototype geometry",
- lib.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
+ assertFalse(lib.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
+ assertTrue(igh.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
lib.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, mock(CheckError.class)));
assertTrue(lib.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
@@ -105,27 +105,39 @@ public class ImplicitGeometryTest {
@Test
public void testClearAllContainedCheckResults() {
+ igh.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
lib.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
assertEquals(1, lib.getAllCheckResults().size());
+ assertEquals(1, igh.getAllCheckResults().size());
+
lib.clearAllContainedCheckResults();
assertTrue(lib.getAllCheckResults().isEmpty());
+ assertEquals(1, igh.getAllCheckResults().size());
+
+ igh.clearAllContainedCheckResults();
+ assertTrue(igh.getAllCheckResults().isEmpty());
}
@Test
public void testContainsAnyError() {
assertFalse(lib.containsAnyError());
+ assertFalse(igh.containsAnyError());
igh.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
assertFalse(lib.containsAnyError());
+ assertFalse(igh.containsAnyError());
lib.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
assertFalse(lib.containsAnyError());
+ assertFalse(igh.containsAnyError());
igh.addCheckResult(
new CheckResult(CheckId.C_GE_P_INNER_RINGS_NESTED, ResultStatus.ERROR, mock(CheckError.class)));
assertFalse(lib.containsAnyError());
+ assertTrue(igh.containsAnyError());
lib.addCheckResult(
new CheckResult(CheckId.C_GE_P_INNER_RINGS_NESTED, ResultStatus.ERROR, mock(CheckError.class)));
assertTrue(lib.containsAnyError());
+ assertTrue(igh.containsAnyError());
}
@Test
@@ -134,19 +146,18 @@ public class ImplicitGeometryTest {
igh.addCheckResult(new CheckResult(CheckId.C_GE_P_NON_PLANAR, ResultStatus.ERROR, mock(CheckError.class)));
igh.collectContainedErrors(errors);
- assertEquals("ImplicitGeometryHolder's errors should be ignored and delegate the call to the prototype geometry",
- 0, errors.size());
+ assertEquals(1, errors.size());
+ errors.clear();
lib.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, mock(CheckError.class)));
igh.collectContainedErrors(errors);
- assertEquals("ImplicitGeometryHolder's errors should be ignored and delegate the call to the prototype geometry",
- 1, errors.size());
+ assertEquals(1, errors.size());
}
@Test
public void testAccept() {
- int polyCount = lib.getPolygons().size();
+ int polyCount = igh.getPolygons().size();
AtomicInteger loCounter = new AtomicInteger(0);
@@ -157,7 +168,7 @@ public class ImplicitGeometryTest {
}
};
igh.accept(c);
- assertEquals("ImplicitGeometryHolder should only check polygons of prototype geometry",
+ assertEquals("Visitors should only walk over ImplicitGeometryHolder",
polyCount, loCounter.intValue());
}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/LinearRingTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/LinearRingTest.java
index d2c95d597aff53eecab7271b0eb1cb505c23c2fc..05c1879877f75ef08debdb6f84ab33578788b1b5 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/LinearRingTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/LinearRingTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.*;
import org.junit.Test;
+import de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.math.UnitVector3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
@@ -35,7 +36,9 @@ public class LinearRingTest {
@Test
public void testIsPointInside1() {
+ Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
Polygon p = new ConcretePolygon();
+ geom.addPolygon(p);
LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
p.setExteriorRing(lr);
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/TrafficSpaceTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/TrafficSpaceTest.java
index 42759031ebce3b99c84e96fd47add0c8db4e6e7b..c5f848def4698ed488eaee30b0cc2a644a28b373 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/TrafficSpaceTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/TrafficSpaceTest.java
@@ -6,7 +6,7 @@ import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
-import de.hft.stuttgart.citydoctor2.utils.visitors.ClearVisitorResultsVisitor;
+import de.hft.stuttgart.citydoctor2.utils.visitors.ClearResultsVisitor;
import org.citygml4j.core.model.core.AbstractSpaceBoundary;
import org.citygml4j.core.model.core.AbstractUnoccupiedSpace;
import org.citygml4j.core.model.deprecated.core.DeprecatedPropertiesOfAbstractCityObject;
@@ -91,7 +91,7 @@ public class TrafficSpaceTest {
tso.clearAllContainedCheckResults();
assertFalse(tso.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
- verify(taMock).accept(any(ClearVisitorResultsVisitor.class));
+ verify(taMock).accept(any(ClearResultsVisitor.class));
}
@Test
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/VertexTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/VertexTest.java
index 6dc58ddafe95ef33ee08df12b7e3bc65e14a12a3..989e02061b04685e5439fb36c6cef900282309eb 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/VertexTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/datastructure/VertexTest.java
@@ -62,20 +62,4 @@ public class VertexTest {
assertEquals(1, adjacentRings.size());
assertTrue(adjacentRings.contains(lr));
}
-
- @Test(expected = IllegalStateException.class)
- public void testGetAdjacentRingsGeometryNotContainingVertex() {
- Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD1, Orientation.OUTWARD);
-
- Polygon p = new ConcretePolygon();
-
- LinearRing lr = new LinearRing(LinearRingType.EXTERIOR);
- p.setExteriorRing(lr);
-
- Vertex v0 = new Vertex(0, 0, 0);
- lr.addVertex(v0);
-
- v0.getAdjacentRings(geom);
- }
-
}
diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/parser/CityGml3ParserTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/parser/CityGml3ParserTest.java
index 88b958f4f01ef0083bb0bb8ed262e8ca71b3e0bb..ad0e6057a1b99be4481c008b05af512b3f750014 100644
--- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/parser/CityGml3ParserTest.java
+++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/parser/CityGml3ParserTest.java
@@ -37,7 +37,7 @@ public class CityGml3ParserTest {
@Test
public void testGetContext() {
- CityGMLContext context = CityGmlParser.getContext();
+ CityGMLContext context = CityGmlParser.setupContext();
List ades = ADERegistry.getInstance().getADEs();
ADE ade = ades.get(0);
assertEquals(QualityADEContext.class, ade.getClass());
diff --git a/CityDoctorParent/CityDoctorValidation/pom.xml b/CityDoctorParent/CityDoctorValidation/pom.xml
index c9514e87ac6f0487a914b8ed92504ebf64c007b5..f52a83cb61cd0d775bc71648142946b5c3b462db 100644
--- a/CityDoctorParent/CityDoctorValidation/pom.xml
+++ b/CityDoctorParent/CityDoctorValidation/pom.xml
@@ -4,7 +4,7 @@
de.hft.stuttgart
CityDoctorParent
- 3.18.1
+ 3.18.2
CityDoctorValidation
CityDoctorValidation
diff --git a/CityDoctorParent/CityDoctorValidation/src/assembly/lin/start.sh b/CityDoctorParent/CityDoctorValidation/src/assembly/lin/start.sh
index 85e23ecb732a128e15c02f1195a0b15e881be1d5..6ff38584ebaf9132d67934dfc867aabb8e517d6a 100644
--- a/CityDoctorParent/CityDoctorValidation/src/assembly/lin/start.sh
+++ b/CityDoctorParent/CityDoctorValidation/src/assembly/lin/start.sh
@@ -1,2 +1,2 @@
#!/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
diff --git a/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.bat b/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.bat
index bf042835beb80493f35164bebb2f4daf1ff004bf..e1f7e4efd3b0d562d33eb1926f174a6a1e60b2f6 100644
--- a/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.bat
+++ b/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.bat
@@ -1,2 +1,2 @@
-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
diff --git a/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.sh b/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.sh
index 8eeb31f3aa46dfc023d4a5de2c7801aaef532d75..8693a99748e89f52935b1879bdeeffe989c1778a 100644
--- a/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.sh
+++ b/CityDoctorParent/CityDoctorValidation/src/assembly/no_runtime/start.sh
@@ -1,2 +1,3 @@
#!/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
diff --git a/CityDoctorParent/CityDoctorValidation/src/assembly/win/start.bat b/CityDoctorParent/CityDoctorValidation/src/assembly/win/start.bat
index 52d8e1092ea00e3b5e5f33a6c9af45e8747b5f3d..88984883487db3abdbd1e942039bbf8a8a7248f5 100644
--- a/CityDoctorParent/CityDoctorValidation/src/assembly/win/start.bat
+++ b/CityDoctorParent/CityDoctorValidation/src/assembly/win/start.bat
@@ -1,2 +1,2 @@
-"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
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidation.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidation.java
index ec2d0df09e0c2e858d747670c34b9df74f5b79d7..c96294b60becb86c1e48346faa559798ba633601 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidation.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidation.java
@@ -23,6 +23,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
+import de.hft.stuttgart.citydoctor2.database.DatabaseSettings;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -70,6 +71,8 @@ public class CityDoctorValidation {
String xmlOutput = getXmlOutput(argParser);
String pdfOutput = getPdfOutput(argParser);
String outputFile = getOutputFile(argParser, true);
+ lookForDbConfigFileParameter(argParser);
+ lookForDbLocationParameter(argParser);
ValidationConfiguration config = getValidationConfig(argParser);
startValidationProcess(inputFile, xmlOutput, pdfOutput, config, outputFile);
@@ -81,18 +84,42 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
- logger.error(Localization.getText("CityDoctorValidation.noOutputFile"));
+ logger.fatal(Localization.getText("CityDoctorValidation.noOutputFile"));
System.exit(11);
}
List outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile"));
+ logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneOutputFile"));
System.exit(12);
}
outputFile = outFiles.get(0);
return outputFile;
}
+ private static void lookForDbConfigFileParameter(ArgumentParser argParser) {
+ if (!argParser.containsOption("db_config")) {
+ return;
+ }
+ List 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.");
+ }
+ }
+
+ private static void lookForDbLocationParameter(ArgumentParser argParser) {
+ if (!argParser.containsOption("db_location")) {
+ return;
+ }
+ List 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) {
@@ -202,7 +229,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("pdfreport")) {
List reportFiles = argParser.getValues("pdfreport");
if (reportFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
+ logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOnePDFOutput"));
System.exit(6);
}
return reportFiles.get(0);
@@ -214,7 +241,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("xmlreport")) {
List reportFiles = argParser.getValues("xmlreport");
if (reportFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
+ logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneXMLOutput"));
System.exit(5);
}
return reportFiles.get(0);
@@ -231,16 +258,16 @@ public class CityDoctorValidation {
if (argParser.containsOption("config")) {
List configFiles = argParser.getValues("config");
if (configFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
+ logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneConfigFile"));
System.exit(4);
}
- return ValidationConfiguration.loadValidationConfig(configFiles.get(0));
+ return ValidationConfiguration.loadValidationConfigFile(configFiles.get(0));
} else {
if (optional) {
logger.warn(Localization.getText("CityDoctorValidation.loadingDefaultConfig"));
return ValidationConfiguration.loadStandardValidationConfig();
} else {
- logger.error(Localization.getText("CityDoctorValidation.noConfig"));
+ logger.fatal(Localization.getText("CityDoctorValidation.noConfig"));
System.exit(7);
return null;
}
@@ -252,7 +279,7 @@ public class CityDoctorValidation {
if (argParser.containsOption("out")) {
List outFiles = argParser.getValues("out");
if (outFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
+ logger.fatal(Localization.getText("CityDoctorValidation.multipleOutputFiles"));
System.exit(3);
}
return outFiles.get(0);
@@ -271,12 +298,12 @@ public class CityDoctorValidation {
if (optional) {
return null;
}
- logger.error(Localization.getText("CityDoctorValidation.noInputFile"));
+ logger.fatal(Localization.getText("CityDoctorValidation.noInputFile"));
System.exit(1);
}
List inFiles = argParser.getValues("in");
if (inFiles.size() != 1) {
- logger.error(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
+ logger.fatal(Localization.getText("CityDoctorValidation.notExactlyOneInputFile"));
System.exit(2);
}
inputFile = inFiles.get(0);
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidationCLI.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidationCLI.java
index 9003eb7b97bcfeb67cafb5f0ae002312cfa46af8..f909db92ebb92923baaabf3495127df9f6788b62 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidationCLI.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/CityDoctorValidationCLI.java
@@ -2,6 +2,7 @@ package de.hft.stuttgart.citydoctor2;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
+import de.hft.stuttgart.citydoctor2.database.DatabaseSettings;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
@@ -26,20 +27,30 @@ public class CityDoctorValidationCLI implements Runnable {
private File config;
@Option(names = {"-x" , "--xml" , "--xmlReport"},
- description = "Create a XML validation report at target location",
+ description = "Creates a XML validation report at target location",
defaultValue = Option.NULL_VALUE)
private String xmlReport;
@Option(names = {"-p" , "--pdf" , "--pdfReport"},
- description = "Create a PDF validation report at target location",
+ description = "Creates a PDF validation report at target location",
defaultValue = Option.NULL_VALUE)
private String pdfReport;
@Option(names = {"-o" , "--out" , "--output"},
- description = "Create a validated copy of the CityGML input with QualityADE at target location.",
+ description = "Creates a validated copy of the CityGML input with QualityADE at target location.",
defaultValue = Option.NULL_VALUE)
private String output;
+ @Option(names = {"--db_location", "--db_loc"},
+ description = "Creates the embedded database file at target location instead of CityDoctor's working directory.",
+ defaultValue = Option.NULL_VALUE)
+ private String dbLocation;
+
+ @Option(names = {"-db", "--db_config", "--db_settings"},
+ description = "Filepath to a .properties file for overriding the default configuration of the embedded database",
+ defaultValue = Option.NULL_VALUE)
+ private String dbConfig;
+
@Override
public void run(){
try {
@@ -47,7 +58,13 @@ public class CityDoctorValidationCLI implements Runnable {
if(config == null){
valConfig=ValidationConfiguration.loadStandardValidationConfig();
} else{
- valConfig=ValidationConfiguration.loadValidationConfig(config.getAbsolutePath());
+ valConfig=ValidationConfiguration.loadValidationConfigFile(config.getAbsolutePath());
+ }
+ if(dbConfig != null){
+ DatabaseSettings.loadPropertiesFromFile(new File(dbConfig));
+ }
+ if (dbLocation != null) {
+ DatabaseSettings.setDBLocation(dbLocation);
}
CityDoctorValidation.startValidationProcess(input, xmlReport, pdfReport, valConfig,output);
} catch (CityDoctorWriteException | CityGmlParseException | IOException | InvalidGmlFileException e) {
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java
index c863720e40b70668737309eaa2148ce5cc42e9f4..caea4d511e2674d9554f43a4e11203851e8d4325 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java
@@ -18,10 +18,55 @@
*/
package de.hft.stuttgart.citydoctor2.check;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+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;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+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;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
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;
@@ -29,13 +74,10 @@ 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.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.datastructure.ImplicitGeometryHolder;
-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;
@@ -59,40 +101,6 @@ import de.hft.stuttgart.quality.model.properties.RequirementProperty;
import de.hft.stuttgart.quality.model.types.Checking;
import de.hft.stuttgart.quality.model.types.Parameter;
import de.hft.stuttgart.quality.model.types.ValidationPlan;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMResult;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.sax.SAXResult;
-import javax.xml.transform.stream.StreamSource;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-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;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* The main container class for checking. It contains the logic for validation,
@@ -142,8 +150,8 @@ public class Checker {
return;
}
File xmlFile = new File(xmlOutput);
- if (xmlFile.getParentFile() != null) {
- xmlFile.getParentFile().mkdirs();
+ if (xmlFile.getParentFile() != null && xmlFile.getParentFile().mkdirs()) {
+ logger.trace("Xml dir created");
}
Reporter reporter = new XmlValidationReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) {
@@ -158,9 +166,10 @@ public class Checker {
return;
}
File pdfFile = new File(pdfOutput);
- if (pdfFile.getParentFile() != null) {
- pdfFile.getParentFile().mkdirs();
+ if (pdfFile.getParentFile() != null && pdfFile.getParentFile().mkdirs()){
+ logger.trace("Pdf dir created");
}
+
Reporter reporter = new PdfReporter();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) {
reporter.writeReport(checkConfig, bos, model, config);
@@ -235,7 +244,6 @@ public class Checker {
private void handleSchematronResults(SvrlContentHandler handler) {
CityObjectCache cache = model.getCache();
handleSchematronErrorsGlobal(handler.getGeneralErrors());
- Map featureMap = new HashMap<>();
boolean onlySchematron = execLayers.isEmpty();
if (onlySchematron) {
CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() {
@@ -244,16 +252,11 @@ public class Checker {
checkable.setValidated(true);
}
};
- model.createFeatureStream().forEach(f -> {
- featureMap.put(f.getGmlId().getGmlString(), f);
- f.accept(visitor);
- });
- } else {
- model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f));
+ model.createFeatureStream().forEach(co -> co.accept(visitor));
}
handler.getFeatureErrors().forEach((k, v) -> {
String trimmedId = k.trim();
- CityObject co = cache.get(new GmlId(k));
+ CityObject co = cache.get(new GmlId(trimmedId));
if (co == null) {
// gml id reported by schematron was not found, add to general errors
handleSchematronErrorsGlobal(v);
@@ -633,7 +636,7 @@ public class Checker {
ArrayList checkList = new ArrayList<>();
for (CheckId id : enabledCheck) {
Check c = checkConfig.getCheckForId(id);
- c.init(parameterMap.get(id), parserConfig);
+ c.init(parameterMap, parserConfig);
checkList.add(c);
}
return checkList;
@@ -649,7 +652,6 @@ public class Checker {
parameterMap.compute(proto.getCheckId(), (k, v) -> {
if (v == null) {
v = new HashMap<>();
- v.put(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, config.getNumberOfRoundingPlacesAsString());
v.put(GlobalParameters.MIN_VERTEX_DISTANCE, config.getMinVertexDistanceAsString());
}
v.putAll(e.getValue().getParameters());
@@ -692,7 +694,8 @@ public class Checker {
AtomicInteger checkedCount = new AtomicInteger(0);
// clear global errors
- model.getGlobalErrors().clear();
+ model.getGlobalErrors().removeIf(e -> !e.isParsingError());
+
CityObjectCache cache = model.getCache();
logger.trace("Setting up ThreadPool");
int threadCount = Runtime.getRuntime().availableProcessors() * 5;
@@ -700,12 +703,11 @@ public class Checker {
(threadCount, threadCount, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
try{
long startTime = System.nanoTime();
- ImplicitGeometryHolder.preparePrototypeGeometriesForChecking();
List> futures = runChecksOnFeatures(exec, cache, features, checkedCount, l);
List missedFeatures = getMissedFeatures(futures);
- ImplicitGeometryHolder.clearMetaDataOfPrototypeGeometries();
+
if (!missedFeatures.isEmpty()){
logger.error(Localization.getText("Checker.dbUnresponsive"));
if (logger.isDebugEnabled()){
@@ -739,8 +741,22 @@ public class Checker {
}
}
+ /**
+ * Runs the validation on a list of Features. The checks are run asynchronously and multithreaded. Returns a list
+ * of {@link Future Futures} for evaluation if Features failed to be checked due to exceptions or failing to load from
+ * the cache. If failed, the respective Future will return the affected Feature's GmlID.
+ * Checks finding an error in a Feature, or not having their dependencies met, are not a check-failure in this
+ * context.
+ * @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> runChecksOnFeatures(ExecutorService exec, CityObjectCache cache, List ids, AtomicInteger checkedCount,
- ProgressListener l)
+ @Nullable ProgressListener l)
throws InterruptedException {
float featureSum = ids.size() + (float) checkedCount.get();
@@ -754,11 +770,9 @@ public class Checker {
}
if(Thread.interrupted()){
Thread.currentThread().interrupt();
- return null;
+ return id;
}
- co.prepareForChecking();
executeChecksForCityObject(co);
- co.clearMetaInformation();
cache.put(co);
checkedCount.incrementAndGet();
if (l!=null){
@@ -773,7 +787,7 @@ public class Checker {
private List getMissedFeatures(List> futures) throws InterruptedException{
List missedList = new ArrayList<>();
- Set errors = new HashSet<>();
+ Set errors = new HashSet<>();
for (Future future : futures) {
try{
GmlId gmlId = future.get();
@@ -783,7 +797,7 @@ public class Checker {
} catch (ExecutionException e){
logger.debug("A Task failed due to an unexpected exception", e);
logger.debug(e.getCause());
- errors.add(e);
+ errors.add(e.getCause().toString());
}
}
if (!errors.isEmpty()) {
@@ -831,7 +845,9 @@ public class Checker {
if (!filterObject(co)) {
return;
}
+ co.prepareForChecking();
executeChecksForCheckable(co);
+ co.clearMetaInformation();
}
/**
@@ -911,12 +927,26 @@ public class Checker {
// 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 validationIssues = CityGmlParser.validateFile(null, inputFile.toPath());
+ for (String xmlError : validationIssues) {
+ XMLValidationError validationError = new XMLValidationError(xmlError);
+ if (xmlReporter != null) {
+ xmlReporter.reportGlobalError(validationError);
+ }
+ if (pdfReporter != null) {
+ pdfReporter.reportGlobalError(validationError);
+ }
+ }
+ }
// execute schematron first
SvrlContentHandler handler = executeSchematronValidationIfAvailable(config, inputFile);
CityGmlConsumer con = new StreamCityGmlConsumer(c, xmlReporter, pdfReporter, handler, config, l);
// parse and validate
- CityGmlParser.streamCityGml(inputFile.getAbsolutePath(), config.getParserConfiguration(), con, outputFile);
+ CityGmlParser.streamCityGml(inputFile, config.getParserConfiguration(), con, outputFile);
// write reports if available
writeReport(xmlReporter);
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/ValidationConfiguration.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/ValidationConfiguration.java
index dec4e67068556520fc746b0919a1c2610ab61e4e..2e12266e8ca6cf232f42d47e703870e9c63ec218 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/ValidationConfiguration.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/ValidationConfiguration.java
@@ -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 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 getGlobalParameters() {
return globalParameters;
}
-
+
public void setGlobalParameters(Map 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 requirements) {
this.requirements = requirements;
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/CheckContainer.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/CheckContainer.java
index 7e628940ea3053a8e1dc3ff0d15d9586bde08e4c..ff4b7b5453bd2b1ffa5c786061a6e77bea28144d 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/CheckContainer.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/CheckContainer.java
@@ -62,7 +62,7 @@ public class CheckContainer extends Check {
}
@Override
- public void init(Map parameters, ParserConfiguration config) {
+ public void init(Map> parameters, ParserConfiguration config) {
check.init(parameters, config);
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/DuplicatePointsCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/DuplicatePointsCheck.java
index 991e7f259e5661ab98f27d08f4410edbfd687572..87f0d395022ada549b15c29fe8ee356fbd7ef4f4 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/DuplicatePointsCheck.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/DuplicatePointsCheck.java
@@ -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 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 params, ParserConfiguration config) {
- String epsilonString = params.get(EPSILON_NAME);
+ public void init(Map> params, ParserConfiguration config) {
+ Map 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);
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorDisconnectedCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorDisconnectedCheck.java
index 99fa9f41016c0ebf955c3d084705addcb01e73a4..7c54953cce7270ed3f68c3348c53f1029b12ed78 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorDisconnectedCheck.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/InteriorDisconnectedCheck.java
@@ -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 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 params, ParserConfiguration config) {
- String epsilonString = params.get(EPSILON_NAME);
+ public void init(Map> params, ParserConfiguration config) {
+ Map 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);
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NullAreaCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NullAreaCheck.java
index fdba67d70045cd768780a85dc6a5bb3aa28c6bf4..feec95f21754400d725e1eb13257840d87166f96 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NullAreaCheck.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/NullAreaCheck.java
@@ -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 dependencies;
+ private double delta = 0.0001;
+
static {
ArrayList 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 parameters, ParserConfiguration config) {
- if (parameters.containsKey(DELTA_NAME)) {
- delta = Double.parseDouble(parameters.get(DELTA_NAME));
+ public void init(Map> params, ParserConfiguration config) {
+ Map localParameters = params.get(getCheckId());
+ if (localParameters == null) {
+ // no parameters
+ return;
+ }
+ String epsilonString = localParameters.get(DELTA_NAME);
+ if (epsilonString != null) {
+ delta = Double.parseDouble(epsilonString);
}
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/PlanarCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/PlanarCheck.java
index af6dd4cc83280a9a4b454c5807339138ec15dbcf..c05cc51754e9e10b3cf9db85adc8b4ff18b267b0 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/PlanarCheck.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/PlanarCheck.java
@@ -81,17 +81,22 @@ public class PlanarCheck extends Check {
private double delta = 0.01;
@Override
- public void init(Map parameters, ParserConfiguration config) {
- if (parameters.containsKey(TYPE)) {
- planarCheckType = parameters.get(TYPE).toLowerCase();
+ public void init(Map> parameters, ParserConfiguration config) {
+ Map 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));
}
}
diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
index 1788939047f0539f857a7755e4bf1bafaa575cbc..e10d61d13d3e87102b6608f8518c420a59758a1e 100644
--- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
+++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/checks/geometry/RingSelfIntCheck.java
@@ -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