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
......@@ -27,5 +27,5 @@ public enum FeatureType {
BUILDING, TRANSPORTATION, VEGETATION, BRIDGE, LAND, WATER, BOUNDARY_SURFACE, INSTALLATION, OPENING,
BUILDING_PART, BUILDING_SUBDIVISION, BRIDGE_CONSTRUCTION_ELEMENT, BRIDGE_INSTALLATION, ROOM, FURNITURE, CITY_FURNITURE,
GENERIC_CITY_OBJECT, TUNNEL, TUNNEL_PART, TUNNEL_CONSTRUCTION_ELEMENT, TUNNEL_INSTALLATION,
OTHER_CITY_OBJECT, TUNNEL, TUNNEL_PART, TUNNEL_CONSTRUCTION_ELEMENT, TUNNEL_INSTALLATION,
}
......@@ -5,10 +5,16 @@ import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.AbstractGenericAttributeProperty;
import org.citygml4j.core.model.generics.*;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
public class GenericAttribute {
public class GenericAttribute implements Serializable {
@Serial
private static final long serialVersionUID = -4101408947632132328L;
private static final Logger logger = LogManager.getLogger(GenericAttribute.class);
......
......@@ -111,7 +111,7 @@ public class GenericCityObject extends CityObject {
cgmlGos.getDeprecatedProperties().setLod4Geometry(new SolidProperty(solid));
break;
default:
throw new IllegalStateException("Cannot add " + geom.getLod() + " solid to buildings");
throw new IllegalStateException("Cannot add " + geom.getLod() + " solid to generic city object");
}
}
@Override
......@@ -121,7 +121,7 @@ public class GenericCityObject extends CityObject {
@Override
public FeatureType getFeatureType() {
return FeatureType.GENERIC_CITY_OBJECT;
return FeatureType.OTHER_CITY_OBJECT;
}
public List<BoundarySurface> getBoundarySurfaces() {
......
......@@ -26,6 +26,8 @@ import de.hft.stuttgart.citydoctor2.utils.BoundingBoxCalculator;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
import javafx.scene.paint.Color;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.util.*;
......@@ -53,9 +55,9 @@ public class Geometry extends GmlElement {
private CityObject parent;
private final List<Polygon> polygons = new ArrayList<>(2);
private List<Edge> edges;
private Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap;
private List<Vertex> vertices;
private transient List<Edge> edges;
private transient Map<SerializablePair<Vertex, Vertex>, Edge> edgeMap;
private transient List<Vertex> vertices;
private Orientation orientation;
public Geometry(GeometryType type, Lod lod, Orientation orientation) {
......@@ -344,6 +346,12 @@ public class Geometry extends GmlElement {
return false;
}
@Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
edges = new ArrayList<>();
edgeMap = new HashMap<>();
}
public void clearMetaData() {
if (vertices != null) {
......
......@@ -6,6 +6,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.database.CityObjectCache;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.ImplicitGeometry;
......@@ -23,6 +24,10 @@ public class ImplicitGeometryHolder extends Geometry {
private static final long serialVersionUID = -8938931081577196349L;
private static final Logger logger = LogManager.getLogger(ImplicitGeometryHolder.class);
public enum PrototypeGeometryType {
LIBRARY_OBJECT, RELATIVE_GEOMETRY
}
private ImplicitGeometry cgmlImplicitGeometry = null;
private static ConcurrentHashMap<ImplicitGeometryHolder, ImplicitGeometry> deferredRelGeomObjects = new ConcurrentHashMap<>();
......@@ -51,8 +56,11 @@ public class ImplicitGeometryHolder extends Geometry {
return igh;
}
public static void resolveDeferredObjects() {
public static void resolveDeferredObjects(CityObjectCache cache) {
Set<GmlId> missingGeoms = new HashSet<>();
if(!deferredRelGeomObjects.isEmpty()) {
logger.info("Resolving deferred relative geometries");
}
for (ImplicitGeometryHolder key : deferredRelGeomObjects.keySet()) {
ImplicitGeometry ig = deferredRelGeomObjects.get(key);
String hrefId = ig.getRelativeGeometry().getHref();
......@@ -61,7 +69,7 @@ public class ImplicitGeometryHolder extends Geometry {
RelativeGeometry relGeom = RelativeGeometry.of(gmlId);
if (relGeom != null) {
ImplicitGeometryHolder igh = new ImplicitGeometryHolder(ig, relGeom);
CityObject parent = key.getParent();
CityObject parent = cache.get(key.getParent().getGmlId());
parent.removeGeometry(igh.getLod(), igh.getType());
parent.addGeometry(igh);
} else {
......@@ -72,6 +80,9 @@ public class ImplicitGeometryHolder extends Geometry {
logger.warn("RelativeGeometries of the following gmlIDs could not be resolved: {}", missingGeoms);
}
deferredRelGeomObjects.clear();
if (logger.isDebugEnabled()) {
logger.debug("Resolving HRef geometries finished");
}
}
private ImplicitGeometryHolder(Lod lod) {
......@@ -94,6 +105,30 @@ public class ImplicitGeometryHolder extends Geometry {
return type;
}
/**
* Resets the Object maps of {@link RelativeGeometry} and {@link LibraryObject} to prevent memory and data leaks
*/
public static void resetObjectMaps() {
RelativeGeometry.resetObjectMap();
LibraryObject.resetObjectMap();
}
/**
* Prepares the prototype geometries for checking and sets a lock to prevent clearing of metadata
*/
public static void preparePrototypeGeometriesForChecking(){
RelativeGeometry.preparePrototypeGeometriesForChecking();
LibraryObject.preparePrototypeGeometriesForChecking();
}
/**
* Clears metadata creating for checking of prototype geometries and releases the metadata lock
*/
public static void clearMetaDataOfPrototypeGeometries(){
RelativeGeometry.clearMetaDataOfPrototypeGeometries();
LibraryObject.clearMetaDataOfPrototypeGeometries();
}
@Override
public void accept(CheckableVisitor c) {
prototypeGeometry.accept(c);
......@@ -104,8 +139,5 @@ public class ImplicitGeometryHolder extends Geometry {
return prototypeGeometry.isValidated();
}
public enum PrototypeGeometryType {
LIBRARY_OBJECT, RELATIVE_GEOMETRY
}
}
......@@ -4,7 +4,7 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipEntry;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
......@@ -26,8 +26,8 @@ public class LibraryObject extends Geometry {
private static final Logger logger = LogManager.getLogger(LibraryObject.class);
@Serial
private static final long serialVersionUID = -50293435187454911L;
private static Map<String, LibraryObject> libraryObjects = new ConcurrentHashMap<>();
private static boolean metadataLocked = false;
private static final Map<String, LibraryObject> libraryObjects = new ConcurrentHashMap<>();
public static LibraryObject of(Path path, ParserConfiguration config) {
if (libraryObjects.containsKey(path.toString())) {
......@@ -44,22 +44,6 @@ public class LibraryObject extends Geometry {
return libOb;
}
public static LibraryObject of(CityGmlZipEntry entry, ParserConfiguration config) {
String fileName = entry.getEntrySubPath();
if (libraryObjects.containsKey(fileName)){
return libraryObjects.get(fileName);
}
Geometry protoGeom = parseZipEntry(entry, config);
if (protoGeom == null) {
return null;
}
LibraryObject libOb = new LibraryObject(protoGeom.getType(), protoGeom.getLod(), protoGeom.getOrientation());
protoGeom.getPolygons().forEach(libOb::addPolygon);
libOb.updateEdgesAndVertices();
libraryObjects.put(fileName, libOb);
return libOb;
}
private LibraryObject(GeometryType type, Lod lod, Orientation orientation) {
super(type, lod, orientation);
}
......@@ -69,31 +53,15 @@ public class LibraryObject extends Geometry {
Geometry geo = null;
if (Files.exists(path)) {
try {
geo = getProtoGeometry(CityGmlParser.parseCityGmlFileSilently(path.toString(), config));
geo = getProtoGeometry(CityGmlParser.nestedCityGmlFileParse(path.toString(), config));
} catch (CityGmlParseException e) {
logger.error(String.format(
"Encountered an error while parsing library object %s", path));
logger.error(Localization.getText("LibraryObject.parsingError"), path);
logger.error(e.getStackTrace());
} catch (InvalidGmlFileException e) {
logger.error(e.getStackTrace());
}
} else {
logger.error(String.format("Implicit geometry references non-existing library object file %s.", path));
}
return geo;
}
private static Geometry parseZipEntry(CityGmlZipEntry entry, ParserConfiguration config) {
Geometry geo = null;
try {
entry.loadEntry(config);
geo = getProtoGeometry(entry.getModel());
} catch (InvalidGmlFileException e) {
logger.error(e.getStackTrace());
} catch (CityGmlParseException e) {
logger.error(String.format(
"Encountered an error while parsing library object %s", entry.getEntrySubPath()));
logger.error(e.getStackTrace());
logger.error(Localization.getText("LibraryObject.fileMissing"), path);
}
return geo;
}
......@@ -111,4 +79,33 @@ public class LibraryObject extends Geometry {
return objects.get(0).getHighestLodGeometry();
}
public static void resetObjectMap() {
libraryObjects.clear();
}
public static void preparePrototypeGeometriesForChecking() {
libraryObjects.values().forEach(LibraryObject::updateEdgesAndVertices);
metadataLocked = true;
}
public static void clearMetaDataOfPrototypeGeometries() {
metadataLocked = false;
libraryObjects.values().forEach(LibraryObject::clearMetaData);
}
@Override
public void updateEdgesAndVertices(){
if (!metadataLocked) {
super.updateEdgesAndVertices();
}
}
@Override
public void clearMetaData(){
if (!metadataLocked) {
super.clearMetaData();
}
}
}
......@@ -44,13 +44,11 @@ public class Opening extends CityObject {
private BoundarySurface partOf;
private OpeningType type;
private SurfaceFeatureType featureType;
private AbstractFillingSurface ao;
public Opening(OpeningType type, SurfaceFeatureType featureType, BoundarySurface partOf,
public Opening(OpeningType type, BoundarySurface partOf,
AbstractFillingSurface ao) {
this.featureType = featureType;
this.partOf = partOf;
this.type = type;
this.ao = ao;
......@@ -92,11 +90,6 @@ public class Opening extends CityObject {
}
}
public SurfaceFeatureType getSurfaceFeatureType() {
return featureType;
}
public BoundarySurface getPartOf() {
return partOf;
}
......
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.construction.OtherConstruction;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.util.geometry.GeometryFactory;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SolidProperty;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
public class OtherConstructionObject extends CityObject{
@Serial
private static final long serialVersionUID = -3885206364978761701L;
private static final Logger logger = LogManager.getLogger(OtherConstructionObject.class);
private final List<BoundarySurface> boundarySurfaceList = new ArrayList<>();
private OtherConstruction gmlObject;
@Override
public void accept(CheckableVisitor c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
}
for (BoundarySurface bs : boundarySurfaceList) {
bs.accept(c);
}
}
public void setGmlObject(OtherConstruction gmlObject) {
this.gmlObject = gmlObject;
}
@Override
public void rebuildGeometries(GeometryFactory factory, ParserConfiguration config) {
for (Geometry geom : getGeometries()) {
if (geom instanceof ImplicitGeometryHolder) {
continue;
}
if (geom.getType() == GeometryType.MULTI_SURFACE) {
MultiSurface ms = CityGmlUtils.createMultiSurface(geom, factory, config);
setMultiSurfaceAccordingToLod(geom, ms);
} else {
Solid solid = CityGmlUtils.createSolid(geom, factory, config);
setSolidAccordingToLod(geom, solid);
}
}
removeEmptyBoundarySurfaces();
}
private void removeEmptyBoundarySurfaces() {
for (BoundarySurface bs : boundarySurfaceList) {
if (bs.getGeometries().isEmpty()) {
for (AbstractSpaceBoundaryProperty bsp : gmlObject.getBoundaries()) {
if (bsp.getObject() != null && bsp.getObject() == bs.getGmlObject()) {
logger.warn("Found empty boundary surface: {}, removing from feature", bs.getGmlId());
gmlObject.getBoundaries().remove(bsp);
break;
}
}
}
}
}
private void setMultiSurfaceAccordingToLod(Geometry geom, MultiSurface ms) {
switch (geom.getLod()) {
case LOD0:
gmlObject.setLod0MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD2:
gmlObject.setLod2MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD3:
gmlObject.setLod3MultiSurface(new MultiSurfaceProperty(ms));
break;
default:
throw new IllegalStateException("Cannot add " + geom.getLod() + " multi surface to other construction object");
}
}
private void setSolidAccordingToLod(Geometry geom, Solid solid) {
switch (geom.getLod()) {
case LOD1:
gmlObject.setLod1Solid(new SolidProperty(solid));
break;
case LOD2:
gmlObject.setLod2Solid(new SolidProperty(solid));
break;
case LOD3:
gmlObject.setLod3Solid(new SolidProperty(solid));
break;
default:
throw new IllegalStateException("Cannot add " + geom.getLod() + " solid to other construction object");
}
}
@Override
public OtherConstruction getGmlObject() {
return gmlObject;
}
@Override
public void unsetGmlGeometries() {
gmlObject.setLod0MultiSurface(null);
gmlObject.setLod2MultiSurface(null);
gmlObject.setLod3MultiSurface(null);
gmlObject.setLod1Solid(null);
gmlObject.setLod2Solid(null);
gmlObject.setLod3Solid(null);
}
@Override
public CityObject getTopLevelCityObject() {
return this;
}
@Override
public FeatureType getFeatureType() {
return FeatureType.OTHER_CITY_OBJECT;
}
public void addBoundarySurface(BoundarySurface bs) {
boundarySurfaceList.add(bs);
bs.setParent(this);
}
public List<BoundarySurface> getBoundarySurfaces() {
return boundarySurfaceList;
}
}
......@@ -15,6 +15,7 @@ public class RelativeGeometry extends Geometry {
private static final long serialVersionUID = -686112245455298977L;
private static final Map<GmlId, RelativeGeometry> relativeGeometries = new ConcurrentHashMap<>();
private static boolean metadataLocked = false;
public static RelativeGeometry of(GmlId gmlId, Geometry geom) {
if (relativeGeometries.containsKey(gmlId)) {
......@@ -34,4 +35,32 @@ public class RelativeGeometry extends Geometry {
private RelativeGeometry(GeometryType type, Lod lod, Orientation orientation) {
super(type, lod, orientation);
}
public static void resetObjectMap() {
relativeGeometries.clear();
}
public static void preparePrototypeGeometriesForChecking() {
relativeGeometries.values().forEach(RelativeGeometry::updateEdgesAndVertices);
metadataLocked = true;
}
public static void clearMetaDataOfPrototypeGeometries() {
metadataLocked = false;
relativeGeometries.values().forEach(RelativeGeometry::clearMetaData);
}
@Override
public void updateEdgesAndVertices(){
if (!metadataLocked) {
super.updateEdgesAndVertices();
}
}
@Override
public void clearMetaData(){
if (!metadataLocked) {
super.clearMetaData();
}
}
}
......@@ -21,6 +21,8 @@ package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial;
import java.util.ArrayList;
import java.util.HashSet;
......@@ -37,7 +39,7 @@ public class Vertex extends Vector3d {
@Serial
private static final long serialVersionUID = -5525361920397934892L;
private List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);
private transient List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);
public Vertex(double x, double y, double z) {
super(x, y, z);
......@@ -91,6 +93,12 @@ public class Vertex extends Vector3d {
throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
}
@Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
clearAdjacentRings();
}
public Set<Polygon> getAdjacentPolygons(Geometry geom) {
return getAdjacentPolygonsWithoutNeighbor(geom);
}
......
......@@ -24,6 +24,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.database.FeatureCache;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.CityGMLVersion;
......@@ -45,6 +48,7 @@ import org.citygml4j.core.model.building.BuildingRoomProperty;
import org.citygml4j.core.model.building.BuildingUnit;
import org.citygml4j.core.model.building.Storey;
import org.citygml4j.core.model.construction.AbstractConstruction;
import org.citygml4j.core.model.construction.OtherConstruction;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractFeatureWithLifespan;
import org.citygml4j.core.model.core.AbstractGenericAttributeProperty;
......@@ -157,35 +161,32 @@ import de.hft.stuttgart.citydoctor2.datastructure.WaterObject;
import de.hft.stuttgart.citydoctor2.math.graph.KDTree;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipEntry;
public class Citygml3FeatureMapper extends ObjectWalker {
private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class);
private static FeatureCache featureCache = new FeatureCache();
private final CityDoctorModel model;
private final ParserConfiguration config;
private final Path directory;
private final CityGmlZipEntry zipEntry;
private final double neighborDistance;
private Map<String, ConcretePolygon> polygonMap = new HashMap<>();
private Map<String, CompositeCollection> compositeMap = new HashMap<>();
private List<ResolvableReference> references = new ArrayList<>();
private Map<Vertex, Vertex> vertexMap = new HashMap<>();
public Citygml3FeatureMapper(ParserConfiguration config, Path path) {
public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) {
this.config = config;
this.directory = path.getParent();
model = new CityDoctorModel(config, path.toFile());
neighborDistance = 1.8d / Math.pow(10, config.getNumberOfRoundingPlaces());
zipEntry = null;
}
public Citygml3FeatureMapper(ParserConfiguration config, CityGmlZipEntry entry) {
this.config = config;
this.directory = null;
this.zipEntry = entry;
model = new CityDoctorModel(config, Path.of(entry.getEntrySubPath()).toFile());
if (unconnected){
model = new CityDoctorModel(config, path.toFile(), new UnconnectedCache());
} else {
featureCache.resetCache();
ImplicitGeometryHolder.resetObjectMaps();
model = new CityDoctorModel(config, path.toFile(), featureCache);
}
neighborDistance = 1.8d / Math.pow(10, config.getNumberOfRoundingPlaces());
}
......@@ -203,6 +204,32 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
@Override
public void visit(OtherConstruction gmlOc){
OtherConstructionObject oco = new OtherConstructionObject();
oco.setGmlObject(gmlOc);
mapAbstractOccupiedSpace(gmlOc, oco, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlOc.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
continue;
}
AbstractSpaceBoundary surface = surfaceProp.getObject();
surface.accept(surfaceMapper);
}
for (BoundarySurface bs : surfaceMapper.getSurfaces()) {
oco.addBoundarySurface(bs);
for (Geometry geom : bs.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
p.setPartOfSurface(bs);
}
}
}
finishCityObjectConstruction(oco);
model.addOtherCityObject(oco);
}
@Override
public void visit(org.citygml4j.core.model.cityfurniture.CityFurniture gmlCityFurniture) {
CityFurniture cf = new CityFurniture();
......@@ -230,7 +257,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
mapAbstractOccupiedSpace(gos, gco, Orientation.OUTWARD);
finishCityObjectConstruction(gco);
model.addGenericCityObject(gco);
model.addOtherCityObject(gco);
}
@Override
......@@ -1024,11 +1051,6 @@ public class Citygml3FeatureMapper extends ObjectWalker {
if (directory != null){
Path libraryObjectPath = directory.resolve(ig.getLibraryObject());
libObj = LibraryObject.of(libraryObjectPath, config);
} else {
String libraryObjectPath = ig.getLibraryObject();
Path loPath = zipEntry.resolveLibraryObjectPath(libraryObjectPath);
CityGmlZipEntry libEntry = zipEntry.getArchive().getEntry(loPath.toString());
libObj = LibraryObject.of(libEntry, config);
}
if (libObj != null) {
igh = new ImplicitGeometryHolder(ig, libObj);
......
......@@ -54,8 +54,7 @@ public class SurfaceMapper extends ObjectWalker {
@Override
public void visit(WallSurface wallSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL,
wallSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.WALL, wallSurface);
handleConstructionSurface(wallSurface, surface);
surfaces.add(surface);
}
......@@ -74,11 +73,11 @@ public class SurfaceMapper extends ObjectWalker {
private void readAbstractFillingSurface(AbstractFillingSurface afs, BoundarySurface cdSurface) {
Opening o;
if (afs instanceof DoorSurface ds) {
o = new Opening(OpeningType.DOOR, SurfaceFeatureType.BUILDING, cdSurface, ds);
o = new Opening(OpeningType.DOOR, cdSurface, ds);
} else if (afs instanceof WindowSurface ws) {
o = new Opening(OpeningType.WINDOW, SurfaceFeatureType.BUILDING, cdSurface, ws);
o = new Opening(OpeningType.WINDOW, cdSurface, ws);
} else {
o = new Opening(OpeningType.UNKNOWN, SurfaceFeatureType.BUILDING, cdSurface, afs);
o = new Opening(OpeningType.UNKNOWN, cdSurface, afs);
}
cdSurface.addOpening(o);
parseAndAddMultiSurface(afs.getLod0MultiSurface(), Lod.LOD0, o);
......@@ -144,64 +143,56 @@ public class SurfaceMapper extends ObjectWalker {
@Override
public void visit(FloorSurface floorSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.FLOOR,
floorSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.FLOOR, floorSurface);
handleConstructionSurface(floorSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(CeilingSurface ceilingSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.CEILING,
ceilingSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.CEILING, ceilingSurface);
handleConstructionSurface(ceilingSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(GroundSurface groundSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.GROUND,
groundSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.GROUND, groundSurface);
handleConstructionSurface(groundSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(InteriorWallSurface interiorWallSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.INTERIOR_WALL,
interiorWallSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.INTERIOR_WALL, interiorWallSurface);
handleConstructionSurface(interiorWallSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(OuterCeilingSurface outerCeilingSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.OUTER_CEILING,
outerCeilingSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.OUTER_CEILING, outerCeilingSurface);
handleConstructionSurface(outerCeilingSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(OuterFloorSurface outerFloorSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.OUTER_FLOOR,
outerFloorSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.OUTER_FLOOR, outerFloorSurface);
handleConstructionSurface(outerFloorSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(RoofSurface roofSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.ROOF,
roofSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.ROOF, roofSurface);
handleConstructionSurface(roofSurface, surface);
surfaces.add(surface);
}
@Override
public void visit(ClosureSurface closureSurface) {
BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.CLOSURE,
closureSurface);
BoundarySurface surface = new BoundarySurface(BoundarySurfaceType.CLOSURE, closureSurface);
handleThematicSurface(closureSurface, surface);
surfaces.add(surface);
}
......
......@@ -19,7 +19,6 @@
package de.hft.stuttgart.citydoctor2.parser;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
......@@ -44,6 +43,7 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
......@@ -79,7 +79,6 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xmlobjects.schema.SchemaHandler;
import org.xmlobjects.schema.SchemaHandlerException;
import org.xmlobjects.stream.XMLReader;
......@@ -91,8 +90,6 @@ import de.hft.stuttgart.citydoctor2.mapper.citygml3.Citygml3FeatureMapper;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.GMLValidationHandler;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipEntry;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipEntryFile;
import de.hft.stuttgart.quality.QualityADEContext;
import de.hft.stuttgart.quality.QualityADEModule;
......@@ -158,33 +155,111 @@ public class CityGmlParser {
ADERegistry adeRegistry = ADERegistry.getInstance();
adeRegistry.loadADE(new QualityADEContext());
} catch (CityGMLContextException e) {
logger.fatal("Unable to create citygml4j context", e);
logger.fatal(Localization.getText("CityGmlParser.creating4jContextFailed"), e);
throw new IllegalStateException("Unable to create citygml4j context");
} catch (ADEException e) {
logger.fatal("Unable to add ADE plugins to citygml4j", e);
logger.fatal(Localization.getText("CityGmlParser.creatingAdePluginsFailed"), e);
throw new IllegalStateException("Unable to add ADE plugins to citygml4j");
}
}
return context;
}
public static CityDoctorModel parseCityGmlFileSilently(String file, ParserConfiguration config)
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* The nested parse uses an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}, which
* is not connected to the database.
* <p/>
* Nested parsing should only be used for accessing Features in auxiliary, small CityGML files, e.g. parsing of
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject} files.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public static CityDoctorModel nestedCityGmlFileParse(String filePath, ParserConfiguration config)
throws CityGmlParseException, InvalidGmlFileException {
return parseCityGmlFile(file, config, null, null, false);
return parseCityGmlFile(filePath, config, null, null, false, true);
}
public static CityDoctorModel parseCityGmlFile(String file, ParserConfiguration config)
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config)
throws CityGmlParseException, InvalidGmlFileException {
return parseCityGmlFile(file, config, null, null, true);
return parseCityGmlFile(filePath, config, null, null);
}
public static CityDoctorModel parseCityGmlFile(String file, ParserConfiguration config, ProgressListener l)
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l)
throws CityGmlParseException, InvalidGmlFileException {
return parseCityGmlFile(file, config, l, null, true);
return parseCityGmlFile(filePath, config, l, null);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
GMLValidationHandler handler, boolean verbose) throws CityGmlParseException, InvalidGmlFileException {
GMLValidationHandler handler) throws CityGmlParseException, InvalidGmlFileException {
return parseCityGmlFile(filePath, config, l, handler, true, false);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @param verbose If verbosity is enabled the parser will write info logs to the logger
* @param unconnected If true the parser will use an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}
* to collect the parsed CityObjects
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
private static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
GMLValidationHandler handler, boolean verbose, boolean unconnected)
throws CityGmlParseException, InvalidGmlFileException {
CityGMLContext context = getContext();
Path file = Paths.get(filePath);
if (config.getValidate()) {
......@@ -195,14 +270,14 @@ public class CityGmlParser {
}
try {
parseEpsgCodeFromFile(file, config);
parseEpsgCodeFromFile(file, config, verbose);
CityGMLInputFactory in = context.createCityGMLInputFactory()
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
if (l != null) {
ois.addListener(l::updateProgress);
}
return readAndKeepFeatures(config, file, in, ois, verbose);
return readAndKeepFeatures(config, file, in, ois, verbose, unconnected);
}
} catch (CityGMLReadException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML file", e);
......@@ -210,118 +285,6 @@ public class CityGmlParser {
}
public static CityDoctorModel parseCityGmlZipEntry(CityGmlZipEntry entry, ParserConfiguration config)
throws CityGmlParseException, InvalidGmlFileException, IOException {
return parseCityGmlZipEntry(entry, config, null);
}
public static CityDoctorModel parseCityGmlZipEntry(CityGmlZipEntry entry, ParserConfiguration config, ProgressListener l)
throws CityGmlParseException, InvalidGmlFileException, IOException {
CityGMLContext context = getContext();
if (config.getValidate()) {
try (CityGmlZipEntryFile entryFile = new CityGmlZipEntryFile(entry)) {
List<String> messages = validateStream(entryFile.getInputStream(), context);
if (!messages.isEmpty()) {
throw new InvalidGmlFileException("Invalid GML File. First error: \n" + messages.get(0));
}
} catch (Exception e) {
throw new CityGmlParseException(e);
}
}
return decompressAndParseCityGmlEntry(entry, config, l, context);
}
public static CityDoctorModel decompressAndParseCityGmlEntry(CityGmlZipEntry entry, ParserConfiguration config, CityGMLContext context)
throws CityGmlParseException {
return decompressAndParseCityGmlEntry(entry, config, null, context);
}
public static CityDoctorModel decompressAndParseCityGmlEntry(CityGmlZipEntry entry, ParserConfiguration config, ProgressListener l, CityGMLContext context)
throws CityGmlParseException {
try (CityGmlZipEntryFile entryFile = new CityGmlZipEntryFile(entry)) {
BufferedInputStream bis = new BufferedInputStream(entryFile.getInputStream());
readEpsgCodeFromInputStream(bis, config);
CityGMLInputFactory in = context.createCityGMLInputFactory()
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
try (ObservedInputStream ois = new ObservedInputStream(bis, bis.available())) {
if (l != null) {
ois.addListener(l::updateProgress);
}
return readAndKeepFeatures(config, entry, in, ois, false);
}
} catch (CityGMLReadException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML file", e);
} catch (Exception e) {
throw new CityGmlParseException(e);
}
}
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, CityGmlZipEntry entry,
CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
return readAndKeepModel(new Citygml3FeatureMapper(config, entry), inputFactory, ois, verbose);
}
private static List<String> validateStream(InputStream vis, CityGMLContext context) throws CityGmlParseException {
GMLValidationHandler handler = new GMLValidationHandler();
try {
BufferedInputStream bis = new BufferedInputStream(vis);
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, bis, schemaHandler);
Source[] schemas = schemaHandler.getSchemas();
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Schema schema = schemaFactory.newSchema(schemas);
Validator validator = schema.newValidator();
validator.setErrorHandler(handler);
validator.validate(new StreamSource(bis));
return handler.getMessages();
} catch (SchemaHandlerException | SAXException | IOException e) {
throw new CityGmlParseException("Failed to validate CityGML file", e);
}
}
private static void readAdditionalSchemaDefinitions(CityGMLContext context, BufferedInputStream bis, SchemaHandler schemaHandler)
throws CityGmlParseException {
bis.mark(Integer.MAX_VALUE);
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects())
.withSchemaHandler(schemaHandler)
.createReader(bis)) {
reader.nextTag();
bis.reset();
} catch (Exception e) {
throw new CityGmlParseException("Failed to read Schema from stream.", e);
}
}
private static void readEpsgCodeFromInputStream(BufferedInputStream bis, ParserConfiguration config) throws CityGmlParseException {
try {
// Mark start position of GML-"file"
bis.mark(Integer.MAX_VALUE);
// Buffer the first 10000 chars of the Stream, EPSG/envelope info should be found within that range
int peekingWidth = Math.min(10000, bis.available());
byte[] buf = new byte[peekingWidth];
bis.read(buf, 0, peekingWidth);
bis.reset();
parseEpsgCodeFromBuffer(buf, config);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML file", e);
}
}
public static void streamCityGml(CityGmlZipEntry entry, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
String outputFile) throws CityGmlParseException, IOException {
try (CityGmlZipEntryFile entryFile = new CityGmlZipEntryFile(entry)) {
parseEpsgCodeFromStream(entryFile.getInputStream(), config);
startReadingCityGmlZipEntry(entry, config, null, cityObjectConsumer, outputFile);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML stream", e);
}
}
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
String outputFile) throws CityGmlParseException {
......@@ -336,7 +299,7 @@ public class CityGmlParser {
public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
parseEpsgCodeFromFile(file, config);
parseEpsgCodeFromFile(file, config, true);
startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile);
}
......@@ -374,31 +337,11 @@ public class CityGmlParser {
private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file);
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile);
}
private static void startReadingCityGmlZipEntry(CityGmlZipEntry entry, ParserConfiguration config, ProgressListener l,
CityGmlConsumer cityObjectConsumer, String outputFile) {
try (CityGmlZipEntryFile entryFile = new CityGmlZipEntryFile(entry);
ObservedInputStream ois = new ObservedInputStream(entryFile.getInputStream(), entry.getFileSize())) {
if (l != null) {
ois.addListener(l::updateProgress);
}
streamAndDiscardFeatures(entry, config, ois, cityObjectConsumer, outputFile);
} catch (IOException | CityGMLReadException e) {
logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
logger.catching(Level.ERROR, e);
}
}
private static void streamAndDiscardFeatures(CityGmlZipEntry entry, ParserConfiguration config, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, entry);
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile);
}
private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
getContext();
......@@ -485,8 +428,9 @@ public class CityGmlParser {
}
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
return readAndKeepModel(new Citygml3FeatureMapper(config, file), inputFactory, ois, verbose);
CityGMLInputFactory inputFactory, ObservedInputStream ois,
boolean verbose, boolean unconnected) throws CityGMLReadException {
return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
}
private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory,
......@@ -497,54 +441,75 @@ public class CityGmlParser {
// object members are replaced by href in model
// need to remove the refs and re-add unparsed objects
List<AbstractCityObject> acos = new ArrayList<>();
while (reader.hasNext()) {
AbstractFeature chunk = reader.next();
version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
if (chunk instanceof CityModel cModel) {
cModel.setCityObjectMembers(null);
mapper.setCityModel(cModel);
mapper.setCityGMLVersion(version);
} else if (chunk instanceof AbstractCityObject aco) {
acos.add(aco);
aco.accept(mapper);
CityModel cgmlModel = null;
CityDoctorModel cdModel = mapper.getModel();
AutoCloseable parsingContext = cdModel.getCache().startParsingMode();
try (parsingContext){
List<AbstractCityObject> queuedObjects = new ArrayList<>();
while (reader.hasNext()) {
AbstractFeature chunk = reader.next();
version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
if (chunk instanceof CityModel cModel) {
cModel.setCityObjectMembers(null);
mapper.setCityModel(cModel);
mapper.setCityGMLVersion(version);
cgmlModel = cModel;
if (!queuedObjects.isEmpty()) {
for (AbstractCityObject aco : queuedObjects) {
if (!cdModel.containsGmlId(aco.getId())) {
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
}
}
}
} else if (chunk instanceof AbstractCityObject aco) {
acos.add(aco); // getid
String id = aco.getId();
aco.accept(mapper);
if (cgmlModel == null) {
// Add object to queue till the CityModel has been parsed
queuedObjects.add(aco);
}else if (!cdModel.containsGmlId(id)){
cgmlModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
}
}
}
}
if (mapper.getModel().getCityModel() == null) {
// file does not contain a city model?
// create it for now
mapper.setCityModel(new CityModel());
}
CityModel cModel = mapper.getModel().getCityModel();
// remove those that should have been parsed
List<AbstractCityObject> parsedCityObjects = mapper.getModel().createFeatureStream()
.map(CityObject::getGmlObject).toList();
acos.removeAll(parsedCityObjects);
// re-add all not parsed objects
for (AbstractCityObject aco : acos) {
cModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
}
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.parsedObjects"),
mapper.getModel().getNumberOfFeatures());
if (mapper.getModel().getCityModel() == null) {
// file does not contain a city model?
// create it for now
logger.warn(Localization.getText("CityGmlParser.missingCityModel"));
mapper.setCityModel(new CityModel());
for (AbstractCityObject aco : queuedObjects) {
if (!cdModel.containsGmlId(aco.getId())) {
mapper.getModel().getCityModel().getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
}
}
}
ImplicitGeometryHolder.resolveDeferredObjects(mapper.getModel().getCache());
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.parsedObjects"),
mapper.getModel().getNumberOfFeatures());
}
mapper.setCityGMLVersion(version);
return mapper.getModel();
} catch (Exception e) {
throw new CityGMLReadException(e);
}
// After parsing is complete, try to resolve deferred RelativeGeometries
ImplicitGeometryHolder.resolveDeferredObjects();
mapper.setCityGMLVersion(version);
return mapper.getModel();
}
}
private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config) throws CityGmlParseException {
private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config, boolean verbose) throws CityGmlParseException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
parseEpsgCodeFromStream(bis, config);
parseEpsgCodeFromStream(bis, config, verbose);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML file", e);
}
}
private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config)
private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config, boolean verbose)
throws ParserConfigurationException, SAXException {
SAXParser parser = FACTORY.newSAXParser();
CityGmlHandler handler = new CityGmlHandler();
......@@ -552,46 +517,19 @@ public class CityGmlParser {
parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) {
try {
parseCoordinateSystem(config, handler);
parseCoordinateSystem(config, handler, verbose);
} catch (Exception e2) {
logEpsgParseError(e2);
}
} catch (Exception e) {
logEpsgParseError(e);
}
if (handler.getEpsg() == null && logger.isInfoEnabled()) {
if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
}
}
private static void parseEpsgCodeFromBuffer(byte[] buffer, ParserConfiguration config)
throws ParserConfigurationException, SAXException {
InputStream is = new ByteArrayInputStream(buffer);
SAXParser parser = FACTORY.newSAXParser();
CityGmlHandler handler = new CityGmlHandler();
try {
parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) {
try {
parseCoordinateSystem(config, handler);
} catch (Exception e2) {
logEpsgParseError(e2);
}
} catch (SAXParseException spe) {
// suppress XML document structure warning
if (!spe.getMessage().matches("XML document structures must start and end within the same entity.")) {
logEpsgParseError(spe);
}
} catch (Exception e) {
logEpsgParseError(e);
}
if (handler.getEpsg() == null && logger.isInfoEnabled()) {
logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
}
}
private static void logEpsgParseError(Exception e) {
logger.debug("Exception while parsing for EPSG code", e);
if (logger.isWarnEnabled()) {
......@@ -599,7 +537,7 @@ public class CityGmlParser {
}
}
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler) {
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
if (handler.getEpsg() == null) {
return;
}
......@@ -607,14 +545,14 @@ public class CityGmlParser {
if (crs == null) {
// could not find a coordinate system for srsName
// assuming metric system
if (logger.isInfoEnabled()) {
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
}
return;
}
if (crs.getProjection().getUnits() == Units.METRES) {
// coordinate system is in meters, do not convert
if (logger.isInfoEnabled()) {
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.noConversionNeeded"));
}
return;
......@@ -639,11 +577,15 @@ public class CityGmlParser {
CoordinateReferenceSystem utm;
if (centerLat < 0) {
// south
logger.info("Converting coordiante system to UTM zone {}S", zone);
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneS"), zone);
}
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
} else {
// north
logger.info("Converting coordiante system to UTM zone {}N", zone);
if (logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.convertCrsToUtmZoneN"), zone);
}
utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
}
config.setCoordinateSystem(crs, utm);
......@@ -759,22 +701,14 @@ public class CityGmlParser {
}
private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
drainCityObjectList(model.getBuildings(), cityObjectConsumer);
drainCityObjectList(model.getBridges(), cityObjectConsumer);
drainCityObjectList(model.getVegetation(), cityObjectConsumer);
drainCityObjectList(model.getLand(), cityObjectConsumer);
drainCityObjectList(model.getTransportation(), cityObjectConsumer);
drainCityObjectList(model.getWater(), cityObjectConsumer);
drainCityObjectList(model.getTunnels(), cityObjectConsumer);
drainCityObjectList(model.getCityFurniture(), cityObjectConsumer);
drainCityObjectList(model.getGenericCityObjects(), cityObjectConsumer);
}
private static void drainCityObjectList(List<? extends CityObject> objects, CityGmlConsumer cityObjectConsumer) {
for (CityObject co : objects) {
List<GmlId> id = new ArrayList<>();
model.createFeatureStream().forEach(co -> {
cityObjectConsumer.accept(co);
}
objects.clear();
id.add(co.getGmlId());
});
id.forEach(model::removeFeature);
}
}
package de.hft.stuttgart.citydoctor2.utils;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipArchive;
import de.hft.stuttgart.citydoctor2.zip.CityGmlZipEntry;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ArchivePacker {
private static final Logger logger = LogManager.getLogger(ArchivePacker.class);
private ArchivePacker(){}
public static void packArchive(String targetPath, CityGmlZipArchive archive){
Path outputPath = Path.of(targetPath);
outputPath = outputPath.getParent().resolve(
FilenameUtils.removeExtension(outputPath.getFileName().toString()) + ".zip");
Path tmpDir = null;
try{
tmpDir = Files.createTempDirectory("zipTmp");
tmpDir.toFile().deleteOnExit();
for (CityGmlZipEntry entry : archive.getEntries()) {
if (entry.getErrorType() != null || !entry.isLoaded()) {
continue;
}
CityDoctorModel model = entry.getModel();
Path filePath = tmpDir.resolve(entry.getEntrySubPath());
Files.createDirectories(filePath.getParent());
model.saveAs(filePath.toString(), model.isValidated());
}
zipFolder(tmpDir.toFile(), outputPath.toFile());
}catch (Exception e){
logger.error(e);
} finally {
if (tmpDir != null) {
try {
FileUtils.deleteDirectory(tmpDir.toFile());
} catch (IOException e) {
logger.error(e);
}
}
}
}
public static void zipFolder(File folder, File zipFile) throws IOException {
zipFolder(folder, new FileOutputStream(zipFile));
}
public static void zipFolder(File folder, OutputStream outputStream) throws IOException {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
processFolder(folder, zipOutputStream, folder.getPath().length() + 1);
}
}
private static void processFolder(File folder, ZipOutputStream zipOutputStream, int prefixLength)
throws IOException {
for (File file : folder.listFiles()) {
if (file.isFile()) {
ZipEntry zipEntry = new ZipEntry(file.getPath().substring(prefixLength));
zipOutputStream.putNextEntry(zipEntry);
try (FileInputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, zipOutputStream);
}
zipOutputStream.closeEntry();
} else if (file.isDirectory()) {
processFolder(file, zipOutputStream, prefixLength);
}
}
}
public static void packAndDeleteDirectory(String directoryPath) throws IOException {
Path sourcePath = Path.of(directoryPath);
if (!Files.exists(sourcePath)) {
throw new IllegalStateException("Directory " + directoryPath + " does not exist");
}
if (!Files.isDirectory(sourcePath)) {
throw new IllegalStateException("Path " + directoryPath + " is not a directory");
}
Path outputPath = sourcePath.getParent().resolve(sourcePath.getFileName() + ".zip");
zipFolder(sourcePath.toFile(), outputPath.toFile());
FileUtils.deleteDirectory(sourcePath.toFile());
}
}
......@@ -97,7 +97,6 @@ public class BoundingBoxCalculator {
* @return the bounding box of the model
*/
public static BoundingBox calculateBoundingBox(CityDoctorModel model) {
// TODO: Rework to Visitor
MinMaxExtentVisitor mima = new MinMaxExtentVisitor();
model.accept(mima);
......
package de.hft.stuttgart.citydoctor2.utils;
import java.text.ChoiceFormat;
public final class TimeFormatter {
private TimeFormatter(){}
private static final String LESS_THAN_A_SECOND =
Localization.getText("TimeFormatter.lessThanASecond");
private static final ChoiceFormat SECONDS =
new ChoiceFormat(
new double[]{1, 2},
new String[]{Localization.getText("TimeFormatter.oneSecond"),
Localization.getText("TimeFormatter.seconds")}
);
private static final ChoiceFormat MINUTES =
new ChoiceFormat(
new double[]{1, 2},
new String[]{Localization.getText("TimeFormatter.oneMinute"),
Localization.getText("TimeFormatter.minutes")}
);
private static final ChoiceFormat HOURS =
new ChoiceFormat(
new double[] {1, 2},
new String[]{Localization.getText("TimeFormatter.oneHour"),
Localization.getText("TimeFormatter.hours")}
);
/**
* Formats a time measurement in milliseconds as a localized String representation.
*
* <p>
* The String representation will return the time as a rounded down integer of the biggest fitting TimeUnit
* (seconds, minutes or hours), returning "< 1 second" for times under a second.
* <p/>
* @param millis
* @return A localized String representation of the timespan
*/
public static String format(long millis) {
if (millis < 1000) {
return LESS_THAN_A_SECOND;
}else if (millis < 60_000) {
long seconds = millis / 1000;
String pattern = SECONDS.format(seconds);
return pattern.replace("{0}", Long.toString(seconds));
}else if (millis < 3_600_000){
long minutes = millis / 60_000;
String pattern = MINUTES.format(minutes);
return pattern.replace("{0}", Long.toString(minutes));
} else {
long hours = millis / 3_600_000;
String pattern = HOURS.format(hours);
return pattern.replace("{0}", Long.toString(hours));
}
}
}
\ No newline at end of file
......@@ -22,12 +22,12 @@ import java.io.File;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
......@@ -92,7 +92,7 @@ public class CityGMLWriterUtils {
storeCityObjects(model.getTransportation(), gmlFactory, model, cModel, val);
storeCityObjects(model.getWater(), gmlFactory, model, cModel, val);
storeCityObjects(model.getCityFurniture(), gmlFactory, model, cModel, val);
storeCityObjects(model.getGenericCityObjects(), gmlFactory, model, cModel, val);
storeCityObjects(model.getOtherCityObjects(), gmlFactory, model, cModel, val);
storeCityObjects(model.getTunnels(), gmlFactory, model, cModel, val);
writer.write(cModel);
......@@ -135,12 +135,13 @@ public class CityGMLWriterUtils {
de.hft.stuttgart.quality.model.enums.ErrorId adeId = QualityADEUtils.mapErrorIdToAdeId(e.getKey());
if (adeId == null) {
// error that is not part of the ade standard
logger.warn("Found error {} that cannot be converted to ADE error. It will not be written", e.getKey());
logger.warn(Localization.getText("CityGmlWriterUtils.unknownADEError"), e.getKey());
continue;
}
stats.setName(adeId);
statistics.getErrors().add(new ErrorProperty(stats));
}
// TODO: Quality ADE update to CityGML 3.0
statistics.setNumErrorBuildings(new FeatureStatisticsProperty(countValidatedCityObjects(model.getBuildings())));
statistics.setNumErrorBridgeObjects(new FeatureStatisticsProperty(countValidatedCityObjects(model.getBridges())));
statistics.setNumErrorLandObjects(new FeatureStatisticsProperty(countValidatedCityObjects(model.getLand())));
......@@ -152,29 +153,30 @@ public class CityGMLWriterUtils {
return val;
}
private static FeatureStatistics countValidatedCityObjects(List<? extends CityObject> cos) {
int numChecked = 0;
int numError = 0;
for (CityObject co : cos) {
private static FeatureStatistics countValidatedCityObjects(Stream<? extends CityObject> cos) {
AtomicInteger numChecked = new AtomicInteger();
AtomicInteger numError = new AtomicInteger();
cos.forEach(co -> {
if (co.isValidated()) {
numChecked++;
numChecked.getAndIncrement();
if (co.containsAnyError()) {
numError++;
numError.getAndIncrement();
}
}
}
return new FeatureStatistics(numChecked, numError);
});
return new FeatureStatistics(numChecked.get(), numError.get());
}
private static void storeCityObjects(List<? extends CityObject> cos, GeometryFactory gmlFactory, CityDoctorModel model, CityModel cModel, Validation val) {
for (CityObject co : cos) {
private static void storeCityObjects(Stream<? extends CityObject> cos, GeometryFactory gmlFactory, CityDoctorModel model, CityModel cModel, Validation val) {
cos.forEach(co -> {
if (model.isValidated() && val != null) {
QualityADEUtils.writeQualityAde(co, val);
}
co.reCreateGeometries(gmlFactory, model.getParserConfig());
cModel.getCityObjectMembers().add(new AbstractCityObjectProperty(co.getGmlObject()));
}
});
}
}
package de.hft.stuttgart.citydoctor2.zip;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.ArchivePacker;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
* This class serves as a representation of a ZIP-archive containing CityGml files. The contained files are represented
* by {@link CityGmlZipEntry} objects.
*/
public class CityGmlZipArchive implements Serializable {
private static final Logger logger = LogManager.getLogger(CityGmlZipArchive.class);
@Serial
private static final long serialVersionUID = 2168389511043362615L;
private List<CityGmlZipEntry> entries;
private final Path archivePath;
private final String archiveNameRE;
/**
* Registers a ZIP-file as an archive. All contained XML and GML files will be registered as {@link CityGmlZipEntry} objects.
*
* @param zipFile path of the ZIP-file
* @return A {@link CityGmlZipArchive} object representing the archive, or null if read-access for the ZIP-file was denied
* @throws MalformedZipFileException if the ZIP-file has encoding errors
*/
public static CityGmlZipArchive register(String zipFile) throws MalformedZipFileException {
ArrayList<CityGmlZipEntry> archiveEntries = new ArrayList<>();
CityGmlZipArchive cgmlArchive = new CityGmlZipArchive(Path.of(zipFile));
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
if (ze.isDirectory()) {
continue;
}
if (ze.getName().endsWith(".gml") || ze.getName().endsWith(".xml")) {
// Ignore report files generated by CityDoctor
if (ze.getName().endsWith("_report.xml")) {
continue;
}
archiveEntries.add(CityGmlZipEntry.register(ze, cgmlArchive));
}
}
cgmlArchive.setEntries(archiveEntries);
return cgmlArchive;
} catch (IOException e) {
logger.error(Localization.getText("CityGmlZipArchive.ioError"));
logger.debug(e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
logger.error(Localization.getText("CityGmlZipArchive.malformedZipFile"));
throw new MalformedZipFileException(Localization.getText("CityGmlZipArchive.malformedZipFile"));
} catch (Exception e) {
logger.error(Localization.getText("CityGmlZipArchive.generalException"));
logger.debug(e.getMessage(), e);
throw (e);
}
}
/**
* Attempts to load all contained {@link CityGmlZipEntry} objects into memory.
*
* @param config Parser configuration for the entries
*/
public void mountArchive(ParserConfiguration config) {
try (ZipFile zip = new ZipFile(archivePath.toFile())) {
for (CityGmlZipEntry entry : entries) {
entry.loadEntry(config);
}
} catch (IOException e) {
logger.error(e);
}
}
private CityGmlZipArchive(Path archivePath) {
this.archivePath = archivePath;
this.archiveNameRE = archivePath.getFileName().toString().replace(".zip", "") + File.separator;
}
private void setEntries(List<CityGmlZipEntry> entries) {
this.entries = entries;
}
/**
* Saves this archive as a ZIP-file.
* @param path target path for the export
*/
public void exportToZipFile(String path) {
ArchivePacker.packArchive(path, this);
}
/**
* Gets an entry in this archive by its subpath.
*
* @param subpath subpath to the entry from this archive's root directory
* @return the corresponding entry, or null if entry could not be found
*/
public CityGmlZipEntry getEntry(String subpath) {
subpath = stripArchivePath(subpath);
for (CityGmlZipEntry entry : entries) {
String entryName = stripArchivePath(entry.getEntrySubPath());
if (entryName.equals(subpath)) {
return entry;
}
}
return null;
}
/**
* Strips the root directory name of this archive from an entry subpath.
* @param path the relative path to the entry
* @return String of the path without the root directory
*/
private String stripArchivePath(String path) {
Path systemPath = Path.of(path);
return systemPath.toString().replace(archiveNameRE, "");
}
/**
* Gets the path to the ZIP-file associated with this archive.
* @return path of the ZIP-file
*/
public Path getArchivePath() {
return archivePath;
}
/**
* Gets the entry list of this archive.
* @return the entry list
*/
public List<CityGmlZipEntry> getEntries() {
return entries;
}
}
package de.hft.stuttgart.citydoctor2.zip;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.parser.ProgressListener;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
/**
* This class serves as a representation of a CityGml file in a ZIP archive.
* <p/>
* The {@link CityDoctorModel} of the associated file can only be accessed after loading the corresponding entry.
*/
public class CityGmlZipEntry implements Serializable {
private static final Logger logger = LogManager.getLogger(CityGmlZipEntry.class);
@Serial
private static final long serialVersionUID = -5732913269959043262L;
private final Path filePath;
private final String fileName;
private final String displayName;
private transient CityDoctorModel model;
private CityGmlZipArchive parentArchive;
private boolean inMemory;
private long fileSize = -1L;
private static final long MB = 1024 * 1024L;
private ZipEntryErrorType errorType = null;
/**
* Attempts to parse the CityGML model associated with this entry and loads it into memory.
* <p/>
* In the event of a parsing error, the entry will be flagged by assigning the appropriate
* {@link ZipEntryErrorType} to the errorType field.
*
* @param config Parser configuration for this model
*/
public void loadEntry(ParserConfiguration config) {
loadEntry(config, null);
}
/**
* Attempts to parse the CityGML model associated with this entry and loads it into memory.
* <p/>
* In the event of a parsing error, the entry will be flagged by assigning the appropriate
* {@link ZipEntryErrorType} to the errorType field.
*
* @param config Parser configuration for this model
* @param l ProgressListener for tracking parsing progress
*/
public void loadEntry(ParserConfiguration config, ProgressListener l) {
if (inMemory) {
return;
}
if (errorType != null) {
logger.warn("Tried loading erroneous CityGmlZipEntry");
return;
}
try {
this.model = CityGmlParser.parseCityGmlZipEntry(this, config, l);
this.inMemory = true;
} catch (CityGmlParseException | InvalidGmlFileException e) {
logger.error(e);
this.errorType = ZipEntryErrorType.INVALID_CITY_GML_FILE;
} catch (IOException e) {
logger.error(e);
this.errorType = ZipEntryErrorType.IO_ERROR;
}
}
/**
* Registers a {@link ZipEntry ZipEntry} in a ZIP-archive as a {@link CityGmlZipEntry}.
* <p/>
* If the filesize of the associated file exceeds the total available system memory, or read access is denied,
* the entry will be flagged by assigning the appropriate {@link ZipEntryErrorType} to the errorType field.
*
* @param entry The ZipEntry object
* @param parentArchive The parent archive for this entry
* @return A corresponding CityGmlZipEntry object
*/
public static CityGmlZipEntry register(ZipEntry entry, CityGmlZipArchive parentArchive) {
CityGmlZipEntry cgzEntry = new CityGmlZipEntry(entry, parentArchive, false);
try {
if (!cgzEntry.entrySizeWithinMemoryLimits()) {
cgzEntry.errorType = ZipEntryErrorType.EXCESSIVE_FILESIZE;
}
} catch (IOException e) {
logger.error(e);
cgzEntry.errorType = ZipEntryErrorType.IO_ERROR;
}
return cgzEntry;
}
/**
* Checks if the filesize of this entry would exceed the system's available memory.
*
* @return true if within limits, false otherwise
* @throws IOException if read access to the ZIP-archive is denied
*/
private boolean entrySizeWithinMemoryLimits() throws IOException {
long memoryLimit = (long) Math.ceil(((double) Runtime.getRuntime().maxMemory() / MB) * 0.9);
if (fileSize == -1L) {
try (CityGmlZipEntryFile entryFile = new CityGmlZipEntryFile(this)) {
long filesize = entryFile.getFileSize();
if (filesize != -1) {
this.fileSize = filesize;
} else {
return false;
}
} catch (Exception e) {
throw new IOException(e);
}
}
return memoryLimit > (fileSize / MB);
}
private CityGmlZipEntry(ZipEntry entry, CityGmlZipArchive parentArchive, boolean inMemory) {
this.fileName = entry.getName();
this.filePath = Path.of(entry.getName());
this.displayName = filePath.getFileName().toString();
if (entry.getSize() != -1) {
this.fileSize = entry.getSize();
}
this.model = null;
this.inMemory = inMemory;
this.parentArchive = parentArchive;
}
/**
* Gets the parent archive of this entry.
* @return the archive
*/
public CityGmlZipArchive getArchive() {
return parentArchive;
}
/**
* Gets the sub-path in the archive of the file associated with this entry.
*
* @return the relative sub-path from the archive root directory
*/
public String getEntrySubPath() {
return fileName;
}
/**
* Gets the name of the file associated with this entry.
* @return the filename
*/
public String getDisplayName() {
return displayName;
}
/**
* Gets the {@link ZipEntryErrorType} of this entry.
* @return the error type, or null if this entry is not erroneous
*/
public ZipEntryErrorType getErrorType() {
return errorType;
}
/**
* Gets the {@link CityDoctorModel} of this entry.
* @return The model, or null if the entry has not been loaded or is erroneous
*/
public CityDoctorModel getModel() {
return model;
}
public void setFileSize(long size) {
fileSize = size;
}
/**
* Resolves the reference path of a {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject}
* in the {@link CityDoctorModel} of this entry.
* @param libraryObjectPath The reference path to the LibraryObject's CityGml file
* @return Relative sub-path within the archive to the CityGml file
*/
public Path resolveLibraryObjectPath(String libraryObjectPath) {
return filePath.getParent().resolve(libraryObjectPath);
}
/**
* Checks if the {@link CityDoctorModel} of this entry has been loaded into memory.
*
* @return true if it is loaded, false otherwise
*/
public boolean isLoaded() {
return inMemory;
}
/**
* Returns the estimated uncompressed filesize of this entry.
*
* @return filesize in bytes, -1L if filesize is unknown
*/
public long getFileSize() {
return fileSize;
}
}
package de.hft.stuttgart.citydoctor2.zip;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* This class serves as a resource for read-access to the file that is being represented by
* a CityGmlZipEntry object.
* <p/>
* Does not decompress the file or the parent ZIP-archive.
*/
public class CityGmlZipEntryFile implements AutoCloseable {
private final ZipFile zip;
private final ZipEntry zipEntry;
private final CityGmlZipEntry cgmlZipEntry;
private boolean closed = false;
/**
* Creates a {@link CityGmlZipEntryFile} for a {@link CityGmlZipEntry} object's referenced file.
*
* @param entry the requested entry
* @throws IOException if read access to the ZIP-archive is denied.
*/
public CityGmlZipEntryFile(CityGmlZipEntry entry) throws IOException {
CityGmlZipArchive archive = entry.getArchive();
zip = new ZipFile(archive.getArchivePath().toFile());
zipEntry = zip.getEntry(entry.getEntrySubPath());
this.cgmlZipEntry = entry;
}
/**
* Gets a new InputStream of this file.
* <p/>
* Returned streams are independent of each other, but will all be closed when the {@link CityGmlZipEntryFile} object
* is closed.
* @return an InputStream of this file.
* @throws IOException when this method is called after the file has been closed.
*/
public InputStream getInputStream() throws IOException {
if (closed){
throw new IOException("CityGmlZipEntryFile closed");
}
return zip.getInputStream(zipEntry);
}
/**
* Gets an estimation of this file's filesize.
* @return filesize in bytes, -1L if unknown
* @throws IOException when this method is called after the file has been closed.
*/
public long getFileSize() throws IOException {
if (closed){
throw new IOException("CityGmlZipEntryFile closed");
}
if (cgmlZipEntry.getFileSize() == -1L) {
if (zipEntry.getSize() == -1) {
long bytes = 0;
InputStream is = new BufferedInputStream(this.getInputStream());
for (int i = is.read(); i != -1; i = is.read()) {
bytes++;
}
cgmlZipEntry.setFileSize(bytes);
} else {
cgmlZipEntry.setFileSize(zipEntry.getSize());
}
}
return cgmlZipEntry.getFileSize();
}
@Override
public void close() throws IOException {
zip.close();
closed = true;
}
}
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