Commit 6d0d664e authored by Matthias Betz's avatar Matthias Betz
Browse files

Merge remote-tracking branch 'remotes/origin/dev' into dev_baris_aabb_auto_mergeable

parents a7ed4b64 cc4fe260
Pipeline #12443 passed with stage
in 2 minutes and 42 seconds
......@@ -13,4 +13,18 @@
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="false" name="CityDoctorGUIStarter" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="de.hft.stuttgart.citydoctor2.gui.CityDoctorGUIStarter" />
<module name="CityDoctorGUI" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/CityDoctorParent/Extensions/CityDoctorGUI" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.hft.stuttgart.citydoctor2.gui.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
......@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- Replaced the JavaFX-based 3D view with an OpenGL renderer (openglfx/JOGL) for smooth display of
large city models. Picking is now GPU-based; the 3D view requires OpenGL 3.3 (Windows/Linux).
## [3.18.3] (2026-04-16)
### Fixes
- Fixed FragmentedSurface errors reporting roof BoundarySurfaces as ground BoundarySurfaces
- Fixed degenerated triangles causing false-positive detection of AllPolygonsWrongOrientation errors
- Fixed -db_location and -db_settings arguments not being parsed when starting the GUI from the CLI
## [3.18.2] (2026-02-24)
### Added
- CLI parameter "-db_location" for changing the location of the database-file of the embedded database. This parameter will
override the location settings of the DB-settings file.
- CLI parameter "-db_settings" for giving a path to a DB-settings .properties file.
- Added -db_location pointing to the system's temp directory to the start scripts for the binaries
- Added buttons for (un)checking all Geometric or Semantic requirements
- Added a Hyperlink to CityDoctor's homepage with detailed explanations for each requirement
- The GUI will now show a loading spinner while waiting for Schematron to finish.
### Fixes
- Connected CityDoctorHealer to the embedded database
- Various small fixes and improvements to performance
## [3.18.1] (2025-12-10)
### Hotfix
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.3</version>
</parent>
<artifactId>CityDoctorCheckResult</artifactId>
<dependencies>
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.3</version>
</parent>
<artifactId>CityDoctorEdge</artifactId>
<dependencies>
......
......@@ -41,7 +41,7 @@ public class IntersectionErrorsTest {
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD1, Orientation.OUTWARD);
ConcretePolygon p1 = new ConcretePolygon();
geom.addPolygon(p1);
LinearRing ext1 = new LinearRing(LinearRingType.EXTERIOR);
p1.setExteriorRing(ext1);
......@@ -66,6 +66,7 @@ public class IntersectionErrorsTest {
ext1.addVertex(v1);
ConcretePolygon p2 = new ConcretePolygon();
geom.addPolygon(p2);
LinearRing ext2 = new LinearRing(LinearRingType.EXTERIOR);
p2.setExteriorRing(ext2);
......@@ -88,8 +89,8 @@ public class IntersectionErrorsTest {
ext2.addVertex(v8);
ext2.addVertex(v5);
geom.addPolygon(p1);
geom.addPolygon(p2);
geom.prepareForChecking();
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.3</version>
</parent>
<properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString>
......
......@@ -37,7 +37,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.check.error.SolidError;
import de.hft.stuttgart.citydoctor2.check.error.SolidNotClosedError;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceFragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
......@@ -181,7 +181,7 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor {
}
@Override
public void visit(SurfaceUnfragmentedError err) {
public void visit(SurfaceFragmentedError err) {
}
@Override
......
......@@ -345,7 +345,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* @param config sometimes there are global parameters which can be used by
* checks. Those are be stored in this container
*/
public void init(Map<String, String> params, ParserConfiguration config) {
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
}
......
......@@ -55,7 +55,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.check.error.SolidError;
import de.hft.stuttgart.citydoctor2.check.error.SolidNotClosedError;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceFragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
......@@ -135,7 +135,7 @@ public interface ErrorVisitor {
public void visit(SchematronError err);
public void visit(SurfaceUnfragmentedError err);
public void visit(SurfaceFragmentedError err);
public void visit(DegeneratedRingError err);
......
......@@ -48,7 +48,7 @@ import de.hft.stuttgart.citydoctor2.check.error.RingTooFewPointsError;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.check.error.SolidNotClosedError;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceFragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
......@@ -189,7 +189,7 @@ public interface HealingMethod {
return false;
}
default boolean visit(SurfaceUnfragmentedError err, ModificationListener l) {
default boolean visit(SurfaceFragmentedError err, ModificationListener l) {
return false;
}
......
......@@ -55,7 +55,7 @@ import de.hft.stuttgart.citydoctor2.check.error.RingTooFewPointsError;
import de.hft.stuttgart.citydoctor2.check.error.SchematronError;
import de.hft.stuttgart.citydoctor2.check.error.SolidNotClosedError;
import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceFragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
......@@ -367,7 +367,7 @@ public class QualityAdeErrorVisitor extends AbstractErrorVisitor {
}
@Override
public void visit(SurfaceUnfragmentedError err) {
public void visit(SurfaceFragmentedError err) {
// not translated
}
......
......@@ -63,7 +63,7 @@ public class Requirement implements Serializable {
public static final Requirement R_GE_R_NULL_AREA = new Requirement("R_GE_R_NULL_AREA", RequirementType.GEOMETRY);
public static final Requirement R_SE_BS_GROUND_UNFRAGMENTED = new Requirement("R_SE_BS_GROUND_UNFRAGMENTED", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_ROOF_UNFRAGMENTED = new Requirement("R_SE_BS_GROUND_UNFRAGMENTED", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_ROOF_UNFRAGMENTED = new Requirement("R_SE_BS_ROOF_UNFRAGMENTED", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_CEILING = new Requirement("R_SE_BS_IS_CEILING", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_FLOOR = new Requirement("R_SE_BS_IS_FLOOR", RequirementType.SEMANTIC);
public static final Requirement R_SE_BS_IS_WALL = new Requirement("R_SE_BS_IS_WALL", RequirementType.SEMANTIC);
......@@ -83,6 +83,7 @@ public class Requirement implements Serializable {
defaultParameters.add(new DefaultParameter(UPPER_ANGLE_NAME, "135", Unit.DEGREE));
R_SE_BS_IS_WALL.parameters = Collections.unmodifiableList(defaultParameters);
defaultParameters = new ArrayList<>();
defaultParameters.add(new DefaultParameter(MAX_ANGLE_DEVIATION, "1", Unit.DEGREE));
R_SE_BS_ROOF_UNFRAGMENTED.parameters = Collections.unmodifiableList(defaultParameters);
......
......@@ -36,7 +36,7 @@ import java.io.Serial;
* @author Matthias Betz
*
*/
public class SurfaceUnfragmentedError implements CheckError {
public class SurfaceFragmentedError implements CheckError {
@Serial
private static final long serialVersionUID = 3146243879393474196L;
......@@ -44,7 +44,7 @@ public class SurfaceUnfragmentedError implements CheckError {
private final BoundarySurface bs;
private final double angleDeviation;
public SurfaceUnfragmentedError(BoundarySurface bs, double angleDerivation) {
public SurfaceFragmentedError(BoundarySurface bs, double angleDerivation) {
this.bs = bs;
this.angleDeviation = angleDerivation;
}
......
......@@ -3,6 +3,7 @@ package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
......@@ -21,6 +22,8 @@ public interface CityObjectCache {
*/
CityObject get(GmlId id);
Collection<CityObject> getAll(List<GmlId> ids);
/**
* Replaces a CityObject with another one
* @param id GmlID of the CityObject that is to be replaced
......
package de.hft.stuttgart.citydoctor2.database;
import com.github.benmanes.caffeine.cache.CacheLoader;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import org.jspecify.annotations.NonNull;
import java.util.Map;
import java.util.Set;
public class DatabaseCacheLoader<K, V> implements CacheLoader<K, V> {
private final Unmarshaller<K, V> unmarshaller;
private final BatchUnmarshaller<K, V> batchUnmarshaller;
public DatabaseCacheLoader(Unmarshaller<K,V> unmarshaller, BatchUnmarshaller<K, V> batchUnmarshaller){
this.unmarshaller = unmarshaller;
this.batchUnmarshaller = batchUnmarshaller;
}
@Override
public @NonNull V load(@NonNull K key) throws EmbeddedDatabaseHandlerException {
return unmarshaller.unmarshall(key);
}
@Override
@NonNull
public Map<K, V> loadAll(@NonNull Set<? extends K> keys) throws EmbeddedDatabaseHandlerException {
return batchUnmarshaller.unmarshall(keys);
}
public interface Unmarshaller<K, V> {
V unmarshall(K key) throws EmbeddedDatabaseHandlerException;
}
public interface BatchUnmarshaller<K, V> {
Map<K, V> unmarshall(Set<? extends K> keys) throws EmbeddedDatabaseHandlerException;
}
}
package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
public final class DatabaseSettings {
private static final Logger logger = LogManager.getLogger(DatabaseSettings.class);
private static final Properties props = new Properties();
private DatabaseSettings() {}
static {
File propFile = new File("DBSettings.properties");
if (propFile.exists()) {
loadPropertiesFromFile(propFile);
}
}
public static void loadPropertiesFromFile(File file) throws EmbeddedDatabaseHandlerException {
try (BufferedReader bis = new BufferedReader(new FileReader(file))) {
props.load(bis);
} catch (IOException e) {
throw new EmbeddedDatabaseHandlerException(e);
}
}
public static void setDBLocation(String dbLocation) throws EmbeddedDatabaseHandlerException{
Path dbPath = Paths.get(dbLocation).toAbsolutePath();
File dbLocationFile = dbPath.toFile();
if (!dbLocationFile.isFile()) {
dbPath = dbPath.resolve("cd_db");
}
Path dbParentDirectoryPath = dbPath.getParent();
if (dbParentDirectoryPath.toFile().mkdirs()){
logger.trace("Created parent directories for database location");
}
if (!dbParentDirectoryPath.toFile().canWrite()) {
throw new EmbeddedDatabaseHandlerException("Missing write permissions for database location:" + dbLocation);
}
props.setProperty("database.name", dbPath.getFileName().toString());
props.setProperty("database.directory", dbParentDirectoryPath.toAbsolutePath().toString());
}
public static EmbeddedDatabaseConfiguration getConfig() {
EmbeddedDatabaseConfiguration defaultConfig = new EmbeddedDatabaseConfiguration();
if (props.isEmpty()) {
logger.trace("No database settings found or specified, using default config");
return defaultConfig;
}
String name = props.getProperty("database.name");
String directory = props.getProperty("database.directory");
String poolSizeString = props.getProperty("database.connectionPoolSize");
String inMemoryModeString = props.getProperty("database.inMemoryMode");
String attemptFallbackString = props.getProperty("database.attemptFallback");
String debugModeString = props.getProperty("database.debugMode");
String tempModeString = props.getProperty("database.tempMode");
boolean inMemoryMode = Boolean.parseBoolean(inMemoryModeString);
boolean attemptFallback = attemptFallbackString == null || Boolean.parseBoolean(attemptFallbackString);
boolean debugMode = Boolean.parseBoolean(debugModeString);
boolean tempMode = Boolean.parseBoolean(tempModeString);
int poolSize = defaultConfig.connectionPoolSize();
if (poolSizeString != null && !poolSizeString.isBlank()){
try {
int size = Integer.parseInt(poolSizeString);
if (size < 1) {
logger.warn(Localization.getText("DatabaseSettings.poolSizeLessThanOne"));
} else poolSize = size;
} catch (NumberFormatException e) {
logger.warn(Localization.getText("DatabaseSettings.poolSizeNaN"));
}
}
if (name == null || name.isBlank()){
name = defaultConfig.databaseName();
}
if (directory == null || directory.isBlank()){
directory = defaultConfig.databaseDirectory();
}
try {
Paths.get(directory, name);
} catch (InvalidPathException e) {
if (!inMemoryMode) {
// Only warn if in-memory mode is not being used
logger.warn(Localization.getText("DatabaseSettings.invalidPath"));
name = defaultConfig.databaseName();
directory = defaultConfig.databaseDirectory();
}
}
return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode, tempMode);
}
}
package de.hft.stuttgart.citydoctor2.database;
import java.io.File;
import java.nio.file.Paths;
import java.util.StringJoiner;
/**
* Record containing the configuration parameters for the setup of the embedded database
* @param databaseName Name of the database
* @param databaseDirectory Directory of the database. Relative paths will be resolved from the current working directory.
* CityDoctor will create the directory if it does not exist, and will overwrite an existing
* database-file if its name matches databaseName
* @param connectionPoolSize Size of the connection pool
* @param inMemoryMode If true, CityDoctor will create the embedded database in RAM
* @param attemptFallback If true, CityDoctor will attempt to create the embedded database in RAM if creation of the
* database-file fails. Will be ignored if inMemoryMode is true
* @param debugMode If true, CityDoctor will start the embedded database in automatic mixed mode to allow access to the
* database during runtime.
* @param tempFileMode If true, CityDoctor will create the database in the system's temp directory. Will be ignored if
* inMemoryMode is true
*/
public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize,
boolean inMemoryMode, boolean attemptFallback, boolean debugMode, boolean tempFileMode) {
private static final String IN_MEMORY_PREFIX = "mem:";
private static final String JDBC_DRIVER_PREFIX = "jdbc:h2:";
private static final String AUTO_SERVER_PARAMETER = "AUTO_SERVER=TRUE";
private static final String KEEP_ALIVE_PARAMETER = "DB_CLOSE_DELAY=-1";
/**
* Instantiates the default configuration for the embedded database.
*/
EmbeddedDatabaseConfiguration() {
this("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(),
false, true, false, false);
}
public static EmbeddedDatabaseConfiguration getTestConfig() {
return new EmbeddedDatabaseConfiguration("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(),
true, true, true, false);
}
public String wrapUrlWithH2JdbcDriver(String url){
return JDBC_DRIVER_PREFIX + url + getH2ParametersSuffix();
}
public String getInMemoryH2Url() {
return wrapUrlWithH2JdbcDriver(IN_MEMORY_PREFIX + databaseName);
}
public String getFallbackH2Url(){
return getInMemoryH2Url() + ";" + KEEP_ALIVE_PARAMETER;
}
public String getH2FileUrl(){
return wrapUrlWithH2JdbcDriver(Paths.get(databaseDirectory).resolve(databaseName).toString());
}
public String getH2ParametersSuffix(){
StringJoiner joiner = new StringJoiner(";",";","");
if (debugMode){
joiner.add(AUTO_SERVER_PARAMETER);
}
if (inMemoryMode){
joiner.add(KEEP_ALIVE_PARAMETER);
}
return joiner.toString();
}
}
......@@ -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();
logger.debug("1] {}",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);
dataSource = new DataSourceWrapper(ds);
setupFeaturesTable();
//Cleanup hook
Runtime.getRuntime().addShutdownHook(new Thread(ds::close));
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);
} 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");
}
}
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 {
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");
}
//CREATE SPATIAL INDEX GEO_TABLE_SPATIAL_INDEX ON GEO_TABLE(THE_GEOM); <- Create spatial id
logger.trace("Created features table");
}
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)){
handler.marshallCityObject(value);
}
}).build(handler::unmarshallCityObject);
if (cause.wasEvicted() && isFeatureMarshallable(value)){
handler.marshallCityObject(value);
}
}).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);
......
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