"...assets/git@transfer.hft-stuttgart.de:ugl/website.git" did not exist on "fbf692709a6637418bfdc85c6768faaa5238425f"
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()){
logger.debug("1] {}",jdbcurl); Path tmpDir = Files.createTempDirectory(config.databaseDirectory());
HikariDataSource ds = new HikariDataSource(); tmpDir.toFile().deleteOnExit();
String tempFileUrl = tmpDir.resolve(config.databaseName()).toFile().getCanonicalPath();
ds.setJdbcUrl(jdbcurl); jdbcUrl = createEmbeddedDataBase(config.wrapUrlWithH2JdbcDriver(tempFileUrl));
ds.setUsername("sa"); tempFileDBLocation = new File(tempFileUrl+".mv.db");
ds.setPassword("sa"); tempFileDBLocation.deleteOnExit();
int coreCount = Runtime.getRuntime().availableProcessors(); } else {
ds.setMaximumPoolSize(coreCount*5); jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl());
ds.setMinimumIdle(coreCount*5); }
logger.debug("1] {}",jdbcUrl);
dataSource = new DataSourceWrapper(ds);
setupFeaturesTable();
//Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
} catch (Exception e) { } catch (Exception e) {
logger.fatal(Localization.getText("DatabaseHandler.setupFailure")); logger.fatal(Localization.getText("DatabaseHandler.setupFailure"));
logger.fatal(e.getMessage()); 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");
}
} }
setupHikariPool(jdbcUrl);
setupFeaturesTable();
}
private void setupHikariPool(String jdbcUrl){
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(jdbcUrl);
ds.setUsername("sa");
ds.setPassword("sa");
ds.setMaximumPoolSize(config.connectionPoolSize());
dataSource = new DataSourceWrapper(ds);
//Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
}
// 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"); }
} try (PreparedStatement createIndexPs = con.prepareStatement("CREATE SPATIAL INDEX FEATURES_SPATIAL_INDEX ON features(bbox);")){
//CREATE SPATIAL INDEX GEO_TABLE_SPATIAL_INDEX ON GEO_TABLE(THE_GEOM); <- Create spatial id 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;
...@@ -51,7 +53,54 @@ public class Triangle3d implements Serializable { ...@@ -51,7 +53,54 @@ public class Triangle3d implements Serializable {
this.p3 = p3; this.p3 = p3;
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;
} }
} }
...@@ -46,9 +46,10 @@ public class Vector3d implements Serializable { ...@@ -46,9 +46,10 @@ public class Vector3d implements Serializable {
public Vector3d() { public Vector3d() {
this(0d, 0d, 0d); this(0d, 0d, 0d);
} }
/** /**
* 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