"ampel-firmware/config.public.h" did not exist on "388456765faff2589f882931704a884a219224aa"
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;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.h2gis.functions.factory.H2GISDBFactory;
import org.h2gis.utilities.wrapper.DataSourceWrapper;
import org.h2gis.functions.factory.H2GISFunctions;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Utility class for setup and access of the embedded database.
......@@ -34,34 +43,71 @@ import java.util.Map;
public class EmbeddedDatabaseHandler {
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 File tempFileDBLocation = null;
public EmbeddedDatabaseHandler(){
public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){
config = dbConfig;
String jdbcUrl;
try {
String jdbcurl = H2GISDBFactory.createDataSource(DB_NAME,
true, ";").getConnection().getMetaData().getURL();
if (config.inMemoryMode()) {
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);
HikariDataSource ds = new HikariDataSource();
} catch (Exception e) {
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.setPassword("sa");
int coreCount = Runtime.getRuntime().availableProcessors();
ds.setMaximumPoolSize(coreCount*5);
ds.setMinimumIdle(coreCount*5);
ds.setMaximumPoolSize(config.connectionPoolSize());
dataSource = new DataSourceWrapper(ds);
setupFeaturesTable();
//Cleanup hook
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 {
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement dropPs = con.prepareStatement("DROP TABLE IF EXISTS features")){
dropPs.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("Dropped existing features table");
logger.trace("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)" +
" PRIMARY KEY, bbox GEOMETRY, data BLOB, errors BOOLEAN);")) {
createPs.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("Created features table");
logger.trace("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) {
logger.fatal(Localization.getText("DatabaseHandler.tableFailure"));
logger.fatal(e.getMessage());
throw new EmbeddedDatabaseHandlerException("Could not setup features table in database", e);
}
}
......@@ -96,8 +146,7 @@ public class EmbeddedDatabaseHandler {
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("MERGE INTO features VALUES (?, ?, ?, ?)")) {
ps.setString(1, co.getGmlId().toString());
String bbox = BoundingBox.of(co).to2DWkt();
ps.setString(2, bbox);
ps.setString(2, co.getBbox().to2DWkt());
ps.setBoolean(4, co.containsAnyError());
if (logger.isDebugEnabled()) {
......@@ -198,7 +247,7 @@ public class EmbeddedDatabaseHandler {
}
}
} catch (IOException e) {
throw new RuntimeException(e);
throw new EmbeddedDatabaseHandlerException(e);
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.unmarshallingFailure"), id);
......@@ -208,6 +257,47 @@ public class EmbeddedDatabaseHandler {
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}.
* @return a List of GmlIds of CityObjects containing any CheckError
......
......@@ -8,7 +8,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
......@@ -42,12 +44,18 @@ public class FeatureCache implements CityObjectCache{
private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>();
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) -> {
if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){
if (cause.wasEvicted() && isFeatureMarshallable(value)){
handler.marshallCityObject(value);
}
}).build(handler::unmarshallCityObject);
}).build(new DatabaseCacheLoader<>(single, batch));
}
/**
......@@ -111,6 +119,11 @@ public class FeatureCache implements CityObjectCache{
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
......
......@@ -5,6 +5,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -37,6 +38,12 @@ public class UnconnectedCache implements CityObjectCache{
return cache.get(id);
}
@Override
public Collection<CityObject> getAll(List<GmlId> ids) {
return ids.stream().map(cache::get).toList();
}
@Override
public void replace(GmlId id, CityObject cityObject) {
cache.put(id, cityObject);
......
......@@ -22,6 +22,8 @@ import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.visitors.MinMaxExtentVisitor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
......@@ -31,7 +33,10 @@ import java.util.Locale;
*
* @author Matthias Betz
*/
public class BoundingBox {
public class BoundingBox implements Serializable {
@Serial
private static final long serialVersionUID = 1998769586245522486L;
private final Vector3d[] bbox;
......
......@@ -18,13 +18,10 @@
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
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 java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import org.citygml4j.core.model.deprecated.bridge.DeprecatedPropertiesOfBridgeConstructiveElement;
import org.citygml4j.core.util.geometry.GeometryFactory;
import org.xmlobjects.gml.model.geometry.GeometryProperty;
......@@ -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.SolidProperty;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import javafx.scene.paint.Color;
public class BridgeConstructiveElement extends CityObject {
private static final Logger logger = LogManager.getLogger(BridgeConstructiveElement.class);
private static final String CANNOT_ADD = "Cannot add ";
@Serial
......@@ -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) {
switch (geom.getLod()) {
case LOD1:
......
......@@ -43,7 +43,7 @@ public abstract class CityObject extends GmlElement {
@Serial
private static final long serialVersionUID = 651712070755024188L;
private BoundingBox bbox;
private final List<Geometry> geometryList = new ArrayList<>();
private final List<GenericAttribute> genericAttributeList = new ArrayList<>();
......@@ -88,6 +88,17 @@ public abstract class CityObject extends GmlElement {
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.
* @param geom the Geometry to add.
......
......@@ -9,6 +9,8 @@ import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Supplier;
public class GenericAttribute implements Serializable {
......@@ -17,6 +19,7 @@ public class GenericAttribute implements Serializable {
private static final long serialVersionUID = -4101408947632132328L;
private static final Logger logger = LogManager.getLogger(GenericAttribute.class);
private static final String NULL_VALUE_STRING= "NULL OR INVALID VALUE";
private final AbstractGenericAttributeProperty original;
......@@ -24,6 +27,7 @@ public class GenericAttribute implements Serializable {
private String name;
private String value;
public GenericAttribute(AbstractGenericAttributeProperty attributeProperty) {
original = attributeProperty;
if (attributeProperty.getObject() == null) {
......@@ -34,40 +38,42 @@ public class GenericAttribute implements Serializable {
if (attributeProperty.getObject() instanceof StringAttribute sa) {
type = "StringAttribute";
value = String.format("\"%s\"", sa.getValue());
value = extractValue(() -> String.format("\"%s\"", sa.getValue()));
} else if (attributeProperty.getObject() instanceof IntAttribute ia) {
type = "IntAttribute";
value = ia.getValue().toString();
value = extractValue(() -> ia.getValue().toString());
} else if (attributeProperty.getObject() instanceof DoubleAttribute da) {
type = "DoubleAttribute";
value = da.getValue().toString();
value = extractValue(() -> da.getValue().toString());
} else if (attributeProperty.getObject() instanceof DateAttribute date) {
type = "DateAttribute";
value = date.getValue().toString();
value = extractValue(() -> date.getValue().toString());
} else if (attributeProperty.getObject() instanceof UriAttribute ua) {
type = "UriAttribute";
value = ua.getValue();
value = extractValue(ua::getValue);
} else if (attributeProperty.getObject() instanceof MeasureAttribute ma) {
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) {
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) {
type = "GenericAttributeSet";
List<AbstractGenericAttributeProperty> attributes = gas.getValue();
StringBuilder sb = new StringBuilder();
sb.append("{");
if(attributes == null || attributes.isEmpty()){
value = NULL_VALUE_STRING;
return;
}
StringJoiner sj = new StringJoiner("; ","{","}");
sj.setEmptyValue("EMPTY_SET");
for (AbstractGenericAttributeProperty attribute : attributes) {
if (attribute.getObject() != null) {
sb.append(attribute.getObject().getName());
sb.append(" = ");
sb.append(attribute.getObject().getValue());
sb.append("; ");
sj.add(String.format("%1$s = %2$s",attribute.getObject().getName(), attribute.getObject().getValue()));
}
}
sb.replace(sb.length() - 1, sb.length(), "}");
value = String.format("'%s'", sb);
value = sj.toString();
} else {
logger.warn("GenericAttribute {} is of unknown type {}", attributeProperty, attributeProperty.getObject());
......@@ -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() {
return original;
}
......
......@@ -18,6 +18,16 @@
*/
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.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
......@@ -26,11 +36,6 @@ import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
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
*
......@@ -55,9 +60,11 @@ public class Geometry extends GmlElement {
private CityObject parent;
private final List<Polygon> polygons = new ArrayList<>(2);
private transient List<Edge> edges;
private transient Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap;
private transient List<Vertex> vertices;
private List<Edge> edges;
private Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap;
private List<Vertex> vertices;
private Map<Vertex, Set<LinearRing>> adjacentRingsOfVertices = new HashMap<>();
private Orientation orientation;
public Geometry(GeometryType type, Lod lod, Orientation orientation) {
......@@ -69,6 +76,15 @@ public class Geometry extends GmlElement {
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() {
return orientation;
}
......@@ -346,18 +362,9 @@ public class Geometry extends GmlElement {
return false;
}
@Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
edges = new ArrayList<>();
edgeMap = new HashMap<>();
}
public void clearMetaData() {
if (vertices != null) {
for (Vertex v : vertices) {
v.clearAdjacentRings();
}
adjacentRingsOfVertices = new HashMap<>();
vertices = null;
}
edges = null;
......
......@@ -61,6 +61,7 @@ public class ImplicitGeometryHolder extends Geometry {
if(!deferredRelGeomObjects.isEmpty()) {
logger.info("Resolving deferred relative geometries");
}
Set<GmlId> changedFeatures = new HashSet<>();
for (ImplicitGeometryHolder key : deferredRelGeomObjects.keySet()) {
ImplicitGeometry ig = deferredRelGeomObjects.get(key);
String hrefId = ig.getRelativeGeometry().getHref();
......@@ -72,10 +73,15 @@ public class ImplicitGeometryHolder extends Geometry {
CityObject parent = cache.get(key.getParent().getGmlId());
parent.removeGeometry(igh.getLod(), igh.getType());
parent.addGeometry(igh);
changedFeatures.add(parent.getTopLevelCityObject().getGmlId());
} else {
missingGeoms.add(gmlId);
}
}
changedFeatures.forEach(gmlId -> {
CityObject co = cache.get(gmlId);
co.setBbox(BoundingBox.of(co));
});
if (!missingGeoms.isEmpty()) {
logger.warn("RelativeGeometries of the following gmlIDs could not be resolved: {}", missingGeoms);
}
......@@ -113,31 +119,6 @@ public class ImplicitGeometryHolder extends Geometry {
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 {
private static final Logger logger = LogManager.getLogger(LibraryObject.class);
@Serial
private static final long serialVersionUID = -50293435187454911L;
private static boolean metadataLocked = false;
private static final Map<String, LibraryObject> libraryObjects = new ConcurrentHashMap<>();
public static LibraryObject of(Path path, ParserConfiguration config) {
......@@ -79,33 +78,8 @@ public class LibraryObject extends Geometry {
return objects.get(0).getHighestLodGeometry();
}
public static void resetObjectMap() {
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 @@
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
......@@ -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
public CheckResult getCheckResult(Check c) {
return poly.getCheckResult(c);
......@@ -104,8 +153,12 @@ public class LinkedPolygon extends Polygon {
@Override
public void accept(CheckableVisitor c) {
// linked polygons are not checked
setValidated(true);
// LinkedPolygon should ignore Checks, as their referenced ConcretePolygon must be within the same TopLevelFeature.
// 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
......
......@@ -15,7 +15,6 @@ public class RelativeGeometry extends Geometry {
private static final long serialVersionUID = -686112245455298977L;
private static final Map<GmlId, RelativeGeometry> relativeGeometries = new ConcurrentHashMap<>();
private static boolean metadataLocked = false;
public static RelativeGeometry of(GmlId gmlId, Geometry geom) {
if (relativeGeometries.containsKey(gmlId)) {
......@@ -40,27 +39,5 @@ public class RelativeGeometry extends Geometry {
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 @@
*/
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.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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
......@@ -39,7 +35,6 @@ public class Vertex extends Vector3d {
@Serial
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) {
super(x, y, z);
......@@ -49,58 +44,16 @@ public class Vertex extends Vector3d {
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) {
return getAdjacentRingsWithoutNeighbor(geom);
return geom.getAdjacentRingsOfVertex(this);
}
void addAdjacentRing(LinearRing ring, Geometry geom) {
findAdjacentRingsForGeometry(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();
getAdjacentRings(geom).add(ring);
}
public Set<Polygon> getAdjacentPolygons(Geometry geom) {
return getAdjacentPolygonsWithoutNeighbor(geom);
return getAdjacentRings(geom).stream().map(r -> r.getParent()).collect(Collectors.toCollection(HashSet::new));
}
@Override
......@@ -127,21 +80,11 @@ public class Vertex extends Vector3d {
}
public void clearAdjacentRings(Geometry geometry) {
if (adjacentRings == null) {
return;
}
findAdjacentRingsForGeometry(geometry).clear();
getAdjacentRings(geometry).clear();
}
void removeAdjacency(LinearRing lr, Geometry geom) {
findAdjacentRingsForGeometry(geom).remove(lr);
}
/**
* Remove all adjacent rings from this vertex, ignoring geometry association
*/
void clearAdjacentRings() {
adjacentRings = new ArrayList<>(2);
getAdjacentRings(geom).remove(lr);
}
}
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;
import java.util.HashMap;
import java.util.List;
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.UnconnectedCache;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
......@@ -166,16 +169,19 @@ public class Citygml3FeatureMapper extends ObjectWalker {
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 ParserConfiguration config;
private final Path directory;
private final double neighborDistance;
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 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) {
this.config = config;
this.directory = path.getParent();
......@@ -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
public void visit(AbstractSpace space) {
// if we are here, an AbstractSpace thing was read that is not handled in the
......@@ -903,8 +939,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void finishCityObjectConstruction(CityObject co) {
parseId(co.getGmlObject(), co);
co.clearGmlGeometries();
resolveAndClearReferences();
resolveAndClearReferences(co);
updateEdgesAndVertices(co);
co.setBbox(BoundingBox.of(co));
}
private void mapAbstractTransportationSpace(AbstractTransportationSpace ats, TransportationSpace trsp) {
......@@ -1092,7 +1129,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
return igh;
}
private void resolveAndClearReferences() {
private void resolveAndClearReferences(CityObject co) {
for (ResolvableReference ref : references) {
String href = ref.href();
if (href.startsWith("#")) {
......@@ -1107,6 +1144,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
handlePolygonReference(href, geom);
}
}
for(String polygonId: polygonMap.keySet()){
parsedPolygonsIdMap.put(polygonId, co.getGmlId());
}
// clear storage for polygons and vertices
references = new ArrayList<>();
......@@ -1117,9 +1157,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void handlePolygonReference(String href, Geometry geom) {
ConcretePolygon concPoly = polygonMap.get(href);
if (concPoly == null) {
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
}
List<GmlId> featureList = unresolvedPolyRefIDMap.computeIfAbsent(href, k -> new ArrayList<>());
GmlId featureId = geom.getParent().getTopLevelCityObject().getGmlId();
featureList.add(featureId);
// if (logger.isWarnEnabled()) {
// logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
// }
} else {
LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom);
if (geom.getParent() instanceof BoundarySurface bs) {
......
......@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException;
public class GMLValidationHandler implements ErrorHandler {
private static final int MAX_LINE_SIZE = 200;
private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class);
private final List<String> messages = new ArrayList<>();
@Override
public void error(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.error(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
public void warning(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.warn(message);
messages.add(message);
}
@Override
public void fatalError(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.fatal(message);
messages.add(message);
}
......
......@@ -34,8 +34,6 @@ public class Segment3d implements Serializable {
private static final double PRECISION = 0.00000001;
private static final double EPSILON = 0.01;
private final Vector3d pointA;
private final Vector3d pointB;
......@@ -178,7 +176,7 @@ public class Segment3d implements Serializable {
return "Segment3d [pointA=" + pointA + ", pointB=" + pointB + "]";
}
public Vector3d intersection(Triangle3d triangle) {
public Vector3d intersection(Triangle3d triangle, double eps) {
Vector3d v0 = triangle.getP1();
Vector3d v1 = triangle.getP2();
Vector3d v2 = triangle.getP3();
......@@ -188,7 +186,7 @@ public class Segment3d implements Serializable {
Vector3d p = direction.cross(edge2);
double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) {
if (det > -eps && det < eps) {
return null;
}
......@@ -197,7 +195,7 @@ public class Segment3d implements Serializable {
Vector3d s = pointA.minus(v0);
double u = invDet * s.dot(p);
if (u < EPSILON || u > (1.0 - EPSILON)) {
if (u < eps || u > (1.0 - eps)) {
return null;
}
......@@ -210,7 +208,7 @@ public class Segment3d implements Serializable {
}
double t = edge2.dot(q) * invDet;
if (t > EPSILON && t < 1 - EPSILON) {
if (t > eps && t < 1 - eps) {
// t is in segment
return pointA.plus(direction.mult(t));
}
......
......@@ -35,6 +35,8 @@ public class Triangle3d implements Serializable {
private static final long serialVersionUID = -6907333357794272435L;
private static final double EPSILON = 0.0001;
private static final double PLANAR_EPSILON = 0.0000001;
private final Vector3d p1;
private final Vector3d p2;
private final Vector3d p3;
......@@ -52,6 +54,53 @@ public class Triangle3d implements Serializable {
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() {
return partOf;
}
......@@ -101,13 +150,13 @@ public class Triangle3d implements Serializable {
double distanceP2T2 = planeT2.getSignedDistance(p2);
double distanceP3T2 = planeT2.getSignedDistance(p3);
if (Math.abs(distanceP1T2) < epsilon) {
if (Math.abs(distanceP1T2) < PLANAR_EPSILON) {
distanceP1T2 = 0.0;
}
if (Math.abs(distanceP2T2) < epsilon) {
if (Math.abs(distanceP2T2) < PLANAR_EPSILON) {
distanceP2T2 = 0.0;
}
if (Math.abs(distanceP3T2) < epsilon) {
if (Math.abs(distanceP3T2) < PLANAR_EPSILON) {
distanceP3T2 = 0.0;
}
......@@ -144,20 +193,18 @@ public class Triangle3d implements Serializable {
return false;
}
boolean intersects = checkTriangleLineIntersection(other, p1, p2) || checkTriangleLineIntersection(other, p1, p3)
|| checkTriangleLineIntersection(other, p2, p3)
|| checkTriangleLineIntersection(this, other.p1, other.p2)
|| checkTriangleLineIntersection(this, other.p1, other.p3)
|| checkTriangleLineIntersection(this, other.p2, other.p3);
if (intersects) {
System.out.println();
}
boolean intersects = checkTriangleLineIntersection(other, p1, p2, epsilon)
|| checkTriangleLineIntersection(other, p1, p3, epsilon)
|| checkTriangleLineIntersection(other, p2, p3, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p2, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p3, epsilon)
|| checkTriangleLineIntersection(this, other.p2, other.p3, epsilon);
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);
Vector3d intersection1 = seg.intersection(other);
Vector3d intersection1 = seg.intersection(other, eps);
return intersection1 != null;
}
......@@ -274,5 +321,4 @@ public class Triangle3d implements Serializable {
public void setPartOf(TesselatedPolygon p) {
partOf = p;
}
}
......@@ -49,6 +49,7 @@ public class Vector3d implements Serializable {
/**
* Convert JTS Coordinate class to Vector3d.
*
* @param coord JTS Coordinate
*/
public Vector3d(Coordinate coord) {
......@@ -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() {
return UnitVector3d.of(this);
......@@ -253,9 +255,9 @@ public class Vector3d implements Serializable {
@Override
public String toString() {
final int maxLen = 5;
return "Vector3d [coords=" +
(coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null) +
"]";
return "Vector3d [coords="
+ (coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null)
+ "]";
}
@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