Commit b8bac3e6 authored by Luna Riegel's avatar Luna Riegel
Browse files

Merge branch 'dev' into 'master'

CityDoctor Version 3.18.2

See merge request !40
parents 947020ff 4b27a7e4
Pipeline #12385 passed with stage
in 2 minutes and 4 seconds
...@@ -8,25 +8,34 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject; ...@@ -8,25 +8,34 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId; import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import de.hft.stuttgart.citydoctor2.utils.Localization; import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.h2gis.functions.factory.H2GISDBFactory;
import org.h2gis.utilities.wrapper.DataSourceWrapper; import org.h2gis.utilities.wrapper.DataSourceWrapper;
import org.h2gis.functions.factory.H2GISFunctions;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InvalidClassException; import java.io.InvalidClassException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Array;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set;
/** /**
* Utility class for setup and access of the embedded database. * Utility class for setup and access of the embedded database.
...@@ -34,34 +43,71 @@ import java.util.Map; ...@@ -34,34 +43,71 @@ import java.util.Map;
public class EmbeddedDatabaseHandler { public class EmbeddedDatabaseHandler {
private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class); private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class);
private static final String DB_NAME = "/database/cd_db"; private final EmbeddedDatabaseConfiguration config;
private DataSourceWrapper dataSource; private DataSourceWrapper dataSource;
private File tempFileDBLocation = null;
public EmbeddedDatabaseHandler(){ public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){
config = dbConfig;
String jdbcUrl;
try { try {
String jdbcurl = H2GISDBFactory.createDataSource(DB_NAME, if (config.inMemoryMode()) {
true, ";").getConnection().getMetaData().getURL(); jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url());
} else if (config.tempFileMode()){
Path tmpDir = Files.createTempDirectory(config.databaseDirectory());
tmpDir.toFile().deleteOnExit();
String tempFileUrl = tmpDir.resolve(config.databaseName()).toFile().getCanonicalPath();
jdbcUrl = createEmbeddedDataBase(config.wrapUrlWithH2JdbcDriver(tempFileUrl));
tempFileDBLocation = new File(tempFileUrl+".mv.db");
tempFileDBLocation.deleteOnExit();
} else {
jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl());
}
logger.debug("1] {}",jdbcUrl);
logger.debug("1] {}",jdbcurl); } catch (Exception e) {
HikariDataSource ds = new HikariDataSource(); logger.fatal(Localization.getText("DatabaseHandler.setupFailure"));
logger.fatal(e.getMessage());
if (config.attemptFallback()){
logger.info("Attempting fallback to in-memory database");
try {
jdbcUrl = createEmbeddedDataBase(config.getFallbackH2Url());
} catch (Exception e1) {
throw new EmbeddedDatabaseHandlerException("Fallback to in-memory database failed, embedded database could not be created", e1);
}
logger.info("Fallback to in-memory database succeeded");
logger.warn("The in-memory database is limited by the system's available RAM. Loading big CityGML files can lead to OutOfMemory errors.");
} else {
throw new EmbeddedDatabaseHandlerException("Embedded database could not be created");
}
ds.setJdbcUrl(jdbcurl); }
setupHikariPool(jdbcUrl);
setupFeaturesTable();
}
private void setupHikariPool(String jdbcUrl){
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(jdbcUrl);
ds.setUsername("sa"); ds.setUsername("sa");
ds.setPassword("sa"); ds.setPassword("sa");
int coreCount = Runtime.getRuntime().availableProcessors(); ds.setMaximumPoolSize(config.connectionPoolSize());
ds.setMaximumPoolSize(coreCount*5);
ds.setMinimumIdle(coreCount*5);
dataSource = new DataSourceWrapper(ds); dataSource = new DataSourceWrapper(ds);
setupFeaturesTable();
//Cleanup hook //Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close)); Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
} catch (Exception e) {
logger.fatal(Localization.getText("DatabaseHandler.setupFailure"));
logger.fatal(e.getMessage());
} }
// Embedded database can only ever be accessed by the local machine, suppress linter-warning about password leak
@SuppressWarnings("java:S6437")
private String createEmbeddedDataBase(String dbUrl) throws SQLException {
try (Connection con = DriverManager.getConnection(dbUrl,"sa","sa")){
H2GISFunctions.load(con);
return con.getMetaData().getURL();
}
}
public File getTempFileDBLocation(){
return this.tempFileDBLocation;
} }
...@@ -69,22 +115,26 @@ public class EmbeddedDatabaseHandler { ...@@ -69,22 +115,26 @@ public class EmbeddedDatabaseHandler {
try (Connection con = dataSource.getConnection()) { try (Connection con = dataSource.getConnection()) {
try (PreparedStatement dropPs = con.prepareStatement("DROP TABLE IF EXISTS features")){ try (PreparedStatement dropPs = con.prepareStatement("DROP TABLE IF EXISTS features")){
dropPs.executeUpdate(); dropPs.executeUpdate();
if (logger.isDebugEnabled()) { logger.trace("Dropped existing features table");
logger.debug("Dropped existing features table");
} }
try (PreparedStatement dropIndexPs = con.prepareStatement("DROP INDEX IF EXISTS FEATURES_SPATIAL_INDEX")){
dropIndexPs.executeUpdate();
logger.trace("Dropped existing spatial index");
} }
try (PreparedStatement createPs = con.prepareStatement("CREATE TABLE features (gmlid VARCHAR(255)" + try (PreparedStatement createPs = con.prepareStatement("CREATE TABLE features (gmlid VARCHAR(255)" +
" PRIMARY KEY, bbox GEOMETRY, data BLOB, errors BOOLEAN);")) { " PRIMARY KEY, bbox GEOMETRY, data BLOB, errors BOOLEAN);")) {
createPs.executeUpdate(); createPs.executeUpdate();
if (logger.isDebugEnabled()) { logger.trace("Created features table");
logger.debug("Created features table");
} }
//CREATE SPATIAL INDEX GEO_TABLE_SPATIAL_INDEX ON GEO_TABLE(THE_GEOM); <- Create spatial id try (PreparedStatement createIndexPs = con.prepareStatement("CREATE SPATIAL INDEX FEATURES_SPATIAL_INDEX ON features(bbox);")){
createIndexPs.executeUpdate();
logger.trace("Created spatial index");
} }
} catch (SQLException e) { } catch (SQLException e) {
logger.fatal(Localization.getText("DatabaseHandler.tableFailure")); logger.fatal(Localization.getText("DatabaseHandler.tableFailure"));
logger.fatal(e.getMessage()); logger.fatal(e.getMessage());
throw new EmbeddedDatabaseHandlerException("Could not setup features table in database", e);
} }
} }
...@@ -96,8 +146,7 @@ public class EmbeddedDatabaseHandler { ...@@ -96,8 +146,7 @@ public class EmbeddedDatabaseHandler {
try (Connection con = dataSource.getConnection()) { try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("MERGE INTO features VALUES (?, ?, ?, ?)")) { try (PreparedStatement ps = con.prepareStatement("MERGE INTO features VALUES (?, ?, ?, ?)")) {
ps.setString(1, co.getGmlId().toString()); ps.setString(1, co.getGmlId().toString());
String bbox = BoundingBox.of(co).to2DWkt(); ps.setString(2, co.getBbox().to2DWkt());
ps.setString(2, bbox);
ps.setBoolean(4, co.containsAnyError()); ps.setBoolean(4, co.containsAnyError());
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
...@@ -198,7 +247,7 @@ public class EmbeddedDatabaseHandler { ...@@ -198,7 +247,7 @@ public class EmbeddedDatabaseHandler {
} }
} }
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new EmbeddedDatabaseHandlerException(e);
} }
} catch (SQLException e) { } catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.unmarshallingFailure"), id); logger.error(Localization.getText("DatabaseHandler.unmarshallingFailure"), id);
...@@ -208,6 +257,47 @@ public class EmbeddedDatabaseHandler { ...@@ -208,6 +257,47 @@ public class EmbeddedDatabaseHandler {
return null; return null;
} }
public Map<GmlId, CityObject> unmarshallAllIds(Set<? extends GmlId> ids){
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement(
"SELECT data FROM features WHERE ARRAY_CONTAINS(? ,gmlid)")) {
String[] idStrings = ids.stream().map(Objects::toString).toArray(String[]::new);
Array array = con.createArrayOf("VARCHAR(255)", idStrings);
ps.setArray(1, array);
ResultSet rs = ps.executeQuery();
boolean encounteredError = false;
Map<GmlId, CityObject> objects= new HashMap<>();
while (rs.next()) {
try (InputStream is = rs.getBinaryStream("data")) {
CityObject co = SerializationUtils.deserialize(is);
// Rebuild the adjacency maps
co.accept(new CheckableUtilsVisitor() {
@Override
public void check(Geometry geom) {
geom.updateVertices();
}
});
objects.put(co.getGmlId(),co);
} catch (InvalidClassException | ClassCastException | SerializationException e){
//Cant get the id of the failed Feature, as the deserialization didn't work.
encounteredError = true;
}
}
if (encounteredError){
logger.error("Deserialization of one or more Features failed");
}
return objects;
} catch (IOException e) {
throw new EmbeddedDatabaseHandlerException(e);
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.unmarshallingFailure"), ids);
logger.error(e.getMessage());
}
return null;
}
/** /**
* Retrieves a List of GmlIds of CityObjects that contain any {@link de.hft.stuttgart.citydoctor2.check.CheckError CheckError}. * Retrieves a List of GmlIds of CityObjects that contain any {@link de.hft.stuttgart.citydoctor2.check.CheckError CheckError}.
* @return a List of GmlIds of CityObjects containing any CheckError * @return a List of GmlIds of CityObjects containing any CheckError
......
...@@ -8,7 +8,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.GmlId; ...@@ -8,7 +8,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
...@@ -42,12 +44,18 @@ public class FeatureCache implements CityObjectCache{ ...@@ -42,12 +44,18 @@ public class FeatureCache implements CityObjectCache{
private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>();
public FeatureCache() { public FeatureCache() {
handler = new EmbeddedDatabaseHandler(); this(DatabaseSettings.getConfig());
}
public FeatureCache(EmbeddedDatabaseConfiguration dbConfig){
handler = new EmbeddedDatabaseHandler(dbConfig);
DatabaseCacheLoader.Unmarshaller<GmlId, CityObject> single = handler::unmarshallCityObject;
DatabaseCacheLoader.BatchUnmarshaller<GmlId, CityObject> batch = handler::unmarshallAllIds;
cache = Caffeine.newBuilder().maximumSize(3000).removalListener((GmlId key, CityObject value, RemovalCause cause) -> { cache = Caffeine.newBuilder().maximumSize(3000).removalListener((GmlId key, CityObject value, RemovalCause cause) -> {
if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){ if (cause.wasEvicted() && isFeatureMarshallable(value)){
handler.marshallCityObject(value); handler.marshallCityObject(value);
} }
}).build(handler::unmarshallCityObject); }).build(new DatabaseCacheLoader<>(single, batch));
} }
/** /**
...@@ -111,6 +119,11 @@ public class FeatureCache implements CityObjectCache{ ...@@ -111,6 +119,11 @@ public class FeatureCache implements CityObjectCache{
return cache.get(id); return cache.get(id);
} }
@Override
public Collection<CityObject> getAll(List<GmlId> ids){
Map<GmlId, CityObject> map = cache.getAll(new HashSet<>(ids));
return map.values();
}
@Override @Override
......
...@@ -5,6 +5,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject; ...@@ -5,6 +5,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId; import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -37,6 +38,12 @@ public class UnconnectedCache implements CityObjectCache{ ...@@ -37,6 +38,12 @@ public class UnconnectedCache implements CityObjectCache{
return cache.get(id); return cache.get(id);
} }
@Override
public Collection<CityObject> getAll(List<GmlId> ids) {
return ids.stream().map(cache::get).toList();
}
@Override @Override
public void replace(GmlId id, CityObject cityObject) { public void replace(GmlId id, CityObject cityObject) {
cache.put(id, cityObject); cache.put(id, cityObject);
......
...@@ -22,6 +22,8 @@ import de.hft.stuttgart.citydoctor2.math.Vector3d; ...@@ -22,6 +22,8 @@ import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator; import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.visitors.MinMaxExtentVisitor; import de.hft.stuttgart.citydoctor2.utils.visitors.MinMaxExtentVisitor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
...@@ -31,7 +33,10 @@ import java.util.Locale; ...@@ -31,7 +33,10 @@ import java.util.Locale;
* *
* @author Matthias Betz * @author Matthias Betz
*/ */
public class BoundingBox { public class BoundingBox implements Serializable {
@Serial
private static final long serialVersionUID = 1998769586245522486L;
private final Vector3d[] bbox; private final Vector3d[] bbox;
......
...@@ -18,13 +18,10 @@ ...@@ -18,13 +18,10 @@
*/ */
package de.hft.stuttgart.citydoctor2.datastructure; package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import java.io.Serial;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import java.util.ArrayList;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils; import java.util.List;
import javafx.scene.paint.Color;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.model.deprecated.bridge.DeprecatedPropertiesOfBridgeConstructiveElement; import org.citygml4j.core.model.deprecated.bridge.DeprecatedPropertiesOfBridgeConstructiveElement;
import org.citygml4j.core.util.geometry.GeometryFactory; import org.citygml4j.core.util.geometry.GeometryFactory;
import org.xmlobjects.gml.model.geometry.GeometryProperty; import org.xmlobjects.gml.model.geometry.GeometryProperty;
...@@ -34,14 +31,13 @@ import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface; ...@@ -34,14 +31,13 @@ import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface;
import org.xmlobjects.gml.model.geometry.primitives.Solid; import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SolidProperty; import org.xmlobjects.gml.model.geometry.primitives.SolidProperty;
import java.io.Serial; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import java.util.ArrayList; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import java.util.List; import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import javafx.scene.paint.Color;
public class BridgeConstructiveElement extends CityObject { public class BridgeConstructiveElement extends CityObject {
private static final Logger logger = LogManager.getLogger(BridgeConstructiveElement.class);
private static final String CANNOT_ADD = "Cannot add "; private static final String CANNOT_ADD = "Cannot add ";
@Serial @Serial
...@@ -105,20 +101,6 @@ public class BridgeConstructiveElement extends CityObject { ...@@ -105,20 +101,6 @@ public class BridgeConstructiveElement extends CityObject {
} }
} }
private void reCreateBoundarySurface(GeometryFactory factory, ParserConfiguration config, BoundarySurface bs) {
if (bs.getGeometries().isEmpty()) {
for (AbstractSpaceBoundaryProperty bsp : gmlBridgeElement.getBoundaries()) {
if (bsp.getObject() != null && bsp.getObject() == bs.getGmlObject()) {
logger.warn("Found empty boundary surface: {}, removing from BridgeConstructiveElement", bs.getGmlId());
gmlBridgeElement.getBoundaries().remove(bsp);
break;
}
}
return;
}
bs.reCreateGeometries(factory, config);
}
private void setCompositeSurfaceAccordingToLod(Geometry geom, CompositeSurface cs) { private void setCompositeSurfaceAccordingToLod(Geometry geom, CompositeSurface cs) {
switch (geom.getLod()) { switch (geom.getLod()) {
case LOD1: case LOD1:
......
...@@ -43,7 +43,7 @@ public abstract class CityObject extends GmlElement { ...@@ -43,7 +43,7 @@ public abstract class CityObject extends GmlElement {
@Serial @Serial
private static final long serialVersionUID = 651712070755024188L; private static final long serialVersionUID = 651712070755024188L;
private BoundingBox bbox;
private final List<Geometry> geometryList = new ArrayList<>(); private final List<Geometry> geometryList = new ArrayList<>();
private final List<GenericAttribute> genericAttributeList = new ArrayList<>(); private final List<GenericAttribute> genericAttributeList = new ArrayList<>();
...@@ -88,6 +88,17 @@ public abstract class CityObject extends GmlElement { ...@@ -88,6 +88,17 @@ public abstract class CityObject extends GmlElement {
this.accept(new UnsetGeometriesVisitor()); this.accept(new UnsetGeometriesVisitor());
} }
public void setBbox(BoundingBox bbox){
this.bbox = bbox;
}
public synchronized BoundingBox getBbox(){
if (bbox == null){
this.setBbox(BoundingBox.of(this));
}
return bbox;
}
/** /**
* Adds a Geometry to this object. * Adds a Geometry to this object.
* @param geom the Geometry to add. * @param geom the Geometry to add.
......
...@@ -9,6 +9,8 @@ import java.io.Serial; ...@@ -9,6 +9,8 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
import java.util.function.Supplier;
public class GenericAttribute implements Serializable { public class GenericAttribute implements Serializable {
...@@ -17,6 +19,7 @@ public class GenericAttribute implements Serializable { ...@@ -17,6 +19,7 @@ public class GenericAttribute implements Serializable {
private static final long serialVersionUID = -4101408947632132328L; private static final long serialVersionUID = -4101408947632132328L;
private static final Logger logger = LogManager.getLogger(GenericAttribute.class); private static final Logger logger = LogManager.getLogger(GenericAttribute.class);
private static final String NULL_VALUE_STRING= "NULL OR INVALID VALUE";
private final AbstractGenericAttributeProperty original; private final AbstractGenericAttributeProperty original;
...@@ -24,6 +27,7 @@ public class GenericAttribute implements Serializable { ...@@ -24,6 +27,7 @@ public class GenericAttribute implements Serializable {
private String name; private String name;
private String value; private String value;
public GenericAttribute(AbstractGenericAttributeProperty attributeProperty) { public GenericAttribute(AbstractGenericAttributeProperty attributeProperty) {
original = attributeProperty; original = attributeProperty;
if (attributeProperty.getObject() == null) { if (attributeProperty.getObject() == null) {
...@@ -34,40 +38,42 @@ public class GenericAttribute implements Serializable { ...@@ -34,40 +38,42 @@ public class GenericAttribute implements Serializable {
if (attributeProperty.getObject() instanceof StringAttribute sa) { if (attributeProperty.getObject() instanceof StringAttribute sa) {
type = "StringAttribute"; type = "StringAttribute";
value = String.format("\"%s\"", sa.getValue()); value = extractValue(() -> String.format("\"%s\"", sa.getValue()));
} else if (attributeProperty.getObject() instanceof IntAttribute ia) { } else if (attributeProperty.getObject() instanceof IntAttribute ia) {
type = "IntAttribute"; type = "IntAttribute";
value = ia.getValue().toString(); value = extractValue(() -> ia.getValue().toString());
} else if (attributeProperty.getObject() instanceof DoubleAttribute da) { } else if (attributeProperty.getObject() instanceof DoubleAttribute da) {
type = "DoubleAttribute"; type = "DoubleAttribute";
value = da.getValue().toString(); value = extractValue(() -> da.getValue().toString());
} else if (attributeProperty.getObject() instanceof DateAttribute date) { } else if (attributeProperty.getObject() instanceof DateAttribute date) {
type = "DateAttribute"; type = "DateAttribute";
value = date.getValue().toString(); value = extractValue(() -> date.getValue().toString());
} else if (attributeProperty.getObject() instanceof UriAttribute ua) { } else if (attributeProperty.getObject() instanceof UriAttribute ua) {
type = "UriAttribute"; type = "UriAttribute";
value = ua.getValue(); value = extractValue(ua::getValue);
} else if (attributeProperty.getObject() instanceof MeasureAttribute ma) { } else if (attributeProperty.getObject() instanceof MeasureAttribute ma) {
type = "MeasureAttribute"; type = "MeasureAttribute";
value = String.format("%s %s", ma.getValue().getValue().toString(), ma.getValue().getUom()); value = extractValue(() ->
String.format("%s %s", ma.getValue().getValue().toString(), ma.getValue().getUom()));
} else if (attributeProperty.getObject() instanceof CodeAttribute ca) { } else if (attributeProperty.getObject() instanceof CodeAttribute ca) {
type = String.format("CodeAttribute (%s)", ca.getValue().getLanguage()); type = String.format("CodeAttribute (%s)", ca.getValue().getLanguage());
value = String.format("'''%s''' %n CodeSpace: %s", ca.getValue().getValue(), ca.getValue().getCodeSpace()); value = extractValue(() ->
String.format("'''%s''' %n CodeSpace: %s", ca.getValue().getValue(), ca.getValue().getCodeSpace()));
} else if (attributeProperty.getObject() instanceof GenericAttributeSet gas) { } else if (attributeProperty.getObject() instanceof GenericAttributeSet gas) {
type = "GenericAttributeSet"; type = "GenericAttributeSet";
List<AbstractGenericAttributeProperty> attributes = gas.getValue(); List<AbstractGenericAttributeProperty> attributes = gas.getValue();
StringBuilder sb = new StringBuilder(); if(attributes == null || attributes.isEmpty()){
sb.append("{"); value = NULL_VALUE_STRING;
return;
}
StringJoiner sj = new StringJoiner("; ","{","}");
sj.setEmptyValue("EMPTY_SET");
for (AbstractGenericAttributeProperty attribute : attributes) { for (AbstractGenericAttributeProperty attribute : attributes) {
if (attribute.getObject() != null) { if (attribute.getObject() != null) {
sb.append(attribute.getObject().getName()); sj.add(String.format("%1$s = %2$s",attribute.getObject().getName(), attribute.getObject().getValue()));
sb.append(" = ");
sb.append(attribute.getObject().getValue());
sb.append("; ");
} }
} }
sb.replace(sb.length() - 1, sb.length(), "}"); value = sj.toString();
value = String.format("'%s'", sb);
} else { } else {
logger.warn("GenericAttribute {} is of unknown type {}", attributeProperty, attributeProperty.getObject()); logger.warn("GenericAttribute {} is of unknown type {}", attributeProperty, attributeProperty.getObject());
...@@ -77,6 +83,14 @@ public class GenericAttribute implements Serializable { ...@@ -77,6 +83,14 @@ public class GenericAttribute implements Serializable {
} }
private String extractValue(Supplier<String> supply){
try{
return supply.get();
}catch (NullPointerException e){
return NULL_VALUE_STRING;
}
}
public AbstractGenericAttributeProperty getOriginal() { public AbstractGenericAttributeProperty getOriginal() {
return original; return original;
} }
......
...@@ -18,6 +18,16 @@ ...@@ -18,6 +18,16 @@
*/ */
package de.hft.stuttgart.citydoctor2.datastructure; package de.hft.stuttgart.citydoctor2.datastructure;
import java.io.Serial;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.math.Triangle3d; import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.math.Vector3d;
...@@ -26,11 +36,6 @@ import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator; ...@@ -26,11 +36,6 @@ import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair; import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.util.*;
/** /**
* Representation of a geometry containing the polygons and edges * Representation of a geometry containing the polygons and edges
* *
...@@ -55,9 +60,11 @@ public class Geometry extends GmlElement { ...@@ -55,9 +60,11 @@ public class Geometry extends GmlElement {
private CityObject parent; private CityObject parent;
private final List<Polygon> polygons = new ArrayList<>(2); private final List<Polygon> polygons = new ArrayList<>(2);
private transient List<Edge> edges; private List<Edge> edges;
private transient Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap; private Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap;
private transient List<Vertex> vertices; private List<Vertex> vertices;
private Map<Vertex, Set<LinearRing>> adjacentRingsOfVertices = new HashMap<>();
private Orientation orientation; private Orientation orientation;
public Geometry(GeometryType type, Lod lod, Orientation orientation) { public Geometry(GeometryType type, Lod lod, Orientation orientation) {
...@@ -69,6 +76,15 @@ public class Geometry extends GmlElement { ...@@ -69,6 +76,15 @@ public class Geometry extends GmlElement {
this.orientation = orientation; this.orientation = orientation;
} }
public Set<LinearRing> getAdjacentRingsOfVertex(Vertex v) {
return adjacentRingsOfVertices.compute(v, (vertex, set) -> {
if (set == null) {
return new HashSet<>(4);
}
return set;
});
}
public Orientation getOrientation() { public Orientation getOrientation() {
return orientation; return orientation;
} }
...@@ -346,18 +362,9 @@ public class Geometry extends GmlElement { ...@@ -346,18 +362,9 @@ public class Geometry extends GmlElement {
return false; return false;
} }
@Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
edges = new ArrayList<>();
edgeMap = new HashMap<>();
}
public void clearMetaData() { public void clearMetaData() {
if (vertices != null) { if (vertices != null) {
for (Vertex v : vertices) { adjacentRingsOfVertices = new HashMap<>();
v.clearAdjacentRings();
}
vertices = null; vertices = null;
} }
edges = null; edges = null;
......
...@@ -61,6 +61,7 @@ public class ImplicitGeometryHolder extends Geometry { ...@@ -61,6 +61,7 @@ public class ImplicitGeometryHolder extends Geometry {
if(!deferredRelGeomObjects.isEmpty()) { if(!deferredRelGeomObjects.isEmpty()) {
logger.info("Resolving deferred relative geometries"); logger.info("Resolving deferred relative geometries");
} }
Set<GmlId> changedFeatures = new HashSet<>();
for (ImplicitGeometryHolder key : deferredRelGeomObjects.keySet()) { for (ImplicitGeometryHolder key : deferredRelGeomObjects.keySet()) {
ImplicitGeometry ig = deferredRelGeomObjects.get(key); ImplicitGeometry ig = deferredRelGeomObjects.get(key);
String hrefId = ig.getRelativeGeometry().getHref(); String hrefId = ig.getRelativeGeometry().getHref();
...@@ -72,10 +73,15 @@ public class ImplicitGeometryHolder extends Geometry { ...@@ -72,10 +73,15 @@ public class ImplicitGeometryHolder extends Geometry {
CityObject parent = cache.get(key.getParent().getGmlId()); CityObject parent = cache.get(key.getParent().getGmlId());
parent.removeGeometry(igh.getLod(), igh.getType()); parent.removeGeometry(igh.getLod(), igh.getType());
parent.addGeometry(igh); parent.addGeometry(igh);
changedFeatures.add(parent.getTopLevelCityObject().getGmlId());
} else { } else {
missingGeoms.add(gmlId); missingGeoms.add(gmlId);
} }
} }
changedFeatures.forEach(gmlId -> {
CityObject co = cache.get(gmlId);
co.setBbox(BoundingBox.of(co));
});
if (!missingGeoms.isEmpty()) { if (!missingGeoms.isEmpty()) {
logger.warn("RelativeGeometries of the following gmlIDs could not be resolved: {}", missingGeoms); logger.warn("RelativeGeometries of the following gmlIDs could not be resolved: {}", missingGeoms);
} }
...@@ -113,31 +119,6 @@ public class ImplicitGeometryHolder extends Geometry { ...@@ -113,31 +119,6 @@ public class ImplicitGeometryHolder extends Geometry {
LibraryObject.resetObjectMap(); LibraryObject.resetObjectMap();
} }
/**
* Prepares the prototype geometries for checking and sets a lock to prevent clearing of metadata
*/
public static void preparePrototypeGeometriesForChecking(){
RelativeGeometry.preparePrototypeGeometriesForChecking();
LibraryObject.preparePrototypeGeometriesForChecking();
}
/**
* Clears metadata creating for checking of prototype geometries and releases the metadata lock
*/
public static void clearMetaDataOfPrototypeGeometries(){
RelativeGeometry.clearMetaDataOfPrototypeGeometries();
LibraryObject.clearMetaDataOfPrototypeGeometries();
}
@Override
public void accept(CheckableVisitor c) {
prototypeGeometry.accept(c);
}
@Override
public boolean isValidated() {
return prototypeGeometry.isValidated();
}
} }
...@@ -26,7 +26,6 @@ public class LibraryObject extends Geometry { ...@@ -26,7 +26,6 @@ public class LibraryObject extends Geometry {
private static final Logger logger = LogManager.getLogger(LibraryObject.class); private static final Logger logger = LogManager.getLogger(LibraryObject.class);
@Serial @Serial
private static final long serialVersionUID = -50293435187454911L; private static final long serialVersionUID = -50293435187454911L;
private static boolean metadataLocked = false;
private static final Map<String, LibraryObject> libraryObjects = new ConcurrentHashMap<>(); private static final Map<String, LibraryObject> libraryObjects = new ConcurrentHashMap<>();
public static LibraryObject of(Path path, ParserConfiguration config) { public static LibraryObject of(Path path, ParserConfiguration config) {
...@@ -79,33 +78,8 @@ public class LibraryObject extends Geometry { ...@@ -79,33 +78,8 @@ public class LibraryObject extends Geometry {
return objects.get(0).getHighestLodGeometry(); return objects.get(0).getHighestLodGeometry();
} }
public static void resetObjectMap() { public static void resetObjectMap() {
libraryObjects.clear(); libraryObjects.clear();
} }
public static void preparePrototypeGeometriesForChecking() {
libraryObjects.values().forEach(LibraryObject::updateEdgesAndVertices);
metadataLocked = true;
}
public static void clearMetaDataOfPrototypeGeometries() {
metadataLocked = false;
libraryObjects.values().forEach(LibraryObject::clearMetaData);
}
@Override
public void updateEdgesAndVertices(){
if (!metadataLocked) {
super.updateEdgesAndVertices();
}
}
@Override
public void clearMetaData(){
if (!metadataLocked) {
super.clearMetaData();
}
}
} }
...@@ -19,8 +19,11 @@ ...@@ -19,8 +19,11 @@
package de.hft.stuttgart.citydoctor2.datastructure; package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.Check; 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.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon; import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
...@@ -62,6 +65,52 @@ public class LinkedPolygon extends Polygon { ...@@ -62,6 +65,52 @@ public class LinkedPolygon extends Polygon {
} }
} }
@Override
public boolean hasExistingGmlId() {
return poly.hasExistingGmlId();
}
@Override
public void setValidated(boolean validated) {
// Linked polys themselves are not checked
}
@Override
public boolean isValidated() {
return poly.isValidated();
}
//TODO: Think how to handle this, as it violates class contract if ignored
@Override
public void prepareForChecking() {
// Linked polys themselves are not checked
}
@Override
public void clearMetaInformation() {
// Linked polys themselves are not checked
}
@Override
public boolean hasError(CheckId checkIdentifier) {
return poly.hasError(checkIdentifier);
}
@Override
public boolean hasAnyError() {
return poly.hasAnyError();
}
@Override
public void collectContainedErrors(List<CheckError> errors) {
poly.collectContainedErrors(errors);
}
@Override
public void collectErrors(List<CheckError> errors) {
poly.collectErrors(errors);
}
@Override @Override
public CheckResult getCheckResult(Check c) { public CheckResult getCheckResult(Check c) {
return poly.getCheckResult(c); return poly.getCheckResult(c);
...@@ -104,8 +153,12 @@ public class LinkedPolygon extends Polygon { ...@@ -104,8 +153,12 @@ public class LinkedPolygon extends Polygon {
@Override @Override
public void accept(CheckableVisitor c) { public void accept(CheckableVisitor c) {
// linked polygons are not checked // LinkedPolygon should ignore Checks, as their referenced ConcretePolygon must be within the same TopLevelFeature.
setValidated(true); // Checks always start iterating from a TopLevelFeature and thus will visit the referenced ConcretePolygon.
if (!c.isValidator()){
// Utility Visitors get passed to the ConcretePolygon, as they can start iterating from a sub-CityObject.
poly.accept(c);
}
} }
@Override @Override
......
...@@ -15,7 +15,6 @@ public class RelativeGeometry extends Geometry { ...@@ -15,7 +15,6 @@ public class RelativeGeometry extends Geometry {
private static final long serialVersionUID = -686112245455298977L; private static final long serialVersionUID = -686112245455298977L;
private static final Map<GmlId, RelativeGeometry> relativeGeometries = new ConcurrentHashMap<>(); private static final Map<GmlId, RelativeGeometry> relativeGeometries = new ConcurrentHashMap<>();
private static boolean metadataLocked = false;
public static RelativeGeometry of(GmlId gmlId, Geometry geom) { public static RelativeGeometry of(GmlId gmlId, Geometry geom) {
if (relativeGeometries.containsKey(gmlId)) { if (relativeGeometries.containsKey(gmlId)) {
...@@ -40,27 +39,5 @@ public class RelativeGeometry extends Geometry { ...@@ -40,27 +39,5 @@ public class RelativeGeometry extends Geometry {
relativeGeometries.clear(); relativeGeometries.clear();
} }
public static void preparePrototypeGeometriesForChecking() {
relativeGeometries.values().forEach(RelativeGeometry::updateEdgesAndVertices);
metadataLocked = true;
}
public static void clearMetaDataOfPrototypeGeometries() {
metadataLocked = false;
relativeGeometries.values().forEach(RelativeGeometry::clearMetaData);
}
@Override
public void updateEdgesAndVertices(){
if (!metadataLocked) {
super.updateEdgesAndVertices();
}
}
@Override
public void clearMetaData(){
if (!metadataLocked) {
super.clearMetaData();
}
}
} }
...@@ -18,16 +18,12 @@ ...@@ -18,16 +18,12 @@
*/ */
package de.hft.stuttgart.citydoctor2.datastructure; package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial; import java.io.Serial;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
/** /**
* Contains the vertex information of a point in a linear ring * Contains the vertex information of a point in a linear ring
...@@ -39,7 +35,6 @@ public class Vertex extends Vector3d { ...@@ -39,7 +35,6 @@ public class Vertex extends Vector3d {
@Serial @Serial
private static final long serialVersionUID = -5525361920397934892L; private static final long serialVersionUID = -5525361920397934892L;
private transient List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);
public Vertex(double x, double y, double z) { public Vertex(double x, double y, double z) {
super(x, y, z); super(x, y, z);
...@@ -49,58 +44,16 @@ public class Vertex extends Vector3d { ...@@ -49,58 +44,16 @@ public class Vertex extends Vector3d {
super(vec); super(vec);
} }
private Set<LinearRing> getAdjacentRingsWithoutNeighbor(Geometry geom) {
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
return adjacency.getValue1();
}
}
throw new IllegalStateException("Requested adjacent rings with Geometry not containing this vertex");
}
public Set<LinearRing> getAdjacentRings(Geometry geom) { public Set<LinearRing> getAdjacentRings(Geometry geom) {
return getAdjacentRingsWithoutNeighbor(geom); return geom.getAdjacentRingsOfVertex(this);
} }
void addAdjacentRing(LinearRing ring, Geometry geom) { void addAdjacentRing(LinearRing ring, Geometry geom) {
findAdjacentRingsForGeometry(geom).add(ring); getAdjacentRings(geom).add(ring);
}
private Set<LinearRing> findAdjacentRingsForGeometry(Geometry geom) {
HashSet<LinearRing> adjacendRingsSet = null;
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
adjacendRingsSet = adjacency.getValue1();
}
}
if (adjacendRingsSet == null) {
adjacendRingsSet = new HashSet<>(4);
adjacentRings.add(new SerializablePair<>(geom, adjacendRingsSet));
}
return adjacendRingsSet;
}
private Set<Polygon> getAdjacentPolygonsWithoutNeighbor(Geometry geom) {
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
Set<Polygon> polygons = new HashSet<>();
for (LinearRing lr : adjacency.getValue1()) {
polygons.add(lr.getParent());
}
return polygons;
}
}
throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
}
@Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
clearAdjacentRings();
} }
public Set<Polygon> getAdjacentPolygons(Geometry geom) { public Set<Polygon> getAdjacentPolygons(Geometry geom) {
return getAdjacentPolygonsWithoutNeighbor(geom); return getAdjacentRings(geom).stream().map(r -> r.getParent()).collect(Collectors.toCollection(HashSet::new));
} }
@Override @Override
...@@ -127,21 +80,11 @@ public class Vertex extends Vector3d { ...@@ -127,21 +80,11 @@ public class Vertex extends Vector3d {
} }
public void clearAdjacentRings(Geometry geometry) { public void clearAdjacentRings(Geometry geometry) {
if (adjacentRings == null) { getAdjacentRings(geometry).clear();
return;
}
findAdjacentRingsForGeometry(geometry).clear();
} }
void removeAdjacency(LinearRing lr, Geometry geom) { void removeAdjacency(LinearRing lr, Geometry geom) {
findAdjacentRingsForGeometry(geom).remove(lr); getAdjacentRings(geom).remove(lr);
}
/**
* Remove all adjacent rings from this vertex, ignoring geometry association
*/
void clearAdjacentRings() {
adjacentRings = new ArrayList<>(2);
} }
} }
package de.hft.stuttgart.citydoctor2.exceptions;
import java.io.Serial;
public class CityObjectCacheException extends RuntimeException{
@Serial
private static final long serialVersionUID = -460467145629964615L;
public CityObjectCacheException(String message) {
super(message);
}
public CityObjectCacheException(String message, Throwable cause) {
super(message, cause);
}
}
package de.hft.stuttgart.citydoctor2.exceptions;
import java.io.Serial;
public class EmbeddedDatabaseHandlerException extends RuntimeException{
@Serial
private static final long serialVersionUID = -7848325704234347425L;
public EmbeddedDatabaseHandlerException(String message) {
super(message);
}
public EmbeddedDatabaseHandlerException(String message, Throwable cause) {
super(message, cause);
}
public EmbeddedDatabaseHandlerException(Throwable cause){
super(cause);
}
}
...@@ -23,9 +23,12 @@ import java.util.ArrayList; ...@@ -23,9 +23,12 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.database.FeatureCache; import de.hft.stuttgart.citydoctor2.database.FeatureCache;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache; import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject; import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
...@@ -166,16 +169,19 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -166,16 +169,19 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class); private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class);
private static FeatureCache featureCache = new FeatureCache(); private static final FeatureCache featureCache = new FeatureCache();
private final CityDoctorModel model; private final CityDoctorModel model;
private final ParserConfiguration config; private final ParserConfiguration config;
private final Path directory; private final Path directory;
private final double neighborDistance; private final double neighborDistance;
private Map<String, ConcretePolygon> polygonMap = new HashMap<>(); private Map<String, ConcretePolygon> polygonMap = new HashMap<>();
private Map<String, CompositeCollection> compositeMap = new HashMap<>(); private Map<String, CompositeCollection> compositeMap = new HashMap<>();;
private List<ResolvableReference> references = new ArrayList<>(); private List<ResolvableReference> references = new ArrayList<>();
private Map<Vertex, Vertex> vertexMap = new HashMap<>(); private Map<Vertex, Vertex> vertexMap = new HashMap<>();
private Map<String, List<GmlId>> unresolvedPolyRefIDMap = new HashMap<>();
private Map<String, GmlId> parsedPolygonsIdMap = new HashMap<>();
public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) { public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) {
this.config = config; this.config = config;
this.directory = path.getParent(); this.directory = path.getParent();
...@@ -197,6 +203,36 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -197,6 +203,36 @@ public class Citygml3FeatureMapper extends ObjectWalker {
} }
} }
public void checkUnresolvedReferences() {
if (unresolvedPolyRefIDMap.isEmpty()) {
return;
}
boolean xLinkGlobalError = false;
String emptyXLinkLog = Localization.getText("CityGmlFeatureMapper.emptyCrossFeatureReference");
List<String> crossRefs = new ArrayList<>();
for (Map.Entry<String, List<GmlId>> entry : unresolvedPolyRefIDMap.entrySet()) {
String id = entry.getKey();
boolean emptyCrossFeatureReference = parsedPolygonsIdMap.containsKey(id);
xLinkGlobalError = xLinkGlobalError || emptyCrossFeatureReference;
if (emptyCrossFeatureReference) {
List<GmlId> gmlIds = entry.getValue();
StringJoiner joiner = new StringJoiner(", ");
gmlIds.forEach(gmlId -> joiner.add(gmlId.toString()));
GmlId crossFeatureId = parsedPolygonsIdMap.get(id);
logger.warn(emptyXLinkLog, id, crossFeatureId, joiner);
crossRefs.add(id);
}
}
if (xLinkGlobalError) {
logger.warn(Localization.getText("CityGmlFeatureMapper.crossFeatureReferencesDetected"));
}
crossRefs.forEach(unresolvedPolyRefIDMap::remove);
if(!unresolvedPolyRefIDMap.isEmpty()) {
logger.warn(Localization.getText("CityGmlFeatureMapper.unresolvedReferencesDetected"));
}
}
@Override @Override
public void visit(AbstractSpace space) { public void visit(AbstractSpace space) {
// if we are here, an AbstractSpace thing was read that is not handled in the // if we are here, an AbstractSpace thing was read that is not handled in the
...@@ -903,8 +939,9 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -903,8 +939,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void finishCityObjectConstruction(CityObject co) { private void finishCityObjectConstruction(CityObject co) {
parseId(co.getGmlObject(), co); parseId(co.getGmlObject(), co);
co.clearGmlGeometries(); co.clearGmlGeometries();
resolveAndClearReferences(); resolveAndClearReferences(co);
updateEdgesAndVertices(co); updateEdgesAndVertices(co);
co.setBbox(BoundingBox.of(co));
} }
private void mapAbstractTransportationSpace(AbstractTransportationSpace ats, TransportationSpace trsp) { private void mapAbstractTransportationSpace(AbstractTransportationSpace ats, TransportationSpace trsp) {
...@@ -1092,7 +1129,7 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1092,7 +1129,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
return igh; return igh;
} }
private void resolveAndClearReferences() { private void resolveAndClearReferences(CityObject co) {
for (ResolvableReference ref : references) { for (ResolvableReference ref : references) {
String href = ref.href(); String href = ref.href();
if (href.startsWith("#")) { if (href.startsWith("#")) {
...@@ -1107,6 +1144,9 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1107,6 +1144,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
handlePolygonReference(href, geom); handlePolygonReference(href, geom);
} }
} }
for(String polygonId: polygonMap.keySet()){
parsedPolygonsIdMap.put(polygonId, co.getGmlId());
}
// clear storage for polygons and vertices // clear storage for polygons and vertices
references = new ArrayList<>(); references = new ArrayList<>();
...@@ -1117,9 +1157,12 @@ public class Citygml3FeatureMapper extends ObjectWalker { ...@@ -1117,9 +1157,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void handlePolygonReference(String href, Geometry geom) { private void handlePolygonReference(String href, Geometry geom) {
ConcretePolygon concPoly = polygonMap.get(href); ConcretePolygon concPoly = polygonMap.get(href);
if (concPoly == null) { if (concPoly == null) {
if (logger.isWarnEnabled()) { List<GmlId> featureList = unresolvedPolyRefIDMap.computeIfAbsent(href, k -> new ArrayList<>());
logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href); GmlId featureId = geom.getParent().getTopLevelCityObject().getGmlId();
} featureList.add(featureId);
// if (logger.isWarnEnabled()) {
// logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
// }
} else { } else {
LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom); LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom);
if (geom.getParent() instanceof BoundarySurface bs) { if (geom.getParent() instanceof BoundarySurface bs) {
......
...@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException; ...@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException;
public class GMLValidationHandler implements ErrorHandler { public class GMLValidationHandler implements ErrorHandler {
private static final int MAX_LINE_SIZE = 200;
private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class); private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class);
private final List<String> messages = new ArrayList<>(); private final List<String> messages = new ArrayList<>();
@Override @Override
public void error(SAXParseException exception) { public void error(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.error(message); logger.error(message);
messages.add(message); messages.add(message);
} }
private String constructMessage(SAXParseException exception) {
String exceptionMessage = exception.getMessage();
String message = "[line: " + exception.getLineNumber() + "] "
+ exceptionMessage.substring(0, Math.min(exceptionMessage.length(), MAX_LINE_SIZE));
if (exceptionMessage.length() > MAX_LINE_SIZE) {
message += "...";
}
return message;
}
@Override @Override
public void warning(SAXParseException exception) { public void warning(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.warn(message); logger.warn(message);
messages.add(message); messages.add(message);
} }
@Override @Override
public void fatalError(SAXParseException exception) { public void fatalError(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] " String message = constructMessage(exception);
+ exception.getMessage();
logger.fatal(message); logger.fatal(message);
messages.add(message); messages.add(message);
} }
......
...@@ -34,8 +34,6 @@ public class Segment3d implements Serializable { ...@@ -34,8 +34,6 @@ public class Segment3d implements Serializable {
private static final double PRECISION = 0.00000001; private static final double PRECISION = 0.00000001;
private static final double EPSILON = 0.01;
private final Vector3d pointA; private final Vector3d pointA;
private final Vector3d pointB; private final Vector3d pointB;
...@@ -178,7 +176,7 @@ public class Segment3d implements Serializable { ...@@ -178,7 +176,7 @@ public class Segment3d implements Serializable {
return "Segment3d [pointA=" + pointA + ", pointB=" + pointB + "]"; return "Segment3d [pointA=" + pointA + ", pointB=" + pointB + "]";
} }
public Vector3d intersection(Triangle3d triangle) { public Vector3d intersection(Triangle3d triangle, double eps) {
Vector3d v0 = triangle.getP1(); Vector3d v0 = triangle.getP1();
Vector3d v1 = triangle.getP2(); Vector3d v1 = triangle.getP2();
Vector3d v2 = triangle.getP3(); Vector3d v2 = triangle.getP3();
...@@ -188,7 +186,7 @@ public class Segment3d implements Serializable { ...@@ -188,7 +186,7 @@ public class Segment3d implements Serializable {
Vector3d p = direction.cross(edge2); Vector3d p = direction.cross(edge2);
double det = edge1.dot(p); double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) { if (det > -eps && det < eps) {
return null; return null;
} }
...@@ -197,7 +195,7 @@ public class Segment3d implements Serializable { ...@@ -197,7 +195,7 @@ public class Segment3d implements Serializable {
Vector3d s = pointA.minus(v0); Vector3d s = pointA.minus(v0);
double u = invDet * s.dot(p); double u = invDet * s.dot(p);
if (u < EPSILON || u > (1.0 - EPSILON)) { if (u < eps || u > (1.0 - eps)) {
return null; return null;
} }
...@@ -210,7 +208,7 @@ public class Segment3d implements Serializable { ...@@ -210,7 +208,7 @@ public class Segment3d implements Serializable {
} }
double t = edge2.dot(q) * invDet; double t = edge2.dot(q) * invDet;
if (t > EPSILON && t < 1 - EPSILON) { if (t > eps && t < 1 - eps) {
// t is in segment // t is in segment
return pointA.plus(direction.mult(t)); return pointA.plus(direction.mult(t));
} }
......
...@@ -35,6 +35,8 @@ public class Triangle3d implements Serializable { ...@@ -35,6 +35,8 @@ public class Triangle3d implements Serializable {
private static final long serialVersionUID = -6907333357794272435L; private static final long serialVersionUID = -6907333357794272435L;
private static final double EPSILON = 0.0001; private static final double EPSILON = 0.0001;
private static final double PLANAR_EPSILON = 0.0000001;
private final Vector3d p1; private final Vector3d p1;
private final Vector3d p2; private final Vector3d p2;
private final Vector3d p3; private final Vector3d p3;
...@@ -52,6 +54,53 @@ public class Triangle3d implements Serializable { ...@@ -52,6 +54,53 @@ public class Triangle3d implements Serializable {
this.partOf = partOf; this.partOf = partOf;
} }
public boolean hasMinExtent(double minExtent) {
Vector3d ab = p2.minus(p1);
Vector3d ac = p3.minus(p1);
Vector3d bc = p3.minus(p2);
// Find the longest edge to use as primary axis
double lenAB2 = ab.getSquaredLength();
double lenAC2 = ac.getSquaredLength();
double lenBC2 = bc.getSquaredLength();
Vector3d axisX;
if (lenAB2 >= lenAC2 && lenAB2 >= lenBC2) {
axisX = ab;
} else if (lenAC2 >= lenBC2) {
axisX = ac;
} else {
axisX = bc;
}
axisX = axisX.normalize();
// Pick any vector not parallel to axisX for in-plane Y axis
Vector3d axisY = pickPerpendicular(axisX);
// Project vertices onto axes
double minX = Math.min(p1.dot(axisX), Math.min(p2.dot(axisX), p3.dot(axisX)));
double maxX = Math.max(p1.dot(axisX), Math.max(p2.dot(axisX), p3.dot(axisX)));
double minY = Math.min(p1.dot(axisY), Math.min(p2.dot(axisY), p3.dot(axisY)));
double maxY = Math.max(p1.dot(axisY), Math.max(p2.dot(axisY), p3.dot(axisY)));
double extentX = maxX - minX;
double extentY = maxY - minY;
return extentX > minExtent && extentY > minExtent;
}
private Vector3d pickPerpendicular(Vector3d v) {
// choose smallest component to avoid near-zero cross
if (Math.abs(v.getX()) < Math.abs(v.getY()) && Math.abs(v.getX()) < Math.abs(v.getZ())) {
return new Vector3d(0, -v.getZ(), v.getY()).normalize();
}
if (Math.abs(v.getY()) < Math.abs(v.getZ())) {
return new Vector3d(-v.getZ(), 0, v.getX()).normalize();
}
return new Vector3d(-v.getY(), v.getX(), 0).normalize();
}
public TesselatedPolygon getPartOf() { public TesselatedPolygon getPartOf() {
return partOf; return partOf;
} }
...@@ -101,13 +150,13 @@ public class Triangle3d implements Serializable { ...@@ -101,13 +150,13 @@ public class Triangle3d implements Serializable {
double distanceP2T2 = planeT2.getSignedDistance(p2); double distanceP2T2 = planeT2.getSignedDistance(p2);
double distanceP3T2 = planeT2.getSignedDistance(p3); double distanceP3T2 = planeT2.getSignedDistance(p3);
if (Math.abs(distanceP1T2) < epsilon) { if (Math.abs(distanceP1T2) < PLANAR_EPSILON) {
distanceP1T2 = 0.0; distanceP1T2 = 0.0;
} }
if (Math.abs(distanceP2T2) < epsilon) { if (Math.abs(distanceP2T2) < PLANAR_EPSILON) {
distanceP2T2 = 0.0; distanceP2T2 = 0.0;
} }
if (Math.abs(distanceP3T2) < epsilon) { if (Math.abs(distanceP3T2) < PLANAR_EPSILON) {
distanceP3T2 = 0.0; distanceP3T2 = 0.0;
} }
...@@ -144,20 +193,18 @@ public class Triangle3d implements Serializable { ...@@ -144,20 +193,18 @@ public class Triangle3d implements Serializable {
return false; return false;
} }
boolean intersects = checkTriangleLineIntersection(other, p1, p2) || checkTriangleLineIntersection(other, p1, p3) boolean intersects = checkTriangleLineIntersection(other, p1, p2, epsilon)
|| checkTriangleLineIntersection(other, p2, p3) || checkTriangleLineIntersection(other, p1, p3, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p2) || checkTriangleLineIntersection(other, p2, p3, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p3) || checkTriangleLineIntersection(this, other.p1, other.p2, epsilon)
|| checkTriangleLineIntersection(this, other.p2, other.p3); || checkTriangleLineIntersection(this, other.p1, other.p3, epsilon)
if (intersects) { || checkTriangleLineIntersection(this, other.p2, other.p3, epsilon);
System.out.println();
}
return intersects; return intersects;
} }
private boolean checkTriangleLineIntersection(Triangle3d other, Vector3d a, Vector3d b) { private boolean checkTriangleLineIntersection(Triangle3d other, Vector3d a, Vector3d b, double eps) {
Segment3d seg = new Segment3d(a, b); Segment3d seg = new Segment3d(a, b);
Vector3d intersection1 = seg.intersection(other); Vector3d intersection1 = seg.intersection(other, eps);
return intersection1 != null; return intersection1 != null;
} }
...@@ -274,5 +321,4 @@ public class Triangle3d implements Serializable { ...@@ -274,5 +321,4 @@ public class Triangle3d implements Serializable {
public void setPartOf(TesselatedPolygon p) { public void setPartOf(TesselatedPolygon p) {
partOf = p; partOf = p;
} }
} }
...@@ -49,6 +49,7 @@ public class Vector3d implements Serializable { ...@@ -49,6 +49,7 @@ public class Vector3d implements Serializable {
/** /**
* Convert JTS Coordinate class to Vector3d. * Convert JTS Coordinate class to Vector3d.
*
* @param coord JTS Coordinate * @param coord JTS Coordinate
*/ */
public Vector3d(Coordinate coord) { public Vector3d(Coordinate coord) {
...@@ -210,7 +211,8 @@ public class Vector3d implements Serializable { ...@@ -210,7 +211,8 @@ public class Vector3d implements Serializable {
} }
/** /**
* normalizes this vector. This method changes the coordinates of this instance. * returns a normalized vector in the same direction as this one. This method
* does not change the coordinates of this instance.
*/ */
public UnitVector3d normalize() { public UnitVector3d normalize() {
return UnitVector3d.of(this); return UnitVector3d.of(this);
...@@ -253,9 +255,9 @@ public class Vector3d implements Serializable { ...@@ -253,9 +255,9 @@ public class Vector3d implements Serializable {
@Override @Override
public String toString() { public String toString() {
final int maxLen = 5; final int maxLen = 5;
return "Vector3d [coords=" + return "Vector3d [coords="
(coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null) + + (coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null)
"]"; + "]";
} }
@Override @Override
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment