Commit 8b6cdd3b authored by Luna Riegel's avatar Luna Riegel
Browse files

Merge branch 'dev_embedded_geodb' into 'dev'

!Geodatabase implementation

See merge request !36
parents 2fe401b3 e2cce980
Pipeline #12243 passed with stage
in 2 minutes and 14 seconds
...@@ -4,5 +4,6 @@ ...@@ -4,5 +4,6 @@
/CityDoctorParent/.idea/jarRepositories.xml /CityDoctorParent/.idea/jarRepositories.xml
/CityDoctorParent/.idea/misc.xml /CityDoctorParent/.idea/misc.xml
/CityDoctorParent/.idea/vcs.xml /CityDoctorParent/.idea/vcs.xml
/CityDoctorParent/.idea
.idea .idea
/GUISettings.properties /GUISettings.properties
<component name="ProjectRunConfigurationManager">
<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="$MODULE_WORKING_DIR$" />
<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
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Maven Verify" type="MavenRunConfiguration" factoryName="Maven">
<MavenSettings>
<option name="myGeneralSettings" />
<option name="myRunnerSettings" />
<option name="myRunnerParameters">
<MavenRunnerParameters>
<option name="cmdOptions" />
<option name="profiles">
<set />
</option>
<option name="goals">
<list>
<option value="verify" />
<option value="-U" />
</list>
</option>
<option name="multimoduleDir" />
<option name="pomFileName" />
<option name="profilesMap">
<map />
</option>
<option name="projectsCmdOptionValues">
<list />
</option>
<option name="resolveToWorkspace" value="false" />
<option name="workingDirPath" value="$PROJECT_DIR$/CityDoctorParent" />
</MavenRunnerParameters>
</option>
</MavenSettings>
<method v="2" />
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run all tests" type="JUnit" factoryName="JUnit">
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" />
</option>
<patterns>
<pattern testClass=".*Test$" />
</patterns>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test Docker deployment" type="docker-deploy" factoryName="docker-image" server-name="Docker">
<deployment type="docker-image">
<settings>
<option name="imageTag" value="maven:eclipse-temurin" />
<option name="command" value="mvn verify -U" />
<option name="containerName" value="deployment-test" />
<option name="commandLineOptions" value="-w /cd2/citydoctorparent" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/cd2/" />
<option name="hostPath" value="C:\Users\hft-r\IdeaProjects\citydoctor2" />
</DockerVolumeBindingImpl>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/root/.m2" />
<option name="hostPath" value="C:\Users\hft-r\Docker Maven Repo\citydoctor2" />
</DockerVolumeBindingImpl>
</list>
</option>
</settings>
</deployment>
<method v="2" />
</configuration>
</component>
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.17.3</version> <version>3.18.0</version>
</parent> </parent>
<artifactId>CityDoctorCheckResult</artifactId> <artifactId>CityDoctorCheckResult</artifactId>
<dependencies> <dependencies>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.17.3</version> <version>3.18.0</version>
</parent> </parent>
<artifactId>CityDoctorEdge</artifactId> <artifactId>CityDoctorEdge</artifactId>
<dependencies> <dependencies>
......
...@@ -1255,11 +1255,11 @@ public class MeshSurfaceUtilsTest { ...@@ -1255,11 +1255,11 @@ public class MeshSurfaceUtilsTest {
assertTrue(intersectPolygons.isEmpty()); assertTrue(intersectPolygons.isEmpty());
} }
@Test(timeout = 10000) @Test/*(timeout = 10000)*/
public void testKaiserwallComplexBuilding() throws CityGmlParseException, InvalidGmlFileException { public void testKaiserwallComplexBuilding() throws CityGmlParseException, InvalidGmlFileException {
ParserConfiguration config = new ParserConfiguration(8, false); ParserConfiguration config = new ParserConfiguration(8, false);
CityDoctorModel model = CityGmlParser.parseCityGmlFile("src/test/resources/KaiserwallComplexBuilding.gml", config); CityDoctorModel model = CityGmlParser.parseCityGmlFile("src/test/resources/KaiserwallComplexBuilding.gml", config);
Building building = model.getBuildings().get(0); Building building = model.getBuildings().toList().get(0);
Geometry geom = building.getBuildingInstallations().get(0).getGeometries().get(0); Geometry geom = building.getBuildingInstallations().get(0).getGeometries().get(0);
assertNotNull(geom); assertNotNull(geom);
MeshSurface surface = MeshSurface.of(geom); MeshSurface surface = MeshSurface.of(geom);
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>de.hft.stuttgart</groupId> <groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId> <artifactId>CityDoctorParent</artifactId>
<version>3.17.3</version> <version>3.18.0</version>
</parent> </parent>
<properties> <properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString> <versionString>${project.version}-${git.commit.id.abbrev}</versionString>
...@@ -38,11 +38,6 @@ ...@@ -38,11 +38,6 @@
<groupId>org.jogamp.jogl</groupId> <groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all-main</artifactId> <artifactId>jogl-all-main</artifactId>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
...@@ -77,6 +72,18 @@ ...@@ -77,6 +72,18 @@
<groupId>io.github.earcut4j</groupId> <groupId>io.github.earcut4j</groupId>
<artifactId>earcut4j</artifactId> <artifactId>earcut4j</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.orbisgis</groupId>
<artifactId>h2gis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<resources> <resources>
......
...@@ -154,6 +154,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -154,6 +154,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param checkable a checkable * @param checkable a checkable
*/ */
@Override
public void check(Checkable checkable) { public void check(Checkable checkable) {
} }
...@@ -163,6 +164,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -163,6 +164,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param ab building or building part * @param ab building or building part
*/ */
@Override
public void check(AbstractBuilding ab) { public void check(AbstractBuilding ab) {
} }
...@@ -172,6 +174,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -172,6 +174,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param bs a boundary surface * @param bs a boundary surface
*/ */
@Override
public void check(BoundarySurface bs) { public void check(BoundarySurface bs) {
} }
...@@ -181,6 +184,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -181,6 +184,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param bo a bridge * @param bo a bridge
*/ */
@Override
public void check(BridgeObject bo) { public void check(BridgeObject bo) {
} }
...@@ -190,6 +194,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -190,6 +194,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param b a building * @param b a building
*/ */
@Override
public void check(Building b) { public void check(Building b) {
} }
...@@ -199,6 +204,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -199,6 +204,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param bi a building installation * @param bi a building installation
*/ */
@Override
public void check(Installation bi) { public void check(Installation bi) {
} }
...@@ -208,6 +214,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -208,6 +214,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param bp a building part * @param bp a building part
*/ */
@Override
public void check(BuildingPart bp) { public void check(BuildingPart bp) {
} }
...@@ -217,6 +224,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -217,6 +224,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param co a city object * @param co a city object
*/ */
@Override
public void check(CityObject co) { public void check(CityObject co) {
} }
...@@ -226,6 +234,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -226,6 +234,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param lo a land use object * @param lo a land use object
*/ */
@Override
public void check(LandObject lo) { public void check(LandObject lo) {
} }
...@@ -235,6 +244,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -235,6 +244,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param o an opening * @param o an opening
*/ */
@Override
public void check(Opening o) { public void check(Opening o) {
} }
...@@ -244,6 +254,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -244,6 +254,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param to a transportation object * @param to a transportation object
*/ */
@Override
public void check(TransportationObject to) { public void check(TransportationObject to) {
} }
...@@ -253,6 +264,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -253,6 +264,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param veg a vegetation object * @param veg a vegetation object
*/ */
@Override
public void check(Vegetation veg) { public void check(Vegetation veg) {
} }
...@@ -262,6 +274,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -262,6 +274,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param wo a water object * @param wo a water object
*/ */
@Override
public void check(WaterObject wo) { public void check(WaterObject wo) {
} }
...@@ -271,6 +284,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -271,6 +284,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param geom a geometry * @param geom a geometry
*/ */
@Override
public void check(Geometry geom) { public void check(Geometry geom) {
} }
...@@ -280,6 +294,7 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -280,6 +294,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param poly a polygon * @param poly a polygon
*/ */
@Override
public void check(Polygon poly) { public void check(Polygon poly) {
} }
...@@ -289,26 +304,36 @@ public abstract non-sealed class Check implements CheckableVisitor { ...@@ -289,26 +304,36 @@ public abstract non-sealed class Check implements CheckableVisitor {
* *
* @param ring a linear ring * @param ring a linear ring
*/ */
@Override
public void check(LinearRing ring) { public void check(LinearRing ring) {
} }
@Override
public void check(TinObject tin) { public void check(TinObject tin) {
} }
@Override
public void check(ReliefObject relief) { public void check(ReliefObject relief) {
} }
@Override
public void check(CityFurniture cf){ public void check(CityFurniture cf){
} }
@Override
public void check(GenericCityObject gco) { public void check(GenericCityObject gco) {
} }
@Override
public void check(OtherConstructionObject oco){
}
/** /**
* The initialization method of this check. It will be called before any check * The initialization method of this check. It will be called before any check
* method will be executed. Override this if you want to have configurable * method will be executed. Override this if you want to have configurable
......
...@@ -52,7 +52,7 @@ public abstract class Checkable implements Serializable { ...@@ -52,7 +52,7 @@ public abstract class Checkable implements Serializable {
private final Map<CheckId, CheckResult> checkResults = new HashMap<>(); private final Map<CheckId, CheckResult> checkResults = new HashMap<>();
private boolean isValidated = false; private boolean isValidated = false;
protected void setValidated(boolean validated) { public void setValidated(boolean validated) {
isValidated = validated; isValidated = validated;
} }
......
...@@ -31,6 +31,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Installation; ...@@ -31,6 +31,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject; import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Opening; import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject; import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject;
import de.hft.stuttgart.citydoctor2.datastructure.TinObject; import de.hft.stuttgart.citydoctor2.datastructure.TinObject;
...@@ -165,4 +166,8 @@ public abstract non-sealed class CheckableUtilsVisitor implements CheckableVisit ...@@ -165,4 +166,8 @@ public abstract non-sealed class CheckableUtilsVisitor implements CheckableVisit
} }
@Override
public void check(OtherConstructionObject oco){
}
} }
...@@ -13,6 +13,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Installation; ...@@ -13,6 +13,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject; import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Opening; import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject; import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject;
import de.hft.stuttgart.citydoctor2.datastructure.TinObject; import de.hft.stuttgart.citydoctor2.datastructure.TinObject;
...@@ -69,4 +70,6 @@ public sealed interface CheckableVisitor permits Check, CheckableUtilsVisitor { ...@@ -69,4 +70,6 @@ public sealed interface CheckableVisitor permits Check, CheckableUtilsVisitor {
void check(GenericCityObject gco); void check(GenericCityObject gco);
void check(OtherConstructionObject oco);
} }
package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.List;
import java.util.stream.Stream;
public interface CityObjectCache {
/**
* Inserts a CityObject into this cache. Overwrites any existing CityObject with the same GmlID.
* @param cityObject
*/
void put(CityObject cityObject);
/**
* Gets a CityObject from this cache by their GmlID
* @param id GmlID of the requested Object
* @return the associated CityObject, or null if no Object has a matching GmlID
*/
CityObject get(GmlId id);
/**
* Replaces a CityObject with another one
* @param id GmlID of the CityObject that is to be replaced
* @param cityObject the replacement
*/
void replace(GmlId id, CityObject cityObject);
/**
* Returns a Stream of CityObjects from a list of GmlIDs
* @param ids the list of GmlIDs
* @return CityObject Stream of the IDs
*/
Stream<CityObject> getStream(List<GmlId> ids);
/**
* Returns the total count of entries.
* @return the count
*/
int getTotalFeatureCount();
/**
* Resets the Cache to its initial state
*/
void resetCache();
/**
* Returns a list of GmlIDs, whose associated CityObjects were found to contain at least one error.
* <p/>
* Requires Validation to be run first, as unvalidated CityObjects are of unknown state.
* @return list of GmlIDs which were found to contain an error
*/
List<GmlId> getErrorFeatureIds();
/**
* Sets this cache into parsing mode, returning an AutoClosable representing the scoped-state of parsing operation.
* @return an AutoClosable representing the scoped-state for parsing
*/
AutoCloseable startParsingMode();
/**
* Sets this cache into checking mode, returning an AutoClosable representing the scoped-state of checking operation.
* @return an AutoClosable representing the scoped-state for checking
*/
AutoCloseable startCheckingMode();
}
package de.hft.stuttgart.citydoctor2.database;
import com.zaxxer.hikari.HikariDataSource;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
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.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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Utility class for setup and access of the embedded database.
*/
public class EmbeddedDatabaseHandler {
private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class);
private static final String DB_NAME = "/database/cd_db";
private DataSourceWrapper dataSource;
public EmbeddedDatabaseHandler(){
try {
String jdbcurl = H2GISDBFactory.createDataSource(DB_NAME,
true, ";AUTO_SERVER=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));
} catch (Exception e) {
logger.fatal(Localization.getText("DatabaseHandler.setupFailure"));
logger.fatal(e.getMessage());
}
}
public void setupFeaturesTable() {
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");
}
}
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
}
} catch (SQLException e) {
logger.fatal(Localization.getText("DatabaseHandler.tableFailure"));
logger.fatal(e.getMessage());
}
}
/**
* Saves a CityObject to the embedded database, updating the entry if it already exists.
* @param co the CityObject
*/
public void marshallCityObject(CityObject co) {
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.setBoolean(4, co.containsAnyError());
if (logger.isDebugEnabled()) {
logger.debug("Attempting to serialize CityObject: {} ", co);
}
try {
byte[] bytes = SerializationUtils.serialize(co);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ps.setBinaryStream(3, bis, bytes.length);
ps.executeUpdate();
} catch (SerializationException e) {
logger.error(e.getMessage());
logger.error(co.getGmlId());
}
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.marshallingFailure"), co.getGmlId());
logger.error(e.getMessage());
}
}
/**
* Tries to save all CityObjects in a Map to the embedded database as a batched statement, updating already existing entries.
* @param map a Map containing the CityObjects
* @return true if the batched statement was successfully created and commited, false otherwise
*/
public boolean tryMarshallCityObjectMap(Map<GmlId, CityObject> map) {
// Id String for error logging
String id = "";
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("MERGE INTO features VALUES (?, ?, ?, ?)")) {
con.setAutoCommit(false);
for (Map.Entry<GmlId, CityObject> entry : map.entrySet()) {
CityObject co = entry.getValue();
id = entry.getKey().toString();
ps.setString(1, id);
String bbox = BoundingBox.of(co).to2DWkt();
ps.setString(2, bbox);
if (logger.isTraceEnabled()) {
logger.trace("Attempting to serialize CityObject: {} ", co);
}
ps.setBoolean(4, co.containsAnyError());
try {
byte[] bytes = SerializationUtils.serialize(co);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ps.setBinaryStream(3, bis, bytes.length);
ps.addBatch();
} catch (SerializationException e) {
logger.error(Localization.getText("DatabaseHandler.serializationFailure"),co.getGmlId());
logger.error(e.getMessage());
}
}
ps.executeBatch();
con.commit();
} catch (SQLException e) {
con.rollback();
logger.error(Localization.getText("DatabaseHandler.batchCommitFailure"), id);
logger.error(e.getMessage());
return false;
} finally{
con.setAutoCommit(true);
}
} catch (SQLException e){
logger.error(Localization.getText("DatabaseHandler.connectionFailure"));
logger.error(e.getMessage());
return false;
}
return true;
}
/**
* Retrieves a CityObject from the embedded database.
* @param id the gmlId of the requested Feature
* @return the Feature, or null if no entry was found
*/
public CityObject unmarshallCityObject(GmlId id) {
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement(
"SELECT data FROM features WHERE gmlid = ?")) {
ps.setString(1, id.toString());
ResultSet rs = ps.executeQuery();
if (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();
}
});
return co;
} catch (InvalidClassException | ClassCastException | SerializationException e){
String msg = String.format("Deserialization of CityObject \"%s\" failed",id);
logger.error(msg,e);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.unmarshallingFailure"), id);
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
*/
public List<GmlId> getIdsOfAllCityObjectsWithErrors(){
List<GmlId> ids = new ArrayList<>();
try (Connection con = dataSource.getConnection()){
try (PreparedStatement ps = con.prepareStatement("SELECT gmlid FROM features WHERE errors = TRUE")) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
ids.add(new GmlId(rs.getString("gmlid")));
}
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.fetchErrorIdsFailure"));
logger.error(e.getMessage());
}
return ids;
}
/**
* Retrieves a List of GmlIds of CityObjects that are candidates for a buffer collision with a given CityObject(subject),
* meaning the candidate's bbox intersects the subject's buffered bbox.
* @param subject the subject CityObject
* @param bufferSize the buffer distance
* @return a List of GmlIds of buffer collision candidates
*/
public List<GmlId> getBufferCollisionCandidates(CityObject subject, double bufferSize) {
return getBufferCollisionCandidates(subject.getGmlId(), bufferSize);
}
/**
* Retrieves a List of GmlIds of CityObjects that are candidates for a buffer collision with a given CityObject(subject),
* meaning the candidate's bbox intersects the subject's buffered bbox.
* @param subjectId the subject CityObject's GmlId
* @param bufferSize the buffer distance
* @return a List of GmlIds of buffer collision candidates
*/
public List<GmlId> getBufferCollisionCandidates(GmlId subjectId, double bufferSize) {
List<GmlId> intersectingObjects = new ArrayList<>();
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT gmlid FROM features AS tab1" +
" WHERE ST_INTERSECTS(ST_BUFFER(tab1.bbox,?), (SELECT bbox FROM features WHERE gmlid = ?)) " +
"AND tab1.gmlid <> ?")) {
ps.setDouble(1, bufferSize);
ps.setString(2, subjectId.toString());
ps.setString(3, subjectId.toString());
ResultSet rs = ps.executeQuery();
while (rs.next()) {
intersectingObjects.add(new GmlId(rs.getString("gmlid")));
}
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.getBufferIntersectFailure"),subjectId);
logger.debug(e.getMessage());
}
return intersectingObjects;
}
/**
* Retrieves a List of GmlIds of CityObjects that are candidates for a geometry collision with a given CityObject(subject),
* meaning the bounding boxes of candidate and subject intersect.
* @param subject the subject CityObject
* @return a List of GmlIds of buffer collision candidates
*/
public List<GmlId> getGeometryCollisionCandidates(CityObject subject) {
return getGeometryCollisionCandidates(subject.getGmlId());
}
/**
* Retrieves a List of GmlIds of CityObjects that are candidates for a geometry collision with a given CityObject(subject),
* meaning the bounding boxes of candidate and subject intersect.
* @param subjectId the subject CityObject
* @return a List of GmlIds of buffer collision candidates
*/
public List<GmlId> getGeometryCollisionCandidates(GmlId subjectId) {
List<GmlId> intersectingObjects = new ArrayList<>();
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT gmlid FROM features AS tab1" +
" WHERE ST_INTERSECTS(tab1.bbox, (SELECT bbox FROM features WHERE gmlid = ?)) AND tab1.gmlid <> ?"))
{
ps.setString(1, subjectId.toString());
ps.setString(2, subjectId.toString());
ResultSet rs = ps.executeQuery();
while (rs.next()) {
intersectingObjects.add(new GmlId(rs.getString("gmlid")));
}
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.getBBoxIntersectFailure"),subjectId);
logger.debug(e.getMessage());
}
return intersectingObjects;
}
/**
* Returns the number of top-level CityObjects entries that are currently in the embedded database.
* @return the count of top-level CityObjects in the database
*/
public int getFeatureCount() {
int count = -1;
try (Connection con = dataSource.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT COUNT(gmlid) AS nr FROM features")) {
ResultSet rs = ps.executeQuery();
if (rs.next()) {
count = rs.getInt("nr");
}
}
} catch (SQLException e) {
logger.error(Localization.getText("DatabaseHandler.getFeatureCountFailure"));
logger.debug(e.getMessage());
}
return count;
}
}
package de.hft.stuttgart.citydoctor2.database;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
/**
* A managed cache for top-level CityObjects (Features). This cache is connected to the embedded database and will
* automatically load missing CityObjects from it. This cache is size limited and will evict entries using the
* Window Tiny LFU policy.
* <p>
* While this class is not a singleton, it is advised to keep an Instance of this class for the whole lifecycle and
* reset and reuse it instead of creating new Objects, as the initialization is quite heavy and resets the embedded
* database.
* <p/>
* <p>
* Avoid holding long-time strong references to CityObjects returned by the cache, as eviction will sever the
* connection between reference and cache, which will lead to conflicting Object states.
* <p/>
*/
public class FeatureCache implements CityObjectCache{
private static final Logger logger = LogManager.getLogger(FeatureCache.class);
private final LoadingCache<GmlId, CityObject> cache;
private final EmbeddedDatabaseHandler handler;
private final AtomicInteger putsCounter = new AtomicInteger(0);
private CacheState state = CacheState.IDLE;
private final ConcurrentHashMap<Thread, GmlId> evictionLocks = new ConcurrentHashMap<>();
public FeatureCache() {
handler = new EmbeddedDatabaseHandler();
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);
}
/**
* Checks if the CityObject is currently being checked by a Checker-thread to prevent concurrent modifications by the
* Marshaller on eviction.
* @param co the CityObject
* @return true if the CityObject can be marshalled, false otherwise
*/
private boolean isFeatureMarshallable(CityObject co) {
return switch (state){
case IDLE, PARSING -> true;
case CHECKING -> ! (evictionLocks.containsValue(co.getGmlId()));
};
}
@Override
public void put(CityObject cityObject) {
cache.put(cityObject.getGmlId(), cityObject);
switch (state){
case IDLE:
break;
case PARSING:
int count = putsCounter.incrementAndGet();
if (count >= 2500) {
batchMarshall();
}
break;
case CHECKING:
evictionLocks.remove(Thread.currentThread());
break;
}
}
private void batchMarshall(){
logger.trace("Setting up batch Marshaller");
Map<GmlId, CityObject> cacheContent= new HashMap<>(cache.asMap());
cache.invalidateAll();
putsCounter.set(0);
Thread batcher = new Thread(() -> {
logger.trace("Entered batch Marshaller");
boolean success = handler.tryMarshallCityObjectMap(cacheContent);
if (!success) {
logger.trace("Batch marshalling in Thread failed, falling back to one-by-one marshalling");
for (Map.Entry<GmlId, CityObject> entry : cacheContent.entrySet()) {
handler.marshallCityObject(entry.getValue());
}
}
logger.trace("Batch marshalling done");
});
batcher.start();
logger.trace("Batch Marshaller started");
}
@Override
public CityObject get(GmlId id) {
if (state == CacheState.CHECKING){
evictionLocks.put(Thread.currentThread(), id);
}
return cache.get(id);
}
@Override
public void replace(GmlId id, CityObject cityObject) {
cache.invalidate(id);
cache.put(cityObject.getGmlId(), cityObject);
}
@Override
public Stream<CityObject> getStream(List<GmlId> gmlIds) {
return gmlIds.stream().map(this::get);
}
@Override
public int getTotalFeatureCount() {
return handler.getFeatureCount();
}
@Override
public void resetCache(){
logger.trace("Resetting cache");
cache.invalidateAll();
putsCounter.set(0);
handler.setupFeaturesTable();
}
@Override
public List<GmlId> getErrorFeatureIds() {
return handler.getIdsOfAllCityObjectsWithErrors();
}
/**
* Synchronizes CityObject states by updating Database with cache content.
*/
public void synchronize(){
HashMap<GmlId, CityObject> cacheContent= new HashMap<>(cache.asMap());
boolean success = handler.tryMarshallCityObjectMap(cacheContent);
if (!success) {
logger.trace("Batch marshalling failed, falling back to one-by-one marshalling");
for (Map.Entry<GmlId, CityObject> entry : cacheContent.entrySet()) {
handler.marshallCityObject(entry.getValue());
}
}
}
/**
* Signals the cache that parsing is beginning.
*/
private boolean beginParsing(){
return state.changeState(this, CacheState.PARSING);
}
/**
* Signals the cache that parsing has finished.
*/
private void finishParsing() {
if (state == CacheState.PARSING && state.changeState(this, CacheState.IDLE)){
return;
}
throw new IllegalStateException("Cache failed to exit parsing mode");
}
/**
* Signals the cache that checking is beginning.
*/
private boolean beginChecking(){
return state.changeState(this, CacheState.CHECKING);
}
/**
* Signals the cache that checking has finished.
*/
private void finishChecking() {
if (state == CacheState.CHECKING && state.changeState(this, CacheState.IDLE)){
return;
}
throw new IllegalStateException("Cache failed to exit checking mode");
}
@Override
public AutoCloseable startParsingMode(){
if (this.beginParsing()){
return this::finishParsing;
} else throw new IllegalStateException("Cache is not in IDLE state, cannot start parsing mode");
}
@Override
public AutoCloseable startCheckingMode(){
if (this.beginChecking()){
return this::finishChecking;
} else throw new IllegalStateException("Cache is not in IDLE state, cannot start checking mode");
}
protected void setState(CacheState state) {
this.state = state;
}
public CacheState getState() {
return state;
}
public enum CacheState {
IDLE {
@Override
protected boolean changeState(FeatureCache cache, CacheState nextState) {
if (cache.getState() != this){
logger.debug("FeatureCache invoked changeState() of IDLE state while cache state is {}", cache.getState());
return false;
}
this.onExit(cache);
cache.setState(nextState);
nextState.onEntry(cache);
return true;
}
@Override
public void onEntry(FeatureCache cache) {
logger.trace("FeatureCache: Entering IDLE state");
// Idle state does nothing on entry
}
@Override
protected void onExit(FeatureCache cache) {
logger.trace("FeatureCache: Leaving IDLE state");
// Idle state does nothing on exit
}
}, PARSING {
@Override
protected boolean changeState(FeatureCache cache, CacheState nextState) {
if (cache.getState() != this){
logger.debug("FeatureCache invoked changeState() of PARSING state while cache state is {}", cache.getState());
return false;
}
boolean success = false;
switch (nextState){
case IDLE:
this.onExit(cache);
cache.setState(CacheState.IDLE);
success = true;
break;
case PARSING:
logger.debug("Tried to set cache to PARSING state while already parsing");
break;
case CHECKING:
logger.debug("Tried to set cache to CHECKING state while parsing");
break;
}
return success;
}
@Override
public void onEntry(FeatureCache cache) {
logger.trace("FeatureCache: Entering PARSING state");
cache.resetCache();
}
@Override
protected void onExit(FeatureCache cache) {
logger.trace("FeatureCache: Leaving PARSING state");
cache.synchronize();
}
}, CHECKING {
@Override
protected boolean changeState(FeatureCache cache, CacheState nextState) {
if (cache.getState() != this){
logger.debug("FeatureCache invoked changeState() of CHECKING state while cache state is {}", cache.getState());
return false;
}
boolean success = false;
switch (nextState){
case IDLE:
this.onExit(cache);
cache.setState(CacheState.IDLE);
success = true;
break;
case PARSING:
logger.debug("Tried to set cache to PARSING state while checking");
break;
case CHECKING:
logger.debug("Tried to set cache to CHECKING state while already checking");
break;
}
return success;
}
@Override
public void onEntry(FeatureCache cache) {
logger.trace("FeatureCache: Entering CHECKING state");
}
@Override
protected void onExit(FeatureCache cache) {
logger.trace("FeatureCache: Leaving CHECKING state");
cache.synchronize();
}
};
/**
* Signals the statemachine to change the state of the FeatureCache. Returns true if the state successfully
* changed, false otherwise with the FeatureCache remaining in its current state.
* @param cache the FeatureCache instance
* @param nextState the requested next state
* @return true if the cache successfully changed state, false otherwise
*/
protected abstract boolean changeState(FeatureCache cache, CacheState nextState);
protected abstract void onEntry(FeatureCache cache);
protected abstract void onExit(FeatureCache cache);
}
}
package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/**
* An adapter implementation for CityObjectCache, wrapping the Interface around a simple HashMap.
* <p>
* This class is intended to allow parsing or checking {@link CityDoctorModel CityDoctorModels} in specific cases where the model
* must not use the {@link FeatureCache}, like resolving of {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject}
* files or mocking of the Interface in unit tests.
*
*/
public class UnconnectedCache implements CityObjectCache{
private final Map<GmlId, CityObject> cache;
private boolean idle = true;
public UnconnectedCache() {
cache = new HashMap<>();
}
@Override
public void put(CityObject cityObject) {
cache.put(cityObject.getGmlId(), cityObject);
}
@Override
public CityObject get(GmlId id) {
return cache.get(id);
}
@Override
public void replace(GmlId id, CityObject cityObject) {
cache.put(id, cityObject);
}
@Override
public Stream<CityObject> getStream(List<GmlId> ids) {
return cache.values().stream().filter(c -> ids.contains(c.getGmlId()));
}
@Override
public int getTotalFeatureCount() {
return cache.size();
}
public void resetCache(){
cache.clear();
}
@Override
public List<GmlId> getErrorFeatureIds() {
List<GmlId> errorFeatureIds = new ArrayList<>();
cache.forEach((gmlId, cityObject) ->{
if (cityObject.containsAnyError()){
errorFeatureIds.add(cityObject.getGmlId());
}
});
return errorFeatureIds;
}
@Override
public AutoCloseable startParsingMode() {
if (idle){
idle = false;
return ()-> idle=true;
} else throw new IllegalStateException("Cache is not idle, could not start parsing mode");
}
@Override
public AutoCloseable startCheckingMode() {
if (idle){
idle = false;
return ()-> idle=true;
} else throw new IllegalStateException("Cache is not idle, could not start checking mode");
}
}
...@@ -40,7 +40,7 @@ public class BoundarySurface extends CityObject { ...@@ -40,7 +40,7 @@ public class BoundarySurface extends CityObject {
@Serial @Serial
private static final long serialVersionUID = 8793865135393496408L; private static final long serialVersionUID = 8793865135393496408L;
private SurfaceFeatureType featureType;
private BoundarySurfaceType type; private BoundarySurfaceType type;
private final List<Opening> openings = new ArrayList<>(2); private final List<Opening> openings = new ArrayList<>(2);
...@@ -49,18 +49,14 @@ public class BoundarySurface extends CityObject { ...@@ -49,18 +49,14 @@ public class BoundarySurface extends CityObject {
private AbstractThematicSurface gmlObject; private AbstractThematicSurface gmlObject;
public BoundarySurface(AbstractThematicSurface aco) { public BoundarySurface(AbstractThematicSurface aco) {
this(SurfaceFeatureType.BUILDING, BoundarySurfaceType.UNDEFINED, aco); this(BoundarySurfaceType.UNDEFINED, aco);
} }
public BoundarySurface(SurfaceFeatureType featureType, BoundarySurfaceType type, AbstractThematicSurface aco) { public BoundarySurface(BoundarySurfaceType type, AbstractThematicSurface aco) {
this.featureType = featureType;
this.type = type; this.type = type;
gmlObject = aco; gmlObject = aco;
} }
public void setFeatureType(SurfaceFeatureType featureType) {
this.featureType = featureType;
}
public void setType(BoundarySurfaceType type) { public void setType(BoundarySurfaceType type) {
this.type = type; this.type = type;
...@@ -70,9 +66,6 @@ public class BoundarySurface extends CityObject { ...@@ -70,9 +66,6 @@ public class BoundarySurface extends CityObject {
this.gmlObject = gmlObject; this.gmlObject = gmlObject;
} }
public SurfaceFeatureType getSurfaceFeatureType() {
return featureType;
}
public BoundarySurfaceType getType() { public BoundarySurfaceType getType() {
return type; return type;
......
...@@ -20,9 +20,11 @@ package de.hft.stuttgart.citydoctor2.datastructure; ...@@ -20,9 +20,11 @@ package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.math.Vector3d; 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 java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
/** /**
* An axis aligned bounding box represented by its two corners * An axis aligned bounding box represented by its two corners
...@@ -47,6 +49,12 @@ public class BoundingBox { ...@@ -47,6 +49,12 @@ public class BoundingBox {
return BoundingBoxCalculator.calculateBoundingBoxFromPoints(points); return BoundingBoxCalculator.calculateBoundingBoxFromPoints(points);
} }
public static BoundingBox of(CityObject co) {
MinMaxExtentVisitor mima = new MinMaxExtentVisitor();
co.accept(mima);
return BoundingBox.of(mima.getMinMaxExtent());
}
/** /**
* Creates an axis aligned bounding box of the whole model. * Creates an axis aligned bounding box of the whole model.
* *
...@@ -161,4 +169,45 @@ public class BoundingBox { ...@@ -161,4 +169,45 @@ public class BoundingBox {
public double getDiagonalLength() { public double getDiagonalLength() {
return bbox[1].getDistance(bbox[0]); return bbox[1].getDistance(bbox[0]);
} }
/**
* Returns whether this BoundinBox is undefined, meaning that one or all corners are infinite.
* @return True if the BoundingBox is undefined, false otherwise
*/
public boolean isUndefined(){
double x1 = bbox[0].getX();
double y1 = bbox[0].getY();
double x2 = bbox[1].getX();
double y2 = bbox[1].getY();
return (Double.isInfinite(x1) || Double.isInfinite(y1) || Double.isInfinite(x2) || Double.isInfinite(y2));
}
/**
* Returns an axis-aligned 2D polygon projection of this bbox as a WKT String.
* <p/>
* An undefined bbox will return an empty polygon.
* @return The 2D polygon WKT for this bbox
*/
public String to2DWkt() {
if (this.isUndefined()){
return "POLYGON EMPTY";
}
double x1 = bbox[0].getX();
double y1 = bbox[0].getY();
double x2 = bbox[1].getX();
double y2 = bbox[1].getY();
double minX = Math.min(x1, x2);
double minY = Math.min(y1, y2);
double maxX = Math.max(x1, x2);
double maxY = Math.max(y1, y2);
return String.format(Locale.ROOT,"POLYGON((%.3f %.3f, %.3f %.3f, %.3f %.3f, %.3f %.3f, %.3f %.3f))",
minX, minY,
maxX, minY,
maxX, maxY,
minX, maxY,
minX, minY
);
}
} }
...@@ -21,6 +21,7 @@ package de.hft.stuttgart.citydoctor2.datastructure; ...@@ -21,6 +21,7 @@ package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor; import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor;
import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.database.CityObjectCache;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException; import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.writer.CityGMLWriterUtils; import de.hft.stuttgart.citydoctor2.writer.CityGMLWriterUtils;
...@@ -46,15 +47,15 @@ import java.util.stream.Stream; ...@@ -46,15 +47,15 @@ import java.util.stream.Stream;
public class CityDoctorModel { public class CityDoctorModel {
private static final String COULD_NOT_FIND_FEATURE = "Could not find feature: "; private static final String COULD_NOT_FIND_FEATURE = "Could not find feature: ";
private final List<Building> buildings; private final List<GmlId> buildings;
private final List<Vegetation> vegetation; private final List<GmlId> vegetation;
private final List<BridgeObject> bridges; private final List<GmlId> bridges;
private final List<CityFurniture> cityfurniture; private final List<GmlId> cityfurniture;
private final List<GenericCityObject> genericObjects; private final List<GmlId> genericObjects;
private final List<CityObject> land; private final List<GmlId> land;
private final List<TransportationObject> roads; private final List<GmlId> roads;
private final List<Tunnel> tunnels; private final List<GmlId> tunnels;
private final List<WaterObject> water; private final List<GmlId> water;
private CityModel cModel; private CityModel cModel;
private final ParserConfiguration config; private final ParserConfiguration config;
private final String fileName; private final String fileName;
...@@ -63,11 +64,15 @@ public class CityDoctorModel { ...@@ -63,11 +64,15 @@ public class CityDoctorModel {
private boolean isValidated = false; private boolean isValidated = false;
private ValidationPlan plan; private ValidationPlan plan;
private CityGMLVersion cityGMLVersion; private CityGMLVersion cityGMLVersion;
private final CityObjectCache cache;
public CityDoctorModel(ParserConfiguration config, File file) { public CityDoctorModel(ParserConfiguration config, File file, CityObjectCache cache) {
if (config == null) { if (config == null) {
throw new IllegalArgumentException("Parser configuration may not be null"); throw new IllegalArgumentException("Parser configuration may not be null");
} }
if( file == null ) {
throw new IllegalArgumentException("File may not be null");
}
this.fileName = file.getName(); this.fileName = file.getName();
this.config = config; this.config = config;
this.file = file; this.file = file;
...@@ -81,6 +86,7 @@ public class CityDoctorModel { ...@@ -81,6 +86,7 @@ public class CityDoctorModel {
cityfurniture = new ArrayList<>(); cityfurniture = new ArrayList<>();
genericObjects = new ArrayList<>(); genericObjects = new ArrayList<>();
globalErrors = new ArrayList<>(); globalErrors = new ArrayList<>();
this.cache = cache;
} }
public boolean isValidated() { public boolean isValidated() {
...@@ -118,20 +124,59 @@ public class CityDoctorModel { ...@@ -118,20 +124,59 @@ public class CityDoctorModel {
} }
public Stream<CityObject> createFeatureStream() { public Stream<CityObject> createFeatureStream() {
return Stream.of(buildings.stream(), vegetation.stream(), bridges.stream(), land.stream(), roads.stream(), return Stream.of(getBuildings(), getVegetation(), getBridges(), getLand(), getTransportation(),
tunnels.stream(), water.stream(), cityfurniture.stream(), genericObjects.stream()).flatMap(co -> co); getTunnels(), getWater(), getCityFurniture(), getOtherCityObjects()).flatMap(co -> co);
}
public List<GmlId> getFeatureIds() {
List<GmlId> featureIds = new ArrayList<>();
featureIds.addAll(buildings);
featureIds.addAll(vegetation);
featureIds.addAll(bridges);
featureIds.addAll(land);
featureIds.addAll(roads);
featureIds.addAll(tunnels);
featureIds.addAll(water);
featureIds.addAll(cityfurniture);
featureIds.addAll(genericObjects);
return featureIds;
}
public boolean containsGmlId(String id) {
if (id == null) {
return false;
}
GmlId gmlId = new GmlId(id);
if (buildings.contains(gmlId)) {
return true;
}
if (vegetation.contains(gmlId)) {
return true;
}
if (bridges.contains(gmlId)) {
return true;
}
if (land.contains(gmlId)) {
return true;
}
if (roads.contains(gmlId)) {
return true;
}
if (tunnels.contains(gmlId)) {
return true;
}
if (water.contains(gmlId)) {
return true;
}
if (cityfurniture.contains(gmlId)) {
return true;
}
return genericObjects.contains(gmlId);
} }
public void accept(CheckableVisitor visitor) { public void accept(CheckableVisitor visitor) {
buildings.forEach(co -> co.accept(visitor)); createFeatureStream().forEach(cityObject -> cityObject.accept(visitor));
vegetation.forEach(co -> co.accept(visitor));
bridges.forEach(co -> co.accept(visitor));
land.forEach(co -> co.accept(visitor));
roads.forEach(co -> co.accept(visitor));
tunnels.forEach(co -> co.accept(visitor));
water.forEach(co -> co.accept(visitor));
cityfurniture.forEach(co -> co.accept(visitor));
genericObjects.forEach(co -> co.accept(visitor));
} }
public void saveAs(String file, boolean saveQualityAde) throws CityDoctorWriteException { public void saveAs(String file, boolean saveQualityAde) throws CityDoctorWriteException {
...@@ -149,6 +194,7 @@ public class CityDoctorModel { ...@@ -149,6 +194,7 @@ public class CityDoctorModel {
} }
} }
private void exportAsGML(String file) throws CityDoctorWriteException { private void exportAsGML(String file) throws CityDoctorWriteException {
CityGMLWriterUtils.writeCityModel(file, this); CityGMLWriterUtils.writeCityModel(file, this);
} }
...@@ -228,106 +274,201 @@ public class CityDoctorModel { ...@@ -228,106 +274,201 @@ public class CityDoctorModel {
public Set<CheckError> collectErrors() { public Set<CheckError> collectErrors() {
List<CheckError> errors = new ArrayList<>(); List<CheckError> errors = new ArrayList<>();
collectErrorsFromList(errors, buildings); createFeatureStream().forEach(cityObject -> cityObject.collectErrors(errors));
collectErrorsFromList(errors, vegetation);
collectErrorsFromList(errors, bridges);
collectErrorsFromList(errors, land);
collectErrorsFromList(errors, roads);
collectErrorsFromList(errors, tunnels);
collectErrorsFromList(errors, water);
collectErrorsFromList(errors, cityfurniture);
collectErrorsFromList(errors, genericObjects);
return new HashSet<>(errors); return new HashSet<>(errors);
} }
private void collectErrorsFromList(List<CheckError> errors, List<? extends CityObject> cos) {
for (CityObject co : cos) {
co.collectContainedErrors(errors);
}
}
public String getFileName() { public String getFileName() {
return fileName; return fileName;
} }
public void addBridge(BridgeObject coBridge) {
bridges.add(coBridge);
}
public void addBuilding(Building coBuilding) { public void addBuilding(Building coBuilding) {
buildings.add(coBuilding); buildings.add(coBuilding.getGmlId());
cache.put(coBuilding);
}
public void removeFeature(GmlId id){
buildings.remove(id);
vegetation.remove(id);
bridges.remove(id);
land.remove(id);
roads.remove(id);
tunnels.remove(id);
water.remove(id);
cityfurniture.remove(id);
genericObjects.remove(id);
}
public Stream<Building> getBuildings() {
return buildings.stream().map(co -> (Building) cache.get(co));
} }
public List<Building> getBuildings() { public List<GmlId> getBuildingIds() {
return buildings; return buildings;
} }
public List<BridgeObject> getBridges() { public int getBuildingCount(){
return buildings.size();
}
public void addBridge(BridgeObject coBridge) {
bridges.add(coBridge.getGmlId());
cache.put(coBridge);
}
public Stream<BridgeObject> getBridges() {
return bridges.stream().map(co -> (BridgeObject) cache.get(co));
}
public List<GmlId> getBridgeIds() {
return bridges; return bridges;
} }
public List<CityFurniture> getCityFurniture() { public int getBridgeCount(){
return cityfurniture; return bridges.size();
} }
public void addCityFurniture(CityFurniture coFurniture) { public void addCityFurniture(CityFurniture coFurniture) {
cityfurniture.add(coFurniture); cityfurniture.add(coFurniture.getGmlId());
cache.put(coFurniture);
}
public Stream<CityFurniture> getCityFurniture() {
return cityfurniture.stream().map(co -> (CityFurniture) cache.get(co));
}
public List<GmlId> getCityFurnitureIds() {
return cityfurniture;
}
public int getCityFurnitureCount(){
return cityfurniture.size();
}
public void addOtherCityObject(CityObject otherCityObject) {
genericObjects.add(otherCityObject.getGmlId());
cache.put(otherCityObject);
}
public Stream<CityObject> getOtherCityObjects() {
return genericObjects.stream().map(cache::get);
} }
public List<GenericCityObject> getGenericCityObjects() { public List<GmlId> getOtherCityObjectsIds() {
return genericObjects; return genericObjects;
} }
public void addGenericCityObject(GenericCityObject coGenericCityObject) { public int getOtherCityObjectsCount(){
genericObjects.add(coGenericCityObject); return genericObjects.size();
} }
public void setCityModel(CityModel cModel) { public void addTransportation(TransportationObject to) {
this.cModel = cModel; roads.add(to.getGmlId());
cache.put(to);
} }
public CityModel getCityModel() { public Stream<TransportationObject> getTransportation() {
return cModel; return roads.stream().map(co -> (TransportationObject) cache.get(co));
} }
public List<TransportationObject> getTransportation() { public List<GmlId> getTransportationIds() {
return roads; return roads;
} }
public List<Tunnel> getTunnels() { public int getTransportationCount(){
return tunnels; return roads.size();
} }
public List<WaterObject> getWater() { public void addTunnel(Tunnel tunnel) {
return water; tunnels.add(tunnel.getGmlId());
cache.put(tunnel);
} }
public List<CityObject> getLand() { public Stream<Tunnel> getTunnels() {
return land; return tunnels.stream().map(co -> (Tunnel) cache.get(co));
} }
public List<Vegetation> getVegetation() { public List<GmlId> getTunnelIds() {
return vegetation; return tunnels;
}
public int getTunnelCount(){
return tunnels.size();
} }
public void addWater(WaterObject wo) { public void addWater(WaterObject wo) {
water.add(wo); water.add(wo.getGmlId());
cache.put(wo);
}
public Stream<WaterObject> getWater() {
return water.stream().map(co -> (WaterObject) cache.get(co));
}
public List<GmlId> getWaterIds() {
return water;
}
public int getWaterCount(){
return water.size();
} }
public void addLand(LandObject lo) { public void addLand(LandObject lo) {
land.add(lo); land.add(lo.getGmlId());
cache.put(lo);
}
public void addRelief(ReliefObject relief) {
land.add(relief.getGmlId());
cache.put(relief);
}
public void addTin(TinObject tin) {
land.add(tin.getGmlId());
cache.put(tin);
}
public Stream<CityObject> getLand() {
return land.stream().map(cache::get);
}
public List<GmlId> getLandIds() {
return land;
}
public int getLandCount(){
return land.size();
} }
public void addVegetation(Vegetation veg) { public void addVegetation(Vegetation veg) {
vegetation.add(veg); vegetation.add(veg.getGmlId());
cache.put(veg);
} }
public void addTransportation(TransportationObject to) { public Stream<Vegetation> getVegetation() {
roads.add(to); return vegetation.stream().map(co -> (Vegetation) cache.get(co));
} }
public void addTunnel(Tunnel tunnel) { public List<GmlId> getVegetationIds() {
tunnels.add(tunnel); return vegetation;
}
public int getVegetationCount(){
return vegetation.size();
}
public void setCityModel(CityModel cModel) {
this.cModel = cModel;
}
public CityModel getCityModel() {
return cModel;
}
public CityObjectCache getCache(){
return cache;
} }
public int getNumberOfFeatures() { public int getNumberOfFeatures() {
...@@ -362,100 +503,109 @@ public class CityDoctorModel { ...@@ -362,100 +503,109 @@ public class CityDoctorModel {
} }
private void replaceWaterObject(CityObject currentFeature, CityObject nextFeature) { private void replaceWaterObject(CityObject currentFeature, CityObject nextFeature) {
int index = water.indexOf(currentFeature); int index = water.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in water objects"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in water objects");
} }
water.set(index, (WaterObject) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
water.set(index, nextFeature.getGmlId());
} }
private void replaceLandObject(CityObject currentFeature, CityObject nextFeature) { private void replaceLandObject(CityObject currentFeature, CityObject nextFeature) {
int index = land.indexOf(currentFeature); int index = land.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in land objects"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in land objects");
} }
land.set(index, nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
land.set(index, nextFeature.getGmlId());
} }
private void replaceVegetation(CityObject currentFeature, CityObject nextFeature) { private void replaceVegetation(CityObject currentFeature, CityObject nextFeature) {
int index = vegetation.indexOf(currentFeature); int index = vegetation.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation");
} }
vegetation.set(index, (Vegetation) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
vegetation.set(index, nextFeature.getGmlId());
} }
private void replaceCityFurniture(CityObject currentFeature, CityObject nextFeature) { private void replaceCityFurniture(CityObject currentFeature, CityObject nextFeature) {
int index = cityfurniture.indexOf(currentFeature); int index = cityfurniture.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation");
} }
cityfurniture.set(index, (CityFurniture) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
cityfurniture.set(index, nextFeature.getGmlId());
} }
private void replaceGenericCityObject(CityObject currentFeature, CityObject nextFeature) { private void replaceGenericCityObject(CityObject currentFeature, CityObject nextFeature) {
int index = genericObjects.indexOf(currentFeature); int index = genericObjects.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in generic city objects"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in generic city objects");
} }
genericObjects.set(index, (GenericCityObject) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
genericObjects.set(index, nextFeature.getGmlId());
} }
private void replaceTransportationObject(CityObject currentFeature, CityObject nextFeature) { private void replaceTransportationObject(CityObject currentFeature, CityObject nextFeature) {
int index = roads.indexOf(currentFeature); int index = roads.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in transportation objects"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in transportation objects");
} }
roads.set(index, (TransportationObject) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
roads.set(index, nextFeature.getGmlId());
} }
private void replaceTunnel(CityObject currentFeature, CityObject nextFeature) { private void replaceTunnel(CityObject currentFeature, CityObject nextFeature) {
int index = tunnels.indexOf(currentFeature); int index = tunnels.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in tunnels"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in tunnels");
} }
tunnels.set(index, (Tunnel) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
tunnels.set(index, nextFeature.getGmlId());
} }
private void replaceBridge(CityObject currentFeature, CityObject nextFeature) { private void replaceBridge(CityObject currentFeature, CityObject nextFeature) {
int index = bridges.indexOf(currentFeature); int index = bridges.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in bridges"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in bridges");
} }
bridges.set(index, (BridgeObject) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
bridges.set(index, nextFeature.getGmlId());
} }
private void replaceBuilding(CityObject currentFeature, CityObject nextFeature) { private void replaceBuilding(CityObject currentFeature, CityObject nextFeature) {
int index = buildings.indexOf(currentFeature); int index = buildings.indexOf(currentFeature.getGmlId());
if (index == -1) { if (index == -1) {
throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in buildings"); throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in buildings");
} }
buildings.set(index, (Building) nextFeature); cache.replace(currentFeature.getGmlId(), nextFeature);
buildings.set(index, nextFeature.getGmlId());
} }
public void addCityObject(CityObject co) { public void addCityObject(CityObject co) {
if (co instanceof Building b) { if (co instanceof Building b) {
buildings.add(b); buildings.add(b.getGmlId());
} else if (co instanceof BridgeObject bo) { } else if (co instanceof BridgeObject bo) {
bridges.add(bo); bridges.add(bo.getGmlId());
} else if (co instanceof TransportationObject to) { } else if (co instanceof TransportationObject to) {
roads.add(to); roads.add(to.getGmlId());
} else if (co instanceof Tunnel tu) { } else if (co instanceof Tunnel tu) {
tunnels.add(tu); tunnels.add(tu.getGmlId());
} else if (co instanceof Vegetation veg) { } else if (co instanceof Vegetation veg) {
vegetation.add(veg); vegetation.add(veg.getGmlId());
} else if (co instanceof LandObject lo) { } else if (co instanceof LandObject lo) {
land.add(lo); land.add(lo.getGmlId());
} else if (co instanceof WaterObject wo) { } else if (co instanceof WaterObject wo) {
water.add(wo); water.add(wo.getGmlId());
} else if (co instanceof ReliefObject) { } else if (co instanceof ReliefObject) {
land.add(co); land.add(co.getGmlId());
} else if (co instanceof TinObject) { } else if (co instanceof TinObject) {
land.add(co); land.add(co.getGmlId());
} else if (co instanceof CityFurniture cf) { } else if (co instanceof CityFurniture cf) {
cityfurniture.add(cf); cityfurniture.add(cf.getGmlId());
} else if (co instanceof GenericCityObject gco) { } else if (co instanceof GenericCityObject gco) {
genericObjects.add(gco); genericObjects.add(gco.getGmlId());
} }
} }
...@@ -467,11 +617,4 @@ public class CityDoctorModel { ...@@ -467,11 +617,4 @@ public class CityDoctorModel {
return cityGMLVersion; return cityGMLVersion;
} }
public void addRelief(ReliefObject relief) {
addCityObject(relief);
}
public void addTin(TinObject tin) {
addCityObject(tin);
}
} }
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