Commit 297334ab authored by Matthias Betz's avatar Matthias Betz
Browse files

Add design spec for OpenGL 3D view



Replaces the JavaFX SubScene/MeshView rendering with an OpenGL viewport
(openglfx-jogl, GL 3.3 core), single global VBO, GPU color-picking, and
baked fake shading. Windows/Linux targets; JavaFX 3D path removed.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parents 5c2847ef bd40b396
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.utils.CopyHandler;
import de.hft.stuttgart.citydoctor2.utils.Copyable;
import java.io.Serial;
public class TunnelPart extends AbstractTunnel {
......@@ -12,6 +9,8 @@ public class TunnelPart extends AbstractTunnel {
private Tunnel parent;
private TunnelPart() {
}
public TunnelPart(Tunnel parent) {
this.parent = parent;
......@@ -22,6 +21,11 @@ public class TunnelPart extends AbstractTunnel {
return parent;
}
@Override
public CityObject getTopLevelCityObject() {
return getParent().getTopLevelCityObject();
}
@Override
public FeatureType getFeatureType() {
return FeatureType.TUNNEL_PART;
......@@ -32,24 +36,4 @@ public class TunnelPart extends AbstractTunnel {
return "TunnelPart [id=" + getGmlId() + "]";
}
@Override
public void fillValues(Copyable original, CopyHandler handler) {
super.fillValues(original, handler);
TunnelPart originalPart = (TunnelPart) original;
parent = handler.getCopyInstance(originalPart.parent);
}
@Override
public void collectInstances(CopyHandler handler) {
super.collectInstances(handler);
handler.addInstance(parent);
}
@Override
public Copyable createCopyInstance() {
return new TunnelPart();
}
private TunnelPart() {
}
}
......@@ -18,24 +18,21 @@
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import de.hft.stuttgart.citydoctor2.utils.Copyable;
import javafx.scene.paint.Color;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.vegetation.AbstractVegetationObject;
import org.citygml4j.core.model.vegetation.PlantCover;
import org.citygml4j.core.model.vegetation.SolitaryVegetationObject;
import org.citygml4j.core.util.geometry.GeometryFactory;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSolid;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSolidProperty;
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.Collections;
/**
* Representation of CityGML vegetation objects
......@@ -64,7 +61,7 @@ public class Vegetation extends CityObject {
}
@Override
public void reCreateGeometries(GeometryFactory factory, ParserConfiguration config) {
public void rebuildGeometries(GeometryFactory factory, ParserConfiguration config) {
for (Geometry geom : getGeometries()) {
if (geom instanceof ImplicitGeometryHolder) {
continue;
......@@ -94,16 +91,13 @@ public class Vegetation extends CityObject {
private void setSolidAccordingToLod(PlantCover pc, Solid solid, Lod lod) {
switch (lod) {
case LOD1:
pc.getDeprecatedProperties().setLod1MultiSolid(new MultiSolidProperty(new MultiSolid(Collections.singletonList(new SolidProperty(solid)))));
pc.setLod1Solid(new SolidProperty(solid));
break;
case LOD2:
pc.getDeprecatedProperties().setLod2MultiSolid(new MultiSolidProperty(new MultiSolid(Collections.singletonList(new SolidProperty(solid)))));
pc.setLod2Solid(new SolidProperty(solid));
break;
case LOD3:
pc.getDeprecatedProperties().setLod3MultiSolid(new MultiSolidProperty(new MultiSolid(Collections.singletonList(new SolidProperty(solid)))));
break;
case LOD4:
pc.getDeprecatedProperties().setLod4MultiSolid(new MultiSolidProperty(new MultiSolid(Collections.singletonList(new SolidProperty(solid)))));
pc.setLod3Solid(new SolidProperty(solid));
break;
default:
throw new IllegalStateException("Cannot set Solid with lod to PlantCover:" + lod);
......@@ -113,13 +107,13 @@ public class Vegetation extends CityObject {
private void setSolidAccordingToLod(SolitaryVegetationObject svo, Solid solid, Lod lod) {
switch (lod) {
case LOD1:
svo.getDeprecatedProperties().setLod1Geometry(new SolidProperty(solid));
svo.setLod1Solid(new SolidProperty(solid));
break;
case LOD2:
svo.getDeprecatedProperties().setLod2Geometry(new SolidProperty(solid));
svo.setLod2Solid(new SolidProperty(solid));
break;
case LOD3:
svo.getDeprecatedProperties().setLod3Geometry(new SolidProperty(solid));
svo.setLod3Solid(new SolidProperty(solid));
break;
case LOD4:
svo.getDeprecatedProperties().setLod4Geometry(new SolidProperty(solid));
......@@ -131,6 +125,9 @@ public class Vegetation extends CityObject {
private void setMultiSurfaceAccordingToLod(PlantCover pc, MultiSurface ms, Lod lod) {
switch (lod) {
case LOD0:
pc.setLod0MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD1:
pc.getDeprecatedProperties().setLod1MultiSurface(new MultiSurfaceProperty(ms));
break;
......@@ -151,14 +148,17 @@ public class Vegetation extends CityObject {
private void setMultiSurfaceAccordingToLod(SolitaryVegetationObject svo, MultiSurface ms, Lod lod) {
switch (lod) {
case LOD0:
svo.setLod0MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD1:
svo.getDeprecatedProperties().setLod1Geometry(new MultiSurfaceProperty(ms));
break;
case LOD2:
svo.getDeprecatedProperties().setLod2Geometry(new MultiSurfaceProperty(ms));
svo.setLod2MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD3:
svo.getDeprecatedProperties().setLod3Geometry(new MultiSurfaceProperty(ms));
svo.setLod3MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD4:
svo.getDeprecatedProperties().setLod4Geometry(new MultiSurfaceProperty(ms));
......@@ -169,7 +169,7 @@ public class Vegetation extends CityObject {
}
@Override
public void accept(Check c) {
public void accept(CheckableVisitor c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
......@@ -181,8 +181,19 @@ public class Vegetation extends CityObject {
return citygmlVegetation;
}
@Override
public Color getRenderColor() {
return Color.LIGHTGREEN;
}
@Override
public void unsetGmlGeometries() {
citygmlVegetation.setLod0MultiSurface(null);
citygmlVegetation.setLod2MultiSurface(null);
citygmlVegetation.setLod3MultiSurface(null);
citygmlVegetation.setLod1Solid(null);
citygmlVegetation.setLod2Solid(null);
citygmlVegetation.setLod3Solid(null);
if (type == VegetationType.SOLITARY_VEGETATION_OBJECT) {
SolitaryVegetationObject svo = (SolitaryVegetationObject) citygmlVegetation;
svo.getDeprecatedProperties().setLod1Geometry(null);
......@@ -192,12 +203,19 @@ public class Vegetation extends CityObject {
} else {
PlantCover pc = (PlantCover) citygmlVegetation;
pc.getDeprecatedProperties().setLod1MultiSurface(null);
pc.setLod2MultiSurface(null);
pc.setLod3MultiSurface(null);
pc.getDeprecatedProperties().setLod4MultiSurface(null);
pc.getDeprecatedProperties().setLod1MultiSolid(null);
pc.getDeprecatedProperties().setLod2MultiSolid(null);
pc.getDeprecatedProperties().setLod3MultiSolid(null);
pc.getDeprecatedProperties().setLod4MultiSolid(null);
}
}
@Override
public CityObject getTopLevelCityObject() {
return this;
}
public void setGmlObject(AbstractVegetationObject avo) {
citygmlVegetation = avo;
}
......@@ -211,8 +229,4 @@ public class Vegetation extends CityObject {
return "Vegetation [id=" + getGmlId() + "]";
}
@Override
public Copyable createCopyInstance() {
return new Vegetation(type);
}
}
......@@ -18,28 +18,23 @@
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.CopyHandler;
import de.hft.stuttgart.citydoctor2.utils.Copyable;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
import java.io.Serial;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
/**
* Contains the vertex information of a point in a linear ring
*
* @author Matthias Betz
*/
public class Vertex extends Vector3d implements Copyable {
public class Vertex extends Vector3d {
@Serial
private static final long serialVersionUID = -5525361920397934892L;
private List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);
public Vertex(double x, double y, double z) {
super(x, y, z);
......@@ -49,52 +44,16 @@ public class Vertex extends Vector3d implements Copyable {
super(vec);
}
private Set<LinearRing> getAdjacentRingsWithoutNeighbor(Geometry geom) {
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
return adjacency.getValue1();
}
}
throw new IllegalStateException("Requested adjacent rings with Geometry not containing this vertex");
}
public Set<LinearRing> getAdjacentRings(Geometry geom) {
return getAdjacentRingsWithoutNeighbor(geom);
return geom.getAdjacentRingsOfVertex(this);
}
void addAdjacentRing(LinearRing ring, Geometry geom) {
findAdjacentRingsForGeometry(geom).add(ring);
}
private Set<LinearRing> findAdjacentRingsForGeometry(Geometry geom) {
HashSet<LinearRing> adjacendRingsSet = null;
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
adjacendRingsSet = adjacency.getValue1();
}
}
if (adjacendRingsSet == null) {
adjacendRingsSet = new HashSet<>(4);
adjacentRings.add(new SerializablePair<>(geom, adjacendRingsSet));
}
return adjacendRingsSet;
}
private Set<Polygon> getAdjacentPolygonsWithoutNeighbor(Geometry geom) {
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
Set<Polygon> polygons = new HashSet<>();
for (LinearRing lr : adjacency.getValue1()) {
polygons.add(lr.getParent());
}
return polygons;
}
}
throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
getAdjacentRings(geom).add(ring);
}
public Set<Polygon> getAdjacentPolygons(Geometry geom) {
return getAdjacentPolygonsWithoutNeighbor(geom);
return getAdjacentRings(geom).stream().map(r -> r.getParent()).collect(Collectors.toCollection(HashSet::new));
}
@Override
......@@ -121,51 +80,11 @@ public class Vertex extends Vector3d implements Copyable {
}
public void clearAdjacentRings(Geometry geometry) {
if (adjacentRings == null) {
return;
}
findAdjacentRingsForGeometry(geometry).clear();
getAdjacentRings(geometry).clear();
}
void removeAdjacency(LinearRing lr, Geometry geom) {
findAdjacentRingsForGeometry(geom).remove(lr);
}
/**
* Remove all adjacent rings from this vertex, ignoring geometry association
*/
void clearAdjacentRings() {
adjacentRings = new ArrayList<>(2);
}
@Override
public void collectInstances(CopyHandler handler) {
for (SerializablePair<Geometry, HashSet<LinearRing>> pair : adjacentRings) {
handler.addInstance(pair.getValue0());
for (LinearRing lr : pair.getValue1()) {
handler.addInstance(lr);
}
}
}
@Override
public Copyable createCopyInstance() {
return new Vertex(this);
}
@Override
public void fillValues(Copyable original, CopyHandler handler) {
Vertex originalVertex = (Vertex) original;
// vertex point data already filled
for (SerializablePair<Geometry, HashSet<LinearRing>> pair : originalVertex.adjacentRings) {
Geometry geomCopy = handler.getCopyInstance(pair.getValue0());
HashSet<LinearRing> set = new HashSet<>();
for (LinearRing originalRing : pair.getValue1()) {
set.add(handler.getCopyInstance(originalRing));
}
SerializablePair<Geometry, HashSet<LinearRing>> copyPair = new SerializablePair<>(geomCopy, set);
adjacentRings.add(copyPair);
}
getAdjacentRings(geom).remove(lr);
}
}
......@@ -18,16 +18,20 @@
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckableVisitor;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
import de.hft.stuttgart.citydoctor2.utils.CopyHandler;
import de.hft.stuttgart.citydoctor2.utils.Copyable;
import javafx.scene.paint.Color;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.model.waterbody.WaterBody;
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;
......@@ -43,26 +47,78 @@ public class WaterObject extends CityObject {
@Serial
private static final long serialVersionUID = -3821060595086337424L;
@Serial
private static final Logger logger = LogManager.getLogger(WaterObject.class);
private WaterBody gmlWater;
private final List<BoundarySurface> boundarySurfaceList = new ArrayList<>();
@Override
public void reCreateGeometries(GeometryFactory factory, ParserConfiguration config) {
public void rebuildGeometries(GeometryFactory factory, ParserConfiguration config) {
for (Geometry geom : getGeometries()) {
if (geom.getType() == GeometryType.MULTI_SURFACE) {
if (geom.getType() == GeometryType.MULTI_SURFACE || geom.getType() == GeometryType.COMPOSITE_SURFACE) {
MultiSurface ms = CityGmlUtils.createMultiSurface(geom, factory, config);
if (geom.getLod() == Lod.LOD0) {
gmlWater.setLod0MultiSurface(new MultiSurfaceProperty(ms));
} else if (geom.getLod() == Lod.LOD1) {
gmlWater.getDeprecatedProperties().setLod1MultiSurface(new MultiSurfaceProperty(ms));
} else {
throw new IllegalStateException(
"Cannot add MultiSurface geometry with lod to WaterBody:" + geom.getLod());
}
setMultiSurfaceAccordingToLod(gmlWater, ms, geom.getLod());
} else if (geom.getType() == GeometryType.SOLID) {
Solid solid = CityGmlUtils.createSolid(geom, factory, config);
setSolidAccordingToLod(gmlWater, solid, geom.getLod());
} else {
throw new IllegalStateException("Cannot add Solid geometry to WaterBody");
}
}
removeEmptyBoundarySurfaces();
}
private void setMultiSurfaceAccordingToLod(WaterBody wb, MultiSurface ms, Lod lod) {
switch (lod) {
case LOD0:
wb.setLod0MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD1:
wb.getDeprecatedProperties().setLod1MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD2:
wb.setLod2MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD3:
wb.setLod3MultiSurface(new MultiSurfaceProperty(ms));
break;
default:
throw new IllegalStateException("Cannot set MultiSurface with lod to WaterBody:" + lod);
}
}
private void setSolidAccordingToLod(WaterBody wb, Solid solid, Lod lod) {
switch (lod) {
case LOD1:
wb.setLod1Solid(new SolidProperty(solid));
break;
case LOD2:
wb.setLod2Solid(new SolidProperty(solid));
break;
case LOD3:
wb.setLod3Solid(new SolidProperty(solid));
break;
case LOD4:
wb.getDeprecatedProperties().setLod4Solid(new SolidProperty(solid));
break;
default:
throw new IllegalStateException("Cannot set MultiSurface with lod to SolitaryVegetationObject:" + lod);
}
}
private void removeEmptyBoundarySurfaces() {
for (BoundarySurface bs : boundarySurfaceList) {
if (bs.getGeometries().isEmpty()) {
for (AbstractSpaceBoundaryProperty bsp : gmlWater.getBoundaries()) {
if (bsp.getObject() != null && bsp.getObject() == bs.getGmlObject()) {
logger.warn("Found empty boundary surface: {}, removing from feature", bs.getGmlId());
gmlWater.getBoundaries().remove(bsp);
break;
}
}
}
}
}
/**
......@@ -85,7 +141,7 @@ public class WaterObject extends CityObject {
}
@Override
public void accept(Check c) {
public void accept(CheckableVisitor c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
......@@ -100,10 +156,26 @@ public class WaterObject extends CityObject {
return gmlWater;
}
@Override
public Color getRenderColor() {
return Color.LIGHTSKYBLUE;
}
@Override
public void unsetGmlGeometries() {
gmlWater.setLod0MultiSurface(null);
gmlWater.getDeprecatedProperties().setLod1MultiSurface(null);
gmlWater.setLod2MultiSurface(null);
gmlWater.setLod3MultiSurface(null);
gmlWater.setLod1Solid(null);
gmlWater.setLod2Solid(null);
gmlWater.setLod3Solid(null);
gmlWater.getDeprecatedProperties().setLod4Solid(null);
}
@Override
public CityObject getTopLevelCityObject() {
return this;
}
public void setGmlObject(WaterBody waterBody) {
......@@ -115,56 +187,4 @@ public class WaterObject extends CityObject {
return "WaterObject [id=" + getGmlId() + "]";
}
@Override
public WaterObject createCopyInstance() {
return new WaterObject();
}
@Override
public void fillValues(Copyable original, CopyHandler handler) {
WaterObject wo = (WaterObject) original;
WaterObject originalWaterObject = (WaterObject) original;
for (BoundarySurface originalBs : originalWaterObject.boundarySurfaceList) {
boundarySurfaceList.add(handler.getCopyInstance(originalBs));
}
gmlWater = wo.gmlWater;
}
@Override
public boolean containsAnyError() {
boolean hasError = super.containsAnyError();
if (hasError) {
return true;
}
for (BoundarySurface bs : boundarySurfaceList) {
if (bs.containsAnyError()) {
return true;
}
}
return false;
}
@Override
public void collectInstances(CopyHandler handler) {
super.collectInstances(handler);
handler.addInstance(boundarySurfaceList);
}
@Override
public void clearMetaInformation() {
super.clearMetaInformation();
for (BoundarySurface bs : boundarySurfaceList) {
bs.clearMetaInformation();
}
}
@Override
public void prepareForChecking() {
super.prepareForChecking();
for (BoundarySurface bs : boundarySurfaceList) {
bs.prepareForChecking();
}
}
}
package de.hft.stuttgart.citydoctor2.exceptions;
import java.io.Serial;
public class CityObjectCacheException extends RuntimeException{
@Serial
private static final long serialVersionUID = -460467145629964615L;
public CityObjectCacheException(String message) {
super(message);
}
public CityObjectCacheException(String message, Throwable cause) {
super(message, cause);
}
}
package de.hft.stuttgart.citydoctor2.exceptions;
import java.io.Serial;
public class EmbeddedDatabaseHandlerException extends RuntimeException{
@Serial
private static final long serialVersionUID = -7848325704234347425L;
public EmbeddedDatabaseHandlerException(String message) {
super(message);
}
public EmbeddedDatabaseHandlerException(String message, Throwable cause) {
super(message, cause);
}
public EmbeddedDatabaseHandlerException(Throwable cause){
super(cause);
}
}
......@@ -18,38 +18,82 @@
*/
package de.hft.stuttgart.citydoctor2.mapper.citygml3;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractTunnel;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeConstructiveElement;
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingRoom;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject.BridgeType;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelConstructiveElement;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelFurniture;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelPart;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject.TransportationType;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation.VegetationType;
import de.hft.stuttgart.citydoctor2.math.graph.KDTree;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.database.FeatureCache;
import de.hft.stuttgart.citydoctor2.database.UnconnectedCache;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.OtherConstructionObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.core.model.CityGMLVersion;
import org.citygml4j.core.model.bridge.*;
import org.citygml4j.core.model.bridge.AbstractBridge;
import org.citygml4j.core.model.bridge.Bridge;
import org.citygml4j.core.model.bridge.BridgeConstructiveElementProperty;
import org.citygml4j.core.model.bridge.BridgeInstallation;
import org.citygml4j.core.model.bridge.BridgeInstallationProperty;
import org.citygml4j.core.model.bridge.BridgePart;
import org.citygml4j.core.model.bridge.BridgePartProperty;
import org.citygml4j.core.model.building.AbstractBuildingSubdivisionProperty;
import org.citygml4j.core.model.building.BuildingConstructiveElement;
import org.citygml4j.core.model.building.BuildingConstructiveElementProperty;
import org.citygml4j.core.model.building.BuildingFurniture;
import org.citygml4j.core.model.building.BuildingFurnitureProperty;
import org.citygml4j.core.model.building.BuildingInstallationProperty;
import org.citygml4j.core.model.building.BuildingPartProperty;
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.building.*;
import org.citygml4j.core.model.construction.AbstractConstruction;
import org.citygml4j.core.model.core.*;
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;
import org.citygml4j.core.model.core.AbstractOccupiedSpace;
import org.citygml4j.core.model.core.AbstractPhysicalSpace;
import org.citygml4j.core.model.core.AbstractSpace;
import org.citygml4j.core.model.core.AbstractSpaceBoundary;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.model.core.AbstractThematicSurface;
import org.citygml4j.core.model.core.AbstractUnoccupiedSpace;
import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.core.model.core.ImplicitGeometry;
import org.citygml4j.core.model.deprecated.transportation.TransportationComplex;
import org.citygml4j.core.model.landuse.LandUse;
import org.citygml4j.core.model.transportation.*;
import org.citygml4j.core.model.relief.AbstractReliefComponentProperty;
import org.citygml4j.core.model.relief.BreaklineRelief;
import org.citygml4j.core.model.relief.MassPointRelief;
import org.citygml4j.core.model.relief.RasterRelief;
import org.citygml4j.core.model.relief.ReliefFeature;
import org.citygml4j.core.model.relief.TINRelief;
import org.citygml4j.core.model.transportation.AbstractTransportationSpace;
import org.citygml4j.core.model.transportation.AuxiliaryTrafficArea;
import org.citygml4j.core.model.transportation.AuxiliaryTrafficSpace;
import org.citygml4j.core.model.transportation.AuxiliaryTrafficSpaceProperty;
import org.citygml4j.core.model.transportation.IntersectionProperty;
import org.citygml4j.core.model.transportation.Railway;
import org.citygml4j.core.model.transportation.Road;
import org.citygml4j.core.model.transportation.SectionProperty;
import org.citygml4j.core.model.transportation.Square;
import org.citygml4j.core.model.transportation.Track;
import org.citygml4j.core.model.transportation.TrafficArea;
import org.citygml4j.core.model.transportation.TrafficSpace;
import org.citygml4j.core.model.transportation.TrafficSpaceProperty;
import org.citygml4j.core.model.transportation.Waterway;
import org.citygml4j.core.model.tunnel.HollowSpace;
import org.citygml4j.core.model.tunnel.HollowSpaceProperty;
import org.citygml4j.core.model.tunnel.Tunnel;
import org.citygml4j.core.model.tunnel.*;
import org.citygml4j.core.model.tunnel.TunnelConstructiveElementProperty;
import org.citygml4j.core.model.tunnel.TunnelFurnitureProperty;
import org.citygml4j.core.model.tunnel.TunnelInstallation;
import org.citygml4j.core.model.tunnel.TunnelInstallationProperty;
import org.citygml4j.core.model.tunnel.TunnelPartProperty;
import org.citygml4j.core.model.vegetation.AbstractVegetationObject;
import org.citygml4j.core.model.vegetation.PlantCover;
import org.citygml4j.core.model.vegetation.SolitaryVegetationObject;
......@@ -62,33 +106,95 @@ import org.xmlobjects.gml.model.geometry.GeometryProperty;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface;
import org.xmlobjects.gml.model.geometry.primitives.*;
import org.xmlobjects.gml.model.geometry.primitives.AbstractSolid;
import org.xmlobjects.gml.model.geometry.primitives.AbstractSurface;
import org.xmlobjects.gml.model.geometry.primitives.Shell;
import org.xmlobjects.gml.model.geometry.primitives.ShellProperty;
import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SolidProperty;
import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractTunnel;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeConstructiveElement;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingRoom;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingRoomFurniture;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityFurniture;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.CompositeCollection;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.GenericAttribute;
import de.hft.stuttgart.citydoctor2.datastructure.GenericCityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.LibraryObject;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinkedPolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.RelativeGeometry;
import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject;
import de.hft.stuttgart.citydoctor2.datastructure.TinObject;
import de.hft.stuttgart.citydoctor2.datastructure.TopLevelTransportFeature;
import de.hft.stuttgart.citydoctor2.datastructure.TrafficAreaObject;
import de.hft.stuttgart.citydoctor2.datastructure.TrafficSpaceObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportSection;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationSpace;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelConstructiveElement;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelFurniture;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelHollow;
import de.hft.stuttgart.citydoctor2.datastructure.TunnelPart;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation.VegetationType;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
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;
public class Citygml3FeatureMapper extends ObjectWalker {
private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class);
private static final FeatureCache featureCache = new FeatureCache();
private final CityDoctorModel model;
private final ParserConfiguration config;
private final Path directory;
private final double neighborDistance;
private final double neighborDistance;
private Map<String, ConcretePolygon> polygonMap = new HashMap<>();
private Map<String, CompositeCollection> compositeMap = new HashMap<>();
private Map<String, CompositeCollection> compositeMap = new HashMap<>();;
private List<ResolvableReference> references = new ArrayList<>();
private Map<Vertex, Vertex> vertexMap = new HashMap<>();
public Citygml3FeatureMapper(ParserConfiguration config, Path path) {
this.config = config;
this.directory = path.getParent();
model = new CityDoctorModel(config, path.toFile());
neighborDistance = 1.8d / Math.pow(10, config.getNumberOfRoundingPlaces());
}
private Map<String, List<GmlId>> unresolvedPolyRefIDMap = new HashMap<>();
private Map<String, GmlId> parsedPolygonsIdMap = new HashMap<>();
public Citygml3FeatureMapper(ParserConfiguration config, Path path, boolean unconnected) {
this.config = config;
this.directory = path.getParent();
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());
}
public static void parseId(AbstractGML gml, GmlElement gmlElement) {
String id = gml.getId();
......@@ -97,6 +203,36 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
}
public void checkUnresolvedReferences() {
if (unresolvedPolyRefIDMap.isEmpty()) {
return;
}
boolean xLinkGlobalError = false;
String emptyXLinkLog = Localization.getText("CityGmlFeatureMapper.emptyCrossFeatureReference");
List<String> crossRefs = new ArrayList<>();
for (Map.Entry<String, List<GmlId>> entry : unresolvedPolyRefIDMap.entrySet()) {
String id = entry.getKey();
boolean emptyCrossFeatureReference = parsedPolygonsIdMap.containsKey(id);
xLinkGlobalError = xLinkGlobalError || emptyCrossFeatureReference;
if (emptyCrossFeatureReference) {
List<GmlId> gmlIds = entry.getValue();
StringJoiner joiner = new StringJoiner(", ");
gmlIds.forEach(gmlId -> joiner.add(gmlId.toString()));
GmlId crossFeatureId = parsedPolygonsIdMap.get(id);
logger.warn(emptyXLinkLog, id, crossFeatureId, joiner);
crossRefs.add(id);
}
}
if (xLinkGlobalError) {
logger.warn(Localization.getText("CityGmlFeatureMapper.crossFeatureReferencesDetected"));
}
crossRefs.forEach(unresolvedPolyRefIDMap::remove);
if(!unresolvedPolyRefIDMap.isEmpty()) {
logger.warn(Localization.getText("CityGmlFeatureMapper.unresolvedReferencesDetected"));
}
}
@Override
public void visit(AbstractSpace space) {
// if we are here, an AbstractSpace thing was read that is not handled in the
......@@ -104,14 +240,38 @@ 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();
cf.setGmlObject(gmlCityFurniture);
mapAbstractOccupiedSpace(gmlCityFurniture, cf);
resolveAndClearReferences();
cf.unsetGmlGeometries();
updateEdgesAndVertices(cf);
mapAbstractOccupiedSpace(gmlCityFurniture, cf, Orientation.OUTWARD);
finishCityObjectConstruction(cf);
model.addCityFurniture(cf);
}
......@@ -128,14 +288,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
if (agLod4.getObject() instanceof MultiSurface ms) {
gco.addGeometry(parseMultiSurface(ms, Lod.LOD4));
} else if (agLod4.getObject() instanceof AbstractSolid solid) {
gco.addGeometry(parseSolid(solid, Lod.LOD4));
gco.addGeometry(parseSolid(solid, Lod.LOD4, Orientation.OUTWARD));
}
}
mapAbstractOccupiedSpace(gos, gco);
resolveAndClearReferences();
gco.unsetGmlGeometries();
updateEdgesAndVertices(gco);
model.addGenericCityObject(gco);
mapAbstractOccupiedSpace(gos, gco, Orientation.OUTWARD);
finishCityObjectConstruction(gco);
model.addOtherCityObject(gco);
}
@Override
......@@ -149,6 +307,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
BuildingPart part = new BuildingPart(cdBuilding);
readAbstractBuilding(bpProp.getObject(), part);
}
finishCityObjectConstruction(cdBuilding);
model.addBuilding(cdBuilding);
}
......@@ -156,9 +315,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
public void visit(WaterBody waterBody) {
WaterObject wo = new WaterObject();
wo.setGmlObject(waterBody);
mapAbstractOccupiedSpace(waterBody, wo);
mapAbstractOccupiedSpace(waterBody, wo, Orientation.OUTWARD);
parseAndAddMultiSurface(waterBody.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, wo);
parseAndAddSolid(waterBody.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, wo);
parseAndAddSolid(waterBody.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, wo, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : waterBody.getBoundaries()) {
......@@ -172,9 +331,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
wo.addBoundarySurface(bs);
}
resolveAndClearReferences();
wo.unsetGmlGeometries();
updateEdgesAndVertices(wo);
finishCityObjectConstruction(wo);
for (BoundarySurface bs : surfaceMapper.getSurfaces()) {
updateEdgesAndVertices(bs);
}
......@@ -191,6 +348,67 @@ public class Citygml3FeatureMapper extends ObjectWalker {
model.addLand(lo);
}
public void visit(ReliefFeature relief) {
ReliefObject ro = new ReliefObject(relief);
mapAbstractSpaceBoundary(relief, ro);
mapReliefFeature(relief, ro);
finishCityObjectConstruction(ro);
model.addRelief(ro);
}
public void mapReliefFeature(ReliefFeature relief, ReliefObject reliefObject) {
if (relief.isSetReliefComponents()) {
for (AbstractReliefComponentProperty arcProp : relief.getReliefComponents()) {
if (arcProp.isSetObject()) {
ObjectWalker ow = new ObjectWalker() {
@Override
public void visit(TINRelief rel) {
mapTINRelief(rel, reliefObject);
}
@Override
public void visit(MassPointRelief rel) {
//Method stub for implementation if requested
}
@Override
public void visit(BreaklineRelief rel) {
//Method stub for implementation if requested
}
@Override
public void visit(RasterRelief rel) {
//Method stub for implementation if requested
}
};
arcProp.getObject().accept(ow);
}
}
}
}
public void mapTINRelief(TINRelief relief, ReliefObject reliefObject) {
TinObject to = new TinObject(relief);
Lod reliefLod = switch (relief.getLod()) {
case 1 -> Lod.LOD1;
case 2 -> Lod.LOD2;
case 3 -> Lod.LOD3;
case 4 -> null;
default -> throw new IllegalStateException("TINRelief has unexpected LOD value: " + relief.getLod());
};
if (reliefLod == null) {
logger.warn("IllegalState: TINRelief {} has LOD value 4!", relief.getId());
return;
}
reliefObject.addComponent(to);
Citygml3GeometryMapper tinMapper = new Citygml3GeometryMapper(config, vertexMap);
relief.getTin().getObject().accept(tinMapper);
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, reliefLod, Orientation.OUTWARD);
tinMapper.getPolygons().forEach(geom::addPolygon);
to.addGeometry(geom);
}
@Override
public void visit(PlantCover plantCover) {
Vegetation veg = new Vegetation(VegetationType.PLANT_COVER);
......@@ -211,39 +429,39 @@ public class Citygml3FeatureMapper extends ObjectWalker {
mapAbstractVegetationObject(solitaryVegetationObject, veg);
parseAndAddAbstractGeometry(solitaryVegetationObject.getDeprecatedProperties().getLod1Geometry(), Lod.LOD1,
veg);
veg, Orientation.OUTWARD);
parseAndAddAbstractGeometry(solitaryVegetationObject.getDeprecatedProperties().getLod2Geometry(), Lod.LOD2,
veg);
veg, Orientation.OUTWARD);
parseAndAddAbstractGeometry(solitaryVegetationObject.getDeprecatedProperties().getLod3Geometry(), Lod.LOD3,
veg);
veg, Orientation.OUTWARD);
parseAndAddAbstractGeometry(solitaryVegetationObject.getDeprecatedProperties().getLod4Geometry(), Lod.LOD4,
veg);
veg, Orientation.OUTWARD);
finishCityObjectConstruction(veg);
model.addVegetation(veg);
}
private void mapAbstractVegetationObject(AbstractVegetationObject avo, Vegetation veg) {
mapAbstractOccupiedSpace(avo, veg);
mapAbstractOccupiedSpace(avo, veg, Orientation.OUTWARD);
}
private void mapAbstractOccupiedSpace(AbstractOccupiedSpace aos, CityObject co) {
mapAbstractPhysicalSpace(aos, co);
parseImplicitGeometry(aos, co);
private void mapAbstractOccupiedSpace(AbstractOccupiedSpace aos, CityObject co, Orientation orientation) {
mapAbstractPhysicalSpace(aos, co, orientation);
parseImplicitGeometry(aos, co, orientation);
}
private void mapAbstractPhysicalSpace(AbstractPhysicalSpace aps, CityObject co) {
mapAbstractSpace(aps, co);
private void mapAbstractPhysicalSpace(AbstractPhysicalSpace aps, CityObject co, Orientation orientation) {
mapAbstractSpace(aps, co, orientation);
}
private void mapAbstractSpace(AbstractSpace as, CityObject co) {
private void mapAbstractSpace(AbstractSpace as, CityObject co, Orientation orientation) {
mapAbstractFeature(as, co);
parseAndAddMultiSurface(as.getLod0MultiSurface(), Lod.LOD0, co);
parseAndAddMultiSurface(as.getLod2MultiSurface(), Lod.LOD2, co);
parseAndAddMultiSurface(as.getLod3MultiSurface(), Lod.LOD3, co);
parseAndAddSolid(as.getLod1Solid(), Lod.LOD1, co);
parseAndAddSolid(as.getLod2Solid(), Lod.LOD2, co);
parseAndAddSolid(as.getLod3Solid(), Lod.LOD3, co);
parseAndAddSolid(as.getLod1Solid(), Lod.LOD1, co, orientation);
parseAndAddSolid(as.getLod2Solid(), Lod.LOD2, co, orientation);
parseAndAddSolid(as.getLod3Solid(), Lod.LOD3, co, orientation);
parseAndAddGenericAttributes(as, co);
}
......@@ -255,12 +473,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
@Override
public void visit(Bridge bridge) {
BridgeObject bo = new BridgeObject(BridgeType.BRIDGE, bridge);
BridgeObject bo = new BridgeObject(bridge);
// parse deprecated geometries
parseAndAddMultiSurface(bridge.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, bo);
parseAndAddMultiSurface(bridge.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, bo);
parseAndAddSolid(bridge.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, bo);
parseAndAddSolid(bridge.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, bo, Orientation.OUTWARD);
mapAbstractBridge(bridge, bo);
for (BridgePartProperty bPartProperty : bridge.getBridgeParts()) {
......@@ -268,12 +486,11 @@ public class Citygml3FeatureMapper extends ObjectWalker {
continue;
}
BridgePart gmlBridgePart = bPartProperty.getObject();
BridgeObject bPart = new BridgeObject(BridgeType.BRIDGE_PART, gmlBridgePart);
BridgeObject bPart = new BridgeObject(gmlBridgePart, bo);
mapAbstractBridge(gmlBridgePart, bPart);
bo.addBridgePart(bPart);
}
resolveAndClearReferences();
updateEdgesAndVertices(bo);
finishCityObjectConstruction(bo);
for (BridgeObject part : bo.getParts()) {
updateEdgesAndVertices(part);
}
......@@ -328,21 +545,18 @@ public class Citygml3FeatureMapper extends ObjectWalker {
for (BoundarySurface bs : bo.getBoundarySurfaces()) {
updateEdgesAndVertices(bs);
}
bo.unsetGmlGeometries();
}
private Installation mapBridgeInstallation(BridgeInstallation gmlBi) {
Installation bi = new Installation();
bi.setGmlObject(gmlBi);
mapAbstractOccupiedSpace(gmlBi, bi);
mapAbstractOccupiedSpace(gmlBi, bi, Orientation.OUTWARD);
GeometryProperty<?> lod2Prop = gmlBi.getDeprecatedProperties().getLod2Geometry();
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, bi);
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, bi, Orientation.OUTWARD);
GeometryProperty<?> lod3Prop = gmlBi.getDeprecatedProperties().getLod3Geometry();
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, bi);
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, bi, Orientation.OUTWARD);
GeometryProperty<?> lod4Prop = gmlBi.getDeprecatedProperties().getLod4Geometry();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, bi);
bi.unsetGmlGeometries();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, bi, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlBi.getBoundaries()) {
......@@ -383,8 +597,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
tu.addTunnelPart(tPart);
}
readAbstractTunnel(tunnel, tu);
resolveAndClearReferences();
updateEdgesAndVertices(tu);
finishCityObjectConstruction(tu);
for (TunnelPart part : tu.getTunnelParts()) {
updateEdgesAndVertices(part);
}
......@@ -393,13 +606,13 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private void readAbstractTunnel(org.citygml4j.core.model.tunnel.AbstractTunnel gmlTunnel, AbstractTunnel cdTunnel) {
mapAbstractOccupiedSpace(gmlTunnel, cdTunnel);
mapAbstractOccupiedSpace(gmlTunnel, cdTunnel, Orientation.OUTWARD);
cdTunnel.setGmlObject(gmlTunnel);
// parse deprecated geometries
parseAndAddMultiSurface(gmlTunnel.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, cdTunnel);
parseAndAddMultiSurface(gmlTunnel.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, cdTunnel);
parseAndAddSolid(gmlTunnel.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, cdTunnel);
parseAndAddSolid(gmlTunnel.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, cdTunnel, Orientation.OUTWARD);
parseTunnelInstallations(gmlTunnel, cdTunnel);
parseHollowSpaces(gmlTunnel, cdTunnel);
......@@ -415,11 +628,6 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
}
}
cdTunnel.unsetGmlGeometries();
resolveAndClearReferences();
updateEdgesAndVertices(cdTunnel);
}
private void parseTunnelBoundarySurfaces(org.citygml4j.core.model.tunnel.AbstractTunnel gmlTunnel) {
......@@ -479,22 +687,22 @@ public class Citygml3FeatureMapper extends ObjectWalker {
// ignore empty properties
continue;
}
Installation ti = mapTunnelInstallation(gmlTi);
Installation ti = mapTunnelInstallation(gmlTi, Orientation.OUTWARD);
cdTunnel.addTunnelInstallation(ti);
}
}
private Installation mapTunnelInstallation(TunnelInstallation gmlTi) {
private Installation mapTunnelInstallation(TunnelInstallation gmlTi, Orientation orientation) {
Installation ti = new Installation();
ti.setGmlObject(gmlTi);
mapAbstractOccupiedSpace(gmlTi, ti);
mapAbstractOccupiedSpace(gmlTi, ti, Orientation.OUTWARD);
GeometryProperty<?> lod2Prop = gmlTi.getDeprecatedProperties().getLod2Geometry();
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, ti);
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, ti, orientation);
GeometryProperty<?> lod3Prop = gmlTi.getDeprecatedProperties().getLod3Geometry();
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, ti);
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, ti, orientation);
GeometryProperty<?> lod4Prop = gmlTi.getDeprecatedProperties().getLod4Geometry();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, ti);
ti.unsetGmlGeometries();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, ti, orientation);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlTi.getBoundaries()) {
......@@ -525,7 +733,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private TunnelHollow mapTunnelHollow(HollowSpace gmlHo) {
TunnelHollow tHollow = new TunnelHollow();
tHollow.setGmlObject(gmlHo);
mapAbstractUnoccupiedSpace(gmlHo, tHollow);
mapAbstractUnoccupiedSpace(gmlHo, tHollow, Orientation.INWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlHo.getBoundaries()) {
......@@ -549,7 +757,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
// ignore empty properties
continue;
}
Installation bi = mapTunnelInstallation(gmlTi);
Installation bi = mapTunnelInstallation(gmlTi, Orientation.INWARD);
tHollow.addRoomInstallation(bi);
}
for (TunnelFurnitureProperty tfProp : gmlHo.getTunnelFurniture()) {
......@@ -565,8 +773,8 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private TunnelFurniture mapTunnelFurniture(org.citygml4j.core.model.tunnel.TunnelFurniture gmlTf) {
TunnelFurniture tf = new TunnelFurniture();
tf.setGmlObject(gmlTf);
mapAbstractOccupiedSpace(gmlTf, tf);
tf.unsetGmlGeometries();
mapAbstractOccupiedSpace(gmlTf, tf, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlTf.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
......@@ -589,8 +797,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private TunnelConstructiveElement mapTunnelConstructiveElement(
org.citygml4j.core.model.tunnel.TunnelConstructiveElement gmlTe) {
TunnelConstructiveElement te = new TunnelConstructiveElement(gmlTe);
mapAbstractOccupiedSpace(gmlTe, te);
te.unsetGmlGeometries();
mapAbstractOccupiedSpace(gmlTe, te, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlTe.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
......@@ -640,34 +847,60 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void mapAbstractConstructiveElement(org.citygml4j.core.model.bridge.BridgeConstructiveElement ele,
BridgeConstructiveElement bce) {
mapAbstractOccupiedSpace(ele, bce);
mapAbstractOccupiedSpace(ele, bce, Orientation.OUTWARD);
}
private void mapAbstractConstruction(AbstractConstruction ac, BridgeObject bo) {
mapAbstractOccupiedSpace(ac, bo);
mapAbstractOccupiedSpace(ac, bo, Orientation.OUTWARD);
}
@Override
public void visit(AuxiliaryTrafficArea ata) {
TransportationObject to = new TransportationObject(TransportationType.AUXILLIARY_TRAFFIC_AREA);
mapAbstractThematicSurface(ata, to);
finishTransportationMapping(to);
public void visit(Track track) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(track);
mapSectionsAndIntersections(track.getSections(), track.getIntersections(), top);
mapAbstractTransportationSpace(track, top);
finishTransportationMapping(top);
}
@Override
public void visit(TrafficArea ta) {
TransportationObject to = new TransportationObject(TransportationType.TRAFFIC_AREA);
mapAbstractThematicSurface(ta, to);
finishTransportationMapping(to);
public void visit(Road road) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(road);
mapSectionsAndIntersections(road.getSections(), road.getIntersections(), top);
mapAbstractTransportationSpace(road, top);
finishTransportationMapping(top);
}
@Override
public void visit(Road road) {
TransportationObject to = new TransportationObject(TransportationType.ROAD);
mapAbstractTransportationSpace(road, to);
finishTransportationMapping(to);
public void visit(Waterway waterway) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(waterway);
mapSectionsAndIntersections(waterway.getSections(), waterway.getIntersections(), top);
mapAbstractTransportationSpace(waterway, top);
finishTransportationMapping(top);
}
@Override
public void visit(Railway railway) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(railway);
mapSectionsAndIntersections(railway.getSections(), railway.getIntersections(), top);
mapAbstractTransportationSpace(railway, top);
finishTransportationMapping(top);
}
@Override
public void visit(Square square) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(square);
mapAbstractTransportationSpace(square, top);
finishTransportationMapping(top);
}
@Override
public void visit(TransportationComplex tc) {
TopLevelTransportFeature top = TopLevelTransportFeature.from(tc);
mapAbstractTransportationSpace(tc, top);
finishTransportationMapping(top);
}
private void mapAbstractThematicSurface(AbstractThematicSurface ats, CityObject co) {
mapAbstractSpaceBoundary(ats, co);
parseAndAddMultiSurface(ats.getLod0MultiSurface(), Lod.LOD0, co);
......@@ -697,33 +930,6 @@ public class Citygml3FeatureMapper extends ObjectWalker {
parseId(ag, to);
}
@Override
public void visit(Railway railway) {
TransportationObject to = new TransportationObject(TransportationType.RAILWAY);
mapAbstractTransportationSpace(railway, to);
finishTransportationMapping(to);
}
@Override
public void visit(Square square) {
TransportationObject to = new TransportationObject(TransportationType.SQUARE);
mapAbstractTransportationSpace(square, to);
finishTransportationMapping(to);
}
@Override
public void visit(Track track) {
TransportationObject to = new TransportationObject(TransportationType.TRACK);
mapAbstractTransportationSpace(track, to);
finishTransportationMapping(to);
}
@Override
public void visit(TransportationComplex transportationComplex) {
TransportationObject to = new TransportationObject(TransportationType.TRANSPORTATION_COMPLEX);
mapAbstractTransportationSpace(transportationComplex, to);
finishTransportationMapping(to);
}
private void finishTransportationMapping(TransportationObject to) {
finishCityObjectConstruction(to);
......@@ -731,59 +937,109 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private void finishCityObjectConstruction(CityObject co) {
co.unsetGmlGeometries();
resolveAndClearReferences();
parseId(co.getGmlObject(), co);
co.clearGmlGeometries();
resolveAndClearReferences(co);
updateEdgesAndVertices(co);
co.setBbox(BoundingBox.of(co));
}
private void mapAbstractTransportationSpace(AbstractTransportationSpace ats, TransportationObject to) {
to.setGmlObject(ats);
parseAbstractTransportationSpaceGeometries(ats, to);
private void mapAbstractTransportationSpace(AbstractTransportationSpace ats, TransportationSpace trsp) {
parseAbstractTransportationSpaceGeometries(ats, trsp);
for (TrafficSpaceProperty tsp : ats.getTrafficSpaces()) {
if (tsp.isSetObject()) {
TransportationObject trafficSpace = new TransportationObject(TransportationType.TRAFFIC_SPACE);
mapTrafficSpace(tsp.getObject(), trafficSpace);
finishTransportationMapping(trafficSpace);
to.getChildren().add(trafficSpace);
TrafficSpaceObject tso = new TrafficSpaceObject(TrafficSpaceObject.TrafficSpaceType.TRAFFIC_SPACE);
mapTrafficSpace(tsp.getObject(), tso);
trsp.addTrafficSpace(tso);
}
}
for (AuxiliaryTrafficSpaceProperty auxTrafficProp : ats.getAuxiliaryTrafficSpaces()) {
if (auxTrafficProp.isSetObject()) {
TransportationObject trafficSpace = parseAuxiliaryTrafficSpace(auxTrafficProp.getObject());
finishTransportationMapping(trafficSpace);
to.addChild(trafficSpace);
for (AuxiliaryTrafficSpaceProperty atsp : ats.getAuxiliaryTrafficSpaces()) {
if (atsp.isSetObject()) {
TrafficSpaceObject atso = new TrafficSpaceObject(TrafficSpaceObject.TrafficSpaceType.AUXILIARY_TRAFFIC_SPACE);
mapAuxiliaryTrafficSpace(atsp.getObject(), atso);
trsp.addAuxTrafficSpace(atso);
}
}
}
private void mapTrafficSpace(TrafficSpace ts, TransportationObject to) {
mapAbstractUnoccupiedSpace(ts, to);
private void mapSectionsAndIntersections(List<SectionProperty> sectionProps, List<IntersectionProperty> intersectionProps,
TopLevelTransportFeature top) {
for (SectionProperty sectionProp : sectionProps) {
if (sectionProp.isSetObject()) {
TransportSection sect = new TransportSection(TransportSection.SectionType.SECTION);
sect.setGmlObject(sectionProp.getObject());
mapAbstractTransportationSpace(sectionProp.getObject(), sect);
finishCityObjectConstruction(sect);
top.addSection(sect);
}
}
for (IntersectionProperty intersectionProp : intersectionProps) {
if (intersectionProp.isSetObject()) {
TransportSection isect = new TransportSection(TransportSection.SectionType.INTERSECTION);
isect.setGmlObject(intersectionProp.getObject());
mapAbstractTransportationSpace(intersectionProp.getObject(), isect);
finishCityObjectConstruction(isect);
top.addIntersection(isect);
}
}
}
private void mapAbstractUnoccupiedSpace(AbstractUnoccupiedSpace aus, CityObject co) {
mapAbstractPhysicalSpace(aus, co);
private void mapTrafficSpace(TrafficSpace ts, TrafficSpaceObject tso) {
tso.setGmlObject(ts);
mapAbstractUnoccupiedSpace(ts, tso, Orientation.OUTWARD);
for (AbstractSpaceBoundaryProperty boundary : ts.getBoundaries()) {
if (boundary.isSetObject()) {
TrafficArea area = (TrafficArea) boundary.getObject();
TrafficAreaObject tao = new TrafficAreaObject(TrafficAreaObject.TrafficAreaType.TRAFFIC_AREA);
mapTrafficArea(area, tao);
tso.addTrafficArea(tao);
}
}
finishCityObjectConstruction(tso);
}
private TransportationObject parseAuxiliaryTrafficSpace(AuxiliaryTrafficSpace ats) {
TransportationObject to = new TransportationObject(TransportationType.AUXILLIARY_TRAFFIC_SPACE);
parseAbstractSpaceGeometries(ats, to);
finishCityObjectConstruction(to);
return to;
private void mapAuxiliaryTrafficSpace(AuxiliaryTrafficSpace ats, TrafficSpaceObject tso) {
tso.setGmlObject(ats);
mapAbstractUnoccupiedSpace(ats, tso, Orientation.OUTWARD);
for (AbstractSpaceBoundaryProperty boundary : ats.getBoundaries()) {
if (boundary.isSetObject()) {
AuxiliaryTrafficArea area = (AuxiliaryTrafficArea) boundary.getObject();
TrafficAreaObject tao = new TrafficAreaObject(TrafficAreaObject.TrafficAreaType.AUXILIARY_TRAFFIC_AREA);
mapAuxiliaryTrafficArea(area, tao);
tso.addTrafficArea(tao);
}
}
finishCityObjectConstruction(tso);
}
private void parseAbstractTransportationSpaceGeometries(AbstractTransportationSpace space, CityObject co) {
parseAndAddMultiSurface(space.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, co);
parseAndAddMultiSurface(space.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, co);
private void mapTrafficArea(TrafficArea ta, TrafficAreaObject tao) {
tao.setGmlObject(ta);
parseAndAddMultiSurface(ta.getLod0MultiSurface(), Lod.LOD0, tao);
parseAndAddMultiSurface(ta.getLod1MultiSurface(), Lod.LOD1, tao);
parseAndAddMultiSurface(ta.getLod2MultiSurface(), Lod.LOD2, tao);
parseAndAddMultiSurface(ta.getLod3MultiSurface(), Lod.LOD3, tao);
parseAndAddMultiSurface(ta.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, tao);
finishCityObjectConstruction(tao);
}
private void parseAbstractSpaceGeometries(AbstractSpace as, CityObject co) {
parseAndAddMultiSurface(as.getLod0MultiSurface(), Lod.LOD0, co);
parseAndAddMultiSurface(as.getLod2MultiSurface(), Lod.LOD2, co);
parseAndAddMultiSurface(as.getLod3MultiSurface(), Lod.LOD3, co);
parseAndAddSolid(as.getLod1Solid(), Lod.LOD1, co);
parseAndAddSolid(as.getLod2Solid(), Lod.LOD2, co);
parseAndAddSolid(as.getLod3Solid(), Lod.LOD3, co);
private void mapAuxiliaryTrafficArea(AuxiliaryTrafficArea ats, TrafficAreaObject tao) {
tao.setGmlObject(ats);
parseAndAddMultiSurface(ats.getLod0MultiSurface(), Lod.LOD0, tao);
parseAndAddMultiSurface(ats.getLod1MultiSurface(), Lod.LOD1, tao);
parseAndAddMultiSurface(ats.getLod2MultiSurface(), Lod.LOD2, tao);
parseAndAddMultiSurface(ats.getLod3MultiSurface(), Lod.LOD3, tao);
parseAndAddMultiSurface(ats.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, tao);
finishCityObjectConstruction(tao);
}
private void mapAbstractUnoccupiedSpace(AbstractUnoccupiedSpace aus, CityObject co, Orientation orientation) {
mapAbstractPhysicalSpace(aus, co, orientation);
}
private void parseAbstractTransportationSpaceGeometries(AbstractTransportationSpace space, CityObject co) {
parseAndAddMultiSurface(space.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, co);
parseAndAddMultiSurface(space.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, co);
}
private void parseAndAddMultiSurface(MultiSurfaceProperty msp, Lod lod, CityObject co) {
......@@ -794,24 +1050,24 @@ public class Citygml3FeatureMapper extends ObjectWalker {
co.addGeometry(geom);
}
private void parseAndAddSolid(SolidProperty sp, Lod lod, CityObject co) {
if (sp == null || sp.getObject() == null) {
return;
}
Geometry geom = parseSolid(sp.getObject(), lod);
if (geom != null) {
co.addGeometry(geom);
}
}
private void parseAndAddSolid(SolidProperty sp, Lod lod, CityObject co, Orientation orientation) {
if (sp == null || sp.getObject() == null) {
return;
}
Geometry geom = parseSolid(sp.getObject(), lod, orientation);
if (geom != null) {
co.addGeometry(geom);
}
}
private void parseImplicitGeometry(AbstractOccupiedSpace aos, CityObject co) {
private void parseImplicitGeometry(AbstractOccupiedSpace aos, CityObject co, Orientation orientation) {
for (int i = 1; i <= 3; i++) {
if (aos.getImplicitRepresentation(i) == null) {
continue;
}
if (aos.getImplicitRepresentation(i).getObject() != null) {
ImplicitGeometry impGeom = aos.getImplicitRepresentation(i).getObject();
ImplicitGeometryHolder igh = resolveImplicitGeometry(impGeom, i);
ImplicitGeometryHolder igh = resolveImplicitGeometry(impGeom, i, orientation);
if (igh != null) {
co.addGeometry(igh);
}
......@@ -825,29 +1081,46 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private ImplicitGeometryHolder resolveImplicitGeometry(ImplicitGeometry ig, int lodInt) {
private ImplicitGeometryHolder resolveImplicitGeometry(ImplicitGeometry ig, int lodInt, Orientation orientation) {
ImplicitGeometryHolder igh = null;
if (ig.getLibraryObject() != null) {
Path libraryObjectPath = directory.resolve(ig.getLibraryObject());
LibraryObject libObj = LibraryObject.of(libraryObjectPath, config);
LibraryObject libObj = null;
if (directory != null){
Path libraryObjectPath = directory.resolve(ig.getLibraryObject());
libObj = LibraryObject.of(libraryObjectPath, config);
}
if (libObj != null) {
igh = new ImplicitGeometryHolder(ig, libObj);
}
} else if (ig.getRelativeGeometry() != null) {
AbstractGeometry aGeom = ig.getRelativeGeometry().getObject();
Geometry geom = null;
Lod lod = Lod.values()[lodInt];
RelativeGeometry relGeo = null;
if (aGeom instanceof MultiSurface ms) {
geom = parseMultiSurface(ms, lod);
} else if (aGeom instanceof Solid s) {
geom = parseSolid(s, lod);
} else if (aGeom instanceof CompositeSurface cs) {
geom = parseCompositeSurface(cs, lod);
}
if (geom != null) {
relGeo = RelativeGeometry.of(geom);
igh = new ImplicitGeometryHolder(ig, relGeo);
if (aGeom != null) {
Geometry geom = null;
if (aGeom instanceof MultiSurface ms) {
geom = parseMultiSurface(ms, lod);
} else if (aGeom instanceof Solid s) {
geom = parseSolid(s, lod, orientation);
} else if (aGeom instanceof CompositeSurface cs) {
geom = parseCompositeSurface(cs, lod);
}
if (geom != null) {
geom.setGmlId(new GmlId(aGeom.getId()));
RelativeGeometry relGeo = RelativeGeometry.of(geom.getGmlId(), geom);
igh = new ImplicitGeometryHolder(ig, relGeo);
}
} else if (ig.getRelativeGeometry().getHref() != null) {
String hrefId = ig.getRelativeGeometry().getHref();
hrefId = hrefId.startsWith("#") ? hrefId.substring(1) : hrefId;
GmlId gmlId = new GmlId(hrefId);
RelativeGeometry relGeo = RelativeGeometry.of(gmlId);
if (relGeo == null) {
igh = ImplicitGeometryHolder.deferRelativeGeomParsing(ig, lod);
} else {
igh = new ImplicitGeometryHolder(ig, relGeo);
}
} else {
logger.warn("Relative geometry of GML-ID {} has neither geometry nor an href", ig.getId());
}
} else {
......@@ -856,7 +1129,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
return igh;
}
private void resolveAndClearReferences() {
private void resolveAndClearReferences(CityObject co) {
for (ResolvableReference ref : references) {
String href = ref.href();
if (href.startsWith("#")) {
......@@ -871,6 +1144,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
handlePolygonReference(href, geom);
}
}
for(String polygonId: polygonMap.keySet()){
parsedPolygonsIdMap.put(polygonId, co.getGmlId());
}
// clear storage for polygons and vertices
references = new ArrayList<>();
......@@ -881,9 +1157,12 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void handlePolygonReference(String href, Geometry geom) {
ConcretePolygon concPoly = polygonMap.get(href);
if (concPoly == null) {
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
}
List<GmlId> featureList = unresolvedPolyRefIDMap.computeIfAbsent(href, k -> new ArrayList<>());
GmlId featureId = geom.getParent().getTopLevelCityObject().getGmlId();
featureList.add(featureId);
// if (logger.isWarnEnabled()) {
// logger.warn(Localization.getText("FeatureMapper.polygonUnreferenced"), href);
// }
} else {
LinkedPolygon lPoly = new LinkedPolygon(concPoly, geom);
if (geom.getParent() instanceof BoundarySurface bs) {
......@@ -898,14 +1177,14 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private void readAbstractBuilding(org.citygml4j.core.model.building.AbstractBuilding gmlAb,
AbstractBuilding cdBuilding) {
mapAbstractOccupiedSpace(gmlAb, cdBuilding);
mapAbstractOccupiedSpace(gmlAb, cdBuilding, Orientation.OUTWARD);
cdBuilding.setGmlObject(gmlAb);
// parse deprecated geometries
parseAndAddMultiSurface(gmlAb.getDeprecatedProperties().getLod1MultiSurface(), Lod.LOD1, cdBuilding);
parseAndAddMultiSurface(gmlAb.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, cdBuilding);
parseAndAddSolid(gmlAb.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, cdBuilding);
parseAndAddSolid(gmlAb.getDeprecatedProperties().getLod4Solid(), Lod.LOD4, cdBuilding, Orientation.OUTWARD);
parseBuildingConstructiveElements(gmlAb, cdBuilding);
parseBuildingInstallations(gmlAb, cdBuilding);
parseBuildingRooms(gmlAb, cdBuilding);
parseBuildingSubdivisions(gmlAb, cdBuilding);
......@@ -935,9 +1214,6 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
updatePartOfSurface(cdBuilding, surfaceMapper);
cdBuilding.unsetGmlGeometries();
resolveAndClearReferences();
updateEdgesAndVertices(cdBuilding);
for (BoundarySurface bs : surfaceMapper.getSurfaces()) {
updateEdgesAndVertices(bs);
for (Opening o : bs.getOpenings()) {
......@@ -962,6 +1238,19 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
}
private void parseBuildingConstructiveElements(org.citygml4j.core.model.building.AbstractBuilding gmlAb, AbstractBuilding cdBuilding) {
for (BuildingConstructiveElementProperty bceProp : gmlAb.getBuildingConstructiveElements()) {
var gmlBCE = bceProp.getObject();
if (gmlBCE == null) {
continue;
}
de.hft.stuttgart.citydoctor2.datastructure.BuildingConstructiveElement bce =
new de.hft.stuttgart.citydoctor2.datastructure.BuildingConstructiveElement(gmlBCE);
mapBuildingConstructiveElement(bce, gmlBCE);
cdBuilding.addConstructiveElement(bce);
}
}
private void parseBuildingRooms(org.citygml4j.core.model.building.AbstractBuilding gmlAb, AbstractBuilding cdBuilding) {
for (BuildingRoomProperty brProp : gmlAb.getBuildingRooms()) {
var gmlBr = brProp.getObject();
......@@ -998,7 +1287,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
BuildingRoom br = new BuildingRoom();
br.setGmlObject(gmlBr);
mapAbstractUnoccupiedSpace(gmlBr, br);
mapAbstractUnoccupiedSpace(gmlBr, br, Orientation.INWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlBr.getBoundaries()) {
......@@ -1039,8 +1328,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private BuildingRoomFurniture mapBuildingFurniture(BuildingFurniture gmlAF) {
BuildingRoomFurniture bf = new BuildingRoomFurniture();
bf.setGmlObject(gmlAF);
mapAbstractOccupiedSpace(gmlAF, bf);
bf.unsetGmlGeometries();
mapAbstractOccupiedSpace(gmlAF, bf, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlAF.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
......@@ -1061,11 +1349,25 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private void mapBuildingConstructiveElement(de.hft.stuttgart.citydoctor2.datastructure.BuildingConstructiveElement bce, BuildingConstructiveElement ele) {
mapAbstractOccupiedSpace(ele, bce, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : ele.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
continue;
}
AbstractSpaceBoundary surface = surfaceProp.getObject();
surface.accept(surfaceMapper);
}
for (BoundarySurface bs : surfaceMapper.getSurfaces()) {
bce.addBoundarySurface(bs);
}
}
private de.hft.stuttgart.citydoctor2.datastructure.Storey mapBuildingStorey(Storey gmlStorey) {
de.hft.stuttgart.citydoctor2.datastructure.Storey storey = new de.hft.stuttgart.citydoctor2.datastructure.Storey();
storey.setGmlObject(gmlStorey);
mapAbstractSpace(gmlStorey, storey);
storey.unsetGmlGeometries();
mapAbstractSpace(gmlStorey, storey, Orientation.INWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlStorey.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
......@@ -1088,8 +1390,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private de.hft.stuttgart.citydoctor2.datastructure.BuildingUnit mapBuildingUnit(BuildingUnit gmlBU) {
de.hft.stuttgart.citydoctor2.datastructure.BuildingUnit bu = new de.hft.stuttgart.citydoctor2.datastructure.BuildingUnit();
bu.setGmlObject(gmlBU);
mapAbstractSpace(gmlBU, bu);
bu.unsetGmlGeometries();
mapAbstractSpace(gmlBU, bu, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlBU.getBoundaries()) {
if (!surfaceProp.isSetObject()) {
......@@ -1123,14 +1424,13 @@ public class Citygml3FeatureMapper extends ObjectWalker {
private Installation mapBuildingInstallation(org.citygml4j.core.model.building.BuildingInstallation gmlBi) {
Installation bi = new Installation();
bi.setGmlObject(gmlBi);
mapAbstractOccupiedSpace(gmlBi, bi);
mapAbstractOccupiedSpace(gmlBi, bi, Orientation.OUTWARD);
GeometryProperty<?> lod2Prop = gmlBi.getDeprecatedProperties().getLod2Geometry();
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, bi);
parseAndAddAbstractGeometry(lod2Prop, Lod.LOD2, bi, Orientation.OUTWARD);
GeometryProperty<?> lod3Prop = gmlBi.getDeprecatedProperties().getLod3Geometry();
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, bi);
parseAndAddAbstractGeometry(lod3Prop, Lod.LOD3, bi, Orientation.OUTWARD);
GeometryProperty<?> lod4Prop = gmlBi.getDeprecatedProperties().getLod4Geometry();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, bi);
bi.unsetGmlGeometries();
parseAndAddAbstractGeometry(lod4Prop, Lod.LOD4, bi, Orientation.OUTWARD);
SurfaceMapper surfaceMapper = new SurfaceMapper(polygonMap, references, compositeMap, vertexMap, config);
for (AbstractSpaceBoundaryProperty surfaceProp : gmlBi.getBoundaries()) {
......@@ -1158,7 +1458,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
return bi;
}
private void parseAndAddAbstractGeometry(GeometryProperty<?> geomProp, Lod lod, CityObject co) {
private void parseAndAddAbstractGeometry(GeometryProperty<?> geomProp, Lod lod, CityObject co, Orientation orientation) {
if (geomProp == null || geomProp.getObject() == null) {
return;
}
......@@ -1167,7 +1467,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
Geometry geom = parseMultiSurface(ms, lod);
co.addGeometry(geom);
} else if (abstractGeometry instanceof Solid solid) {
Geometry geom = parseSolid(solid, lod);
Geometry geom = parseSolid(solid, lod, orientation);
if (geom != null) {
co.addGeometry(geom);
}
......@@ -1176,9 +1476,9 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
}
private Geometry parseSolid(AbstractSolid abstractSolid, Lod lod) {
private Geometry parseSolid(AbstractSolid abstractSolid, Lod lod, Orientation orientation) {
if (abstractSolid instanceof Solid s) {
return handleSolidGeometry(s, lod);
return handleSolidGeometry(s, lod, orientation);
} else {
logger.warn("Cannot handle solid class {}, can only process pure solids",
abstractSolid.getClass().getSimpleName());
......@@ -1186,13 +1486,13 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
}
private Geometry handleSolidGeometry(Solid solid, Lod lod) {
private Geometry handleSolidGeometry(Solid solid, Lod lod, Orientation orientation) {
ShellProperty exteriorProperty = solid.getExterior();
if (exteriorProperty == null || exteriorProperty.getObject() == null) {
logger.warn("Found solid {} without exterior hull, ignoring", solid.getId());
return null;
}
Geometry geom = new Geometry(GeometryType.SOLID, lod);
Geometry geom = new Geometry(GeometryType.SOLID, lod, orientation);
Shell exterior = solid.getExterior().getObject();
Citygml3GeometryMapper geometryMapper = new Citygml3GeometryMapper(config, vertexMap);
readSurfaceMember(geom, geometryMapper, exterior.getSurfaceMembers());
......@@ -1200,7 +1500,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private Geometry parseMultiSurface(MultiSurface ms, Lod lod) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, lod);
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, lod, null);
Citygml3GeometryMapper geometryMapper = new Citygml3GeometryMapper(config, vertexMap);
readSurfaceMember(geom, geometryMapper, ms.getSurfaceMember());
return geom;
......@@ -1230,7 +1530,7 @@ public class Citygml3FeatureMapper extends ObjectWalker {
}
private Geometry parseCompositeSurface(CompositeSurface cs, Lod lod) {
Geometry geom = new Geometry(GeometryType.COMPOSITE_SURFACE, lod);
Geometry geom = new Geometry(GeometryType.COMPOSITE_SURFACE, lod, null);
Citygml3GeometryMapper geometryMapper = new Citygml3GeometryMapper(config, vertexMap);
readSurfaceMember(geom, geometryMapper, cs.getSurfaceMembers());
return geom;
......
......@@ -35,6 +35,7 @@ import org.xmlobjects.gml.visitor.GeometryWalker;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -63,6 +64,55 @@ public class Citygml3GeometryMapper extends GeometryWalker {
}
}
@Override
public void visit(Surface surface) {
if (surface.getPatches() != null && !surface.getPatches().isSetObjects()) {
logger.warn("Surface {} has no PolygonPatches.", surface.getId());
return;
}
PatchCollection patchCollection = new PatchCollection();
GeometryWalker patchCollector = new GeometryWalker() {
@Override
public void visit(PolygonPatch patch) {
parsePolygonPatch(patch, patchCollection);
}
@Override
public void visit(Triangle tri) {
parsePolygon(null, tri.getExterior(), Collections.emptyList());
}
};
surface.getPatches().getObjects().forEach(abstractSurfacePatch -> abstractSurfacePatch.accept(patchCollector));
}
private void parsePolygonPatch(PolygonPatch patch, PatchCollection collection) {
AbstractRingProperty exterior = patch.getExterior();
List<AbstractRingProperty> interior = patch.getInterior();
if (exterior == null || exterior.getObject() == null) {
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("GeometryMapper.emptyPolygon"));
}
return;
}
ConcretePolygon conc = new ConcretePolygon();
polygons.add(conc);
currentRing = new LinearRing(LinearRingType.EXTERIOR);
exterior.getObject().accept(this);
conc.setExteriorRing(currentRing);
for (AbstractRingProperty interiorGmlRing : interior) {
AbstractRing gmlRing = interiorGmlRing.getObject();
if (gmlRing == null) {
continue;
}
currentRing = new LinearRing(LinearRingType.INTERIOR);
gmlRing.accept(this);
conc.addInteriorRing(currentRing);
}
collection.addPatchMember(conc);
}
private void parsePolygon(String id, AbstractRingProperty exterior, List<AbstractRingProperty> interior) {
if (exterior == null || exterior.getObject() == null) {
if (logger.isWarnEnabled()) {
......
......@@ -28,29 +28,38 @@ import org.xml.sax.SAXParseException;
public class GMLValidationHandler implements ErrorHandler {
private static final int MAX_LINE_SIZE = 200;
private static final Logger logger = LogManager.getLogger(GMLValidationHandler.class);
private final List<String> messages = new ArrayList<>();
@Override
public void error(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.error(message);
messages.add(message);
}
private String constructMessage(SAXParseException exception) {
String exceptionMessage = exception.getMessage();
String message = "[line: " + exception.getLineNumber() + "] "
+ exceptionMessage.substring(0, Math.min(exceptionMessage.length(), MAX_LINE_SIZE));
if (exceptionMessage.length() > MAX_LINE_SIZE) {
message += "...";
}
return message;
}
@Override
public void warning(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.warn(message);
messages.add(message);
}
@Override
public void fatalError(SAXParseException exception) {
String message = "[" + exception.getLineNumber() + ", " + exception.getColumnNumber() + "] "
+ exception.getMessage();
String message = constructMessage(exception);
logger.fatal(message);
messages.add(message);
}
......
......@@ -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);
......@@ -107,7 +106,7 @@ public class SurfaceMapper extends ObjectWalker {
}
private Geometry parseMultiSurface(MultiSurface ms, Lod lod) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, lod);
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, lod, null);
Citygml3GeometryMapper geometryMapper = new Citygml3GeometryMapper(config, vertexMap);
readSurfaceMember(geom, geometryMapper, ms.getSurfaceMember());
return geom;
......@@ -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);
}
......
......@@ -34,8 +34,6 @@ public class Segment3d implements Serializable {
private static final double PRECISION = 0.00000001;
private static final double EPSILON = 0.01;
private final Vector3d pointA;
private final Vector3d pointB;
......@@ -178,7 +176,7 @@ public class Segment3d implements Serializable {
return "Segment3d [pointA=" + pointA + ", pointB=" + pointB + "]";
}
public Vector3d intersection(Triangle3d triangle) {
public Vector3d intersection(Triangle3d triangle, double eps) {
Vector3d v0 = triangle.getP1();
Vector3d v1 = triangle.getP2();
Vector3d v2 = triangle.getP3();
......@@ -188,7 +186,7 @@ public class Segment3d implements Serializable {
Vector3d p = direction.cross(edge2);
double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) {
if (det > -eps && det < eps) {
return null;
}
......@@ -197,7 +195,7 @@ public class Segment3d implements Serializable {
Vector3d s = pointA.minus(v0);
double u = invDet * s.dot(p);
if (u < EPSILON || u > (1.0 - EPSILON)) {
if (u < eps || u > (1.0 - eps)) {
return null;
}
......@@ -210,7 +208,7 @@ public class Segment3d implements Serializable {
}
double t = edge2.dot(q) * invDet;
if (t > EPSILON && t < 1 - EPSILON) {
if (t > eps && t < 1 - eps) {
// t is in segment
return pointA.plus(direction.mult(t));
}
......
......@@ -35,6 +35,8 @@ public class Triangle3d implements Serializable {
private static final long serialVersionUID = -6907333357794272435L;
private static final double EPSILON = 0.0001;
private static final double PLANAR_EPSILON = 0.0000001;
private final Vector3d p1;
private final Vector3d p2;
private final Vector3d p3;
......@@ -51,7 +53,54 @@ public class Triangle3d implements Serializable {
this.p3 = p3;
this.partOf = partOf;
}
public boolean hasMinExtent(double minExtent) {
Vector3d ab = p2.minus(p1);
Vector3d ac = p3.minus(p1);
Vector3d bc = p3.minus(p2);
// Find the longest edge to use as primary axis
double lenAB2 = ab.getSquaredLength();
double lenAC2 = ac.getSquaredLength();
double lenBC2 = bc.getSquaredLength();
Vector3d axisX;
if (lenAB2 >= lenAC2 && lenAB2 >= lenBC2) {
axisX = ab;
} else if (lenAC2 >= lenBC2) {
axisX = ac;
} else {
axisX = bc;
}
axisX = axisX.normalize();
// Pick any vector not parallel to axisX for in-plane Y axis
Vector3d axisY = pickPerpendicular(axisX);
// Project vertices onto axes
double minX = Math.min(p1.dot(axisX), Math.min(p2.dot(axisX), p3.dot(axisX)));
double maxX = Math.max(p1.dot(axisX), Math.max(p2.dot(axisX), p3.dot(axisX)));
double minY = Math.min(p1.dot(axisY), Math.min(p2.dot(axisY), p3.dot(axisY)));
double maxY = Math.max(p1.dot(axisY), Math.max(p2.dot(axisY), p3.dot(axisY)));
double extentX = maxX - minX;
double extentY = maxY - minY;
return extentX > minExtent && extentY > minExtent;
}
private Vector3d pickPerpendicular(Vector3d v) {
// choose smallest component to avoid near-zero cross
if (Math.abs(v.getX()) < Math.abs(v.getY()) && Math.abs(v.getX()) < Math.abs(v.getZ())) {
return new Vector3d(0, -v.getZ(), v.getY()).normalize();
}
if (Math.abs(v.getY()) < Math.abs(v.getZ())) {
return new Vector3d(-v.getZ(), 0, v.getX()).normalize();
}
return new Vector3d(-v.getY(), v.getX(), 0).normalize();
}
public TesselatedPolygon getPartOf() {
return partOf;
}
......@@ -87,8 +136,13 @@ public class Triangle3d implements Serializable {
Vector3d n2 = v1.cross(v2);
return new Plane(n2, p1);
}
public boolean doesIntersect(Triangle3d other) {
return doesIntersect(other, EPSILON);
}
public boolean doesIntersect(Triangle3d other, double epsilon) {
// plane of other triangle
Plane planeT2 = other.getPlane();
// check if all points are on one side of the plane
......@@ -96,13 +150,13 @@ public class Triangle3d implements Serializable {
double distanceP2T2 = planeT2.getSignedDistance(p2);
double distanceP3T2 = planeT2.getSignedDistance(p3);
if (Math.abs(distanceP1T2) < EPSILON) {
if (Math.abs(distanceP1T2) < PLANAR_EPSILON) {
distanceP1T2 = 0.0;
}
if (Math.abs(distanceP2T2) < EPSILON) {
if (Math.abs(distanceP2T2) < PLANAR_EPSILON) {
distanceP2T2 = 0.0;
}
if (Math.abs(distanceP3T2) < EPSILON) {
if (Math.abs(distanceP3T2) < PLANAR_EPSILON) {
distanceP3T2 = 0.0;
}
......@@ -123,13 +177,13 @@ public class Triangle3d implements Serializable {
double distanceP2T1 = planeT1.getSignedDistance(other.getP2());
double distanceP3T1 = planeT1.getSignedDistance(other.getP3());
if (Math.abs(distanceP1T1) < EPSILON) {
if (Math.abs(distanceP1T1) < epsilon) {
distanceP1T1 = 0.0;
}
if (Math.abs(distanceP2T1) < EPSILON) {
if (Math.abs(distanceP2T1) < epsilon) {
distanceP2T1 = 0.0;
}
if (Math.abs(distanceP3T1) < EPSILON) {
if (Math.abs(distanceP3T1) < epsilon) {
distanceP3T1 = 0.0;
}
......@@ -139,16 +193,18 @@ public class Triangle3d implements Serializable {
return false;
}
return checkTriangleLineIntersection(other, p1, p2) || checkTriangleLineIntersection(other, p1, p3)
|| checkTriangleLineIntersection(other, p2, p3)
|| checkTriangleLineIntersection(this, other.p1, other.p2)
|| checkTriangleLineIntersection(this, other.p1, other.p3)
|| checkTriangleLineIntersection(this, other.p2, other.p3);
boolean intersects = checkTriangleLineIntersection(other, p1, p2, epsilon)
|| checkTriangleLineIntersection(other, p1, p3, epsilon)
|| checkTriangleLineIntersection(other, p2, p3, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p2, epsilon)
|| checkTriangleLineIntersection(this, other.p1, other.p3, epsilon)
|| checkTriangleLineIntersection(this, other.p2, other.p3, epsilon);
return intersects;
}
private boolean checkTriangleLineIntersection(Triangle3d other, Vector3d a, Vector3d b) {
private boolean checkTriangleLineIntersection(Triangle3d other, Vector3d a, Vector3d b, double eps) {
Segment3d seg = new Segment3d(a, b);
Vector3d intersection1 = seg.intersection(other);
Vector3d intersection1 = seg.intersection(other, eps);
return intersection1 != null;
}
......@@ -171,7 +227,11 @@ public class Triangle3d implements Serializable {
private boolean doesIntersectCoplanarTriangle(Triangle3d other) {
Triangle2d t1 = projectTo2d();
Triangle2d t2 = other.projectTo2d();
return t1.intersects(t2);
boolean intersects = t1.intersects(t2);
if (intersects) {
System.out.println();
}
return intersects;
}
public Triangle2d projectTo2d() {
......@@ -261,5 +321,4 @@ public class Triangle3d implements Serializable {
public void setPartOf(TesselatedPolygon p) {
partOf = p;
}
}
......@@ -46,9 +46,10 @@ public class Vector3d implements Serializable {
public Vector3d() {
this(0d, 0d, 0d);
}
/**
* Convert JTS Coordinate class to Vector3d.
*
* @param coord JTS Coordinate
*/
public Vector3d(Coordinate coord) {
......@@ -210,7 +211,8 @@ public class Vector3d implements Serializable {
}
/**
* normalizes this vector. This method changes the coordinates of this instance.
* returns a normalized vector in the same direction as this one. This method
* does not change the coordinates of this instance.
*/
public UnitVector3d normalize() {
return UnitVector3d.of(this);
......@@ -253,9 +255,9 @@ public class Vector3d implements Serializable {
@Override
public String toString() {
final int maxLen = 5;
return "Vector3d [coords=" +
(coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null) +
"]";
return "Vector3d [coords="
+ (coords != null ? Arrays.toString(Arrays.copyOf(coords, Math.min(coords.length, maxLen))) : null)
+ "]";
}
@Override
......
/*-
* Copyright 2022 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
......@@ -27,6 +27,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -48,10 +50,10 @@ import org.apache.logging.log4j.Logger;
import org.citygml4j.core.ade.ADEException;
import org.citygml4j.core.ade.ADERegistry;
import org.citygml4j.core.model.CityGMLVersion;
import org.citygml4j.core.model.ade.ADEProperty;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractCityObjectProperty;
import org.citygml4j.core.model.core.AbstractFeature;
import org.citygml4j.core.model.core.AbstractFeatureProperty;
import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.core.util.CityGMLConstants;
import org.citygml4j.xml.CityGMLContext;
......@@ -69,6 +71,7 @@ import org.locationtech.proj4j.BasicCoordinateTransform;
import org.locationtech.proj4j.CRSFactory;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.ProjCoordinate;
import org.locationtech.proj4j.io.Proj4FileReader;
import org.locationtech.proj4j.proj.Projection;
import org.locationtech.proj4j.units.Units;
import org.xml.sax.InputSource;
......@@ -80,8 +83,11 @@ import org.xmlobjects.schema.SchemaHandlerException;
import org.xmlobjects.stream.XMLReader;
import org.xmlobjects.stream.XMLReaderFactory;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.ImplicitGeometryHolder;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.Citygml3FeatureMapper;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.GMLValidationHandler;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
......@@ -91,9 +97,8 @@ import de.hft.stuttgart.quality.QualityADEModule;
/**
* Utility class to parse CityGML files.
*
* @author Matthias Betz
*
* @author Matthias Betz
*/
public class CityGmlParser {
......@@ -119,14 +124,16 @@ public class CityGmlParser {
private static final SAXParserFactory FACTORY;
private static Proj4FileReader csReader = new Proj4FileReader();
private static final Pattern P_VUNITS = Pattern.compile("^\\+vunits=.+");
private static final Pattern P_GEOIDGRID = Pattern.compile("^\\+geoidgrid=.+");
private static CityGMLContext context;
private static List<QName> chunkProperties = new ArrayList<>();
// Toggle to suppress logger output for parsing of libraryObjects
private static boolean gagged = false;
static {
System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
System.setProperty("javax.xml.transform.TransformerFactory",
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
FACTORY = SAXParserFactory.newInstance();
try {
FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
......@@ -142,7 +149,7 @@ public class CityGmlParser {
private CityGmlParser() {
}
public static synchronized CityGMLContext getContext() {
public static synchronized CityGMLContext setupContext() {
if (context == null) {
try {
context = CityGMLContext.newInstance(CityGmlParser.class.getClassLoader());
......@@ -150,58 +157,149 @@ 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 parseCityGmlFile(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(filePath, config, null, null, false, true);
}
/**
* 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);
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);
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) throws CityGmlParseException, InvalidGmlFileException {
CityGMLContext context = getContext();
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 = setupContext();
Path file = Paths.get(filePath);
List<String> validationIssues = Collections.emptyList();
if (config.getValidate()) {
List<String> messages = validateFile(context, handler, file);
if (!messages.isEmpty()) {
throw new InvalidGmlFileException("Invalid GML File. First error: \n" + messages.get(0));
}
validationIssues = validateFile(handler, file);
}
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);
CityDoctorModel model = readAndKeepFeatures(config, file, in, ois, verbose, unconnected);
for (String xmlError : validationIssues) {
model.addGlobalError(new XMLValidationError(xmlError));
}
return model;
}
} catch (CityGMLReadException | IOException e) {
throw new CityGmlParseException("Failed to read CityGML file", e);
}
}
public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
String outputFile) throws CityGmlParseException {
Path f = Paths.get(file);
streamCityGml(f, config, null, cityObjectConsumer, outputFile);
}
public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer,
String outputFile) throws CityGmlParseException {
streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile);
......@@ -209,8 +307,8 @@ public class CityGmlParser {
public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
parseEpsgCodeFromFile(file, config);
startReadingCityGml(file, config, l, cityObjectConsumer, outputFile);
parseEpsgCodeFromFile(file, config, true);
startReadingCityGmlFile(file, config, l, cityObjectConsumer, outputFile);
}
public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
......@@ -232,7 +330,7 @@ public class CityGmlParser {
throw new CityGmlParseException("Did not find any CityModel in CityGML file");
}
private static void startReadingCityGml(Path file, ParserConfiguration config, ProgressListener l,
private static void startReadingCityGmlFile(Path file, ParserConfiguration config, ProgressListener l,
CityGmlConsumer cityObjectConsumer, String outputFile) {
try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
if (l != null) {
......@@ -247,12 +345,19 @@ public class CityGmlParser {
private static void readAndDiscardFeatures(Path file, ParserConfiguration config, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGMLReadException {
getContext();
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file, false);
readAndDiscardModel(mapper, ois, cityObjectConsumer, outputFile, config, file);
}
private static void readAndDiscardModel(Citygml3FeatureMapper mapper, ObservedInputStream ois,
CityGmlConsumer cityObjectConsumer, String outputFile, ParserConfiguration config, Path file)
throws CityGMLReadException {
setupContext();
CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
CityGMLChunkWriter writer = null;
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file);
CityDoctorModel model = mapper.getModel();
boolean isInitialized = false;
while (reader.hasNext()) {
......@@ -279,6 +384,7 @@ public class CityGmlParser {
writer.writeMember(chunk);
}
}
// end of stream
logger.debug("End of gml file stream");
} catch (CityGMLReadException e) {
......@@ -296,10 +402,10 @@ public class CityGmlParser {
}
}
private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) {
private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) throws CityGMLWriteException {
if (writer != null) {
for (ADEProperty genEle : cModel.getADEProperties()) {
writer.getCityModelInfo().addADEProperty(genEle);
for (AbstractFeatureProperty featureProp : cModel.getFeatureMembers()) {
writer.writeMember(featureProp.getObject());
}
}
}
......@@ -316,7 +422,7 @@ public class CityGmlParser {
if (outputFile == null) {
return null;
}
CityGMLContext gmlContext = CityGmlParser.getContext();
CityGMLContext gmlContext = CityGmlParser.setupContext();
CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
CityGMLChunkWriter writer = factory.createCityGMLChunkWriter(new File(outputFile),
......@@ -329,72 +435,92 @@ public class CityGmlParser {
return writer;
}
/**
* Suppresses logger output of {@link #readAndKeepFeatures} for the next parse.
* Used to prevent logging spam while resolving implicit geometries.
*/
public static void gagLogger(boolean value){
gagged = value;
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
CityGMLInputFactory inputFactory, ObservedInputStream ois, boolean verbose, boolean unconnected)
throws CityGMLReadException {
return readAndKeepModel(new Citygml3FeatureMapper(config, file, unconnected), inputFactory, ois, verbose);
}
private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
CityGMLInputFactory inputFactory, ObservedInputStream ois) throws CityGMLReadException {
private static CityDoctorModel readAndKeepModel(Citygml3FeatureMapper mapper, CityGMLInputFactory inputFactory,
ObservedInputStream ois, boolean verbose) throws CityGMLReadException {
try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file);
CityGMLVersion version = null;
// model is read in chunked mode
// 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() && !gagged) {
logger.info(Localization.getText("CityGmlParser.parsedObjects"),
mapper.getModel().getNumberOfFeatures());
} else if (gagged){
// Remove gag
gagged = false;
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.checkUnresolvedReferences();
mapper.setCityGMLVersion(version);
return mapper.getModel();
} catch (Exception e) {
throw new CityGMLReadException(e);
}
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();
......@@ -402,22 +528,27 @@ public class CityGmlParser {
parser.parse(new InputSource(is), handler);
} catch (EnvelopeFoundException e) {
try {
parseCoordinateSystem(config, handler);
parseCoordinateSystem(config, handler, verbose);
} catch (Exception e2) {
logger.debug("Exception while parsing for EPSG code", e2);
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("CityGmlParser.noEPSG"));
}
logEpsgParseError(e2);
}
} catch (Exception e) {
logger.debug("Exception while parsing for EPSG code", e);
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("CityGmlParser.noEPSG"));
}
logEpsgParseError(e);
}
if (handler.getEpsg() == null && logger.isInfoEnabled() && verbose) {
logger.info(Localization.getText("CityGmlParser.missingEPSGCode"));
}
}
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler) {
private static void logEpsgParseError(Exception e) {
logger.debug("Exception while parsing for EPSG code", e);
if (logger.isWarnEnabled()) {
logger.warn(Localization.getText("CityGmlParser.failedEPSGParse"));
}
}
private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler, boolean verbose) {
if (handler.getEpsg() == null) {
return;
}
......@@ -425,11 +556,14 @@ public class CityGmlParser {
if (crs == null) {
// could not find a coordinate system for srsName
// assuming metric system
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;
......@@ -454,11 +588,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);
......@@ -481,15 +619,15 @@ public class CityGmlParser {
* string and detect the corresponding reference system. If it is found, it
* returns a proj4j.CoordinateReferenceSystem. It throws an
* IllegalArgumentException otherwise.
*
* <p>
* This method should be able to parse any EPSG id : e.g. "EPSG:1234". German
* Citygmls might also have "DE_DHDN_3GK3" or "ETRS89_UTM32" as srsName, so
* those are also included. It isn't guaranteed that those formats are correctly
* parsed, though.
*
* <p>
* The EPSG ids and parameters are defined in resources ('nad/epsg') inside
* proj4j-0.1.0.jar. Some EPSG ids are missing though, e.g. 7415
*
*
* @param srsName
* @return CoordinateReferenceSystem
*/
......@@ -508,23 +646,24 @@ public class CityGmlParser {
Matcher mOGC = P_OGC.matcher(srsName);
if (mOGC.find()) {
return CRS_FACTORY.createFromName("EPSG:" + mOGC.group(1));
return getCrsFromOGCSrsCode(mOGC.group(1));
}
Matcher mOGC2 = P_OGC2.matcher(srsName);
if (mOGC2.find()) {
return CRS_FACTORY.createFromName("EPSG:" + mOGC2.group(1));
return getCrsFromOGCSrsCode(mOGC2.group(1));
}
Matcher mURN = P_URN.matcher(srsName);
// NOTE: Could use a HashMap if the switch/case becomes too long.
if (mURN.find()) {
return switch (mURN.group(1)) {
case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
default -> null;
};
return switch (mURN.group(1)) {
case "DE_DHDN_3GK2" -> CRS_FACTORY.createFromName("EPSG:31466");
case "DE_DHDN_3GK3" -> CRS_FACTORY.createFromName("EPSG:31467");
case "DE_DHDN_3GK4" -> CRS_FACTORY.createFromName("EPSG:31468");
case "DE_DHDN_3GK5" -> CRS_FACTORY.createFromName("EPSG:31469");
case "ETRS89_UTM32" -> CRS_FACTORY.createFromName("EPSG:25832");
default -> null;
};
}
if (srsName.equals("http://www.opengis.net/def/crs/EPSG/0/6697")) {
return CRS_FACTORY.createFromParameters("EPSG:6697", "+proj=longlat +ellps=GRS80 +no_defs +axis=neu");
......@@ -532,14 +671,23 @@ public class CityGmlParser {
return null;
}
private static List<String> validateFile(CityGMLContext context, GMLValidationHandler handler, Path file)
private static CoordinateReferenceSystem getCrsFromOGCSrsCode(String srsCode) {
String crsName = "EPSG:" + srsCode;
List<String> params = new ArrayList<>(Arrays.stream(csReader.getParameters(crsName)).toList());
params.removeIf(P_VUNITS.asPredicate());
params.removeIf(P_GEOIDGRID.asPredicate());
return CRS_FACTORY.createFromParameters(crsName, params.toArray(new String[0]));
}
public static List<String> validateFile(GMLValidationHandler handler, Path file)
throws CityGmlParseException {
if (handler == null) {
handler = new GMLValidationHandler();
}
setupContext();
try {
SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
readAdditionalSchemaDefinitions(context, file, schemaHandler);
readAdditionalSchemaDefinitions(context, file, 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);
......@@ -555,29 +703,22 @@ public class CityGmlParser {
private static void readAdditionalSchemaDefinitions(CityGMLContext context, Path file, SchemaHandler schemaHandler)
throws CityGmlParseException {
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects())
.withSchemaHandler(schemaHandler)
.createReader(file)) {
reader.nextTag();
try (XMLReader reader = XMLReaderFactory.newInstance(context.getXMLObjects()).withSchemaHandler(schemaHandler)
.createReader(file)) {
reader.nextTag();
} catch (Exception e) {
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
throw new CityGmlParseException("Failed to read file " + file.toAbsolutePath() + ".", e);
}
}
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);
}
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);
}
}
......@@ -49,6 +49,15 @@ public class EarcutTesselator {
start = addRingToArray(innerRing, vertices, start, axis);
}
// center vertices?
// double[] projectedCenter = new double[2];
// Geometry geometry = p.getParent();
// Vector3d center = geometry.getCenter();
// axis.writeCoordinatesOfVectorInArray(center, projectedCenter, 0);
// for (int i = 0; i < vertices.length; i++) {
// vertices[i] = vertices[i] - projectedCenter[i % 2];
// }
// triangulation
List<Integer> indices = Earcut.earcut(vertices, holeStart, 2);
List<Triangle3d> triangles = new ArrayList<>();
......@@ -56,14 +65,15 @@ public class EarcutTesselator {
throw new IllegalStateException();
}
for (int i = 0; i < indices.size(); i = i + 3) {
Triangle3d t = new Triangle3d(vertexObjects.get(indices.get(i + 0)),
vertexObjects.get(indices.get(i + 1)),
vertexObjects.get(indices.get(i + 2)));
Vertex v1 = vertexObjects.get(indices.get(i + 0));
Vertex v2 = vertexObjects.get(indices.get(i + 1));
Vertex v3 = vertexObjects.get(indices.get(i + 2));
Triangle3d t = new Triangle3d(v1, v2, v3);
triangles.add(t);
}
return new TesselatedPolygon(triangles, p);
}
private static void addVerticesToList(LinearRing ring, List<Vertex> vertexObjects) {
List<Vertex> vertices = ring.getVertices();
for (int i = 0; i < vertices.size() - 1; i++) {
......
......@@ -28,6 +28,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
import de.hft.stuttgart.citydoctor2.utils.visitors.MinMaxExtentVisitor;
/**
* Utility class for calculating axis aligned bounding boxes for different
......@@ -48,8 +50,8 @@ public class BoundingBoxCalculator {
* empty array is returned. This is considered an axis aligned bounding box.
* Only the exterior rings of the polygons is used as inner rings should be
* within the exterior or they are faulty.
*
* @param a list of polygons containing the vertices
*
* @param polygons a list of polygons containing the vertices
* @return an BoundingBox containing the two end points of the bounding box.
*/
public static BoundingBox calculateBoundingBox(Collection<? extends Polygon> polygons) {
......@@ -95,20 +97,10 @@ public class BoundingBoxCalculator {
* @return the bounding box of the model
*/
public static BoundingBox calculateBoundingBox(CityDoctorModel model) {
Vector3d low = new Vector3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
Vector3d high = new Vector3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
findMinMax(low, high, model.getBuildings());
findMinMax(low, high, model.getBridges());
findMinMax(low, high, model.getLand());
findMinMax(low, high, model.getTransportation());
findMinMax(low, high, model.getWater());
findMinMax(low, high, model.getVegetation());
MinMaxExtentVisitor mima = new MinMaxExtentVisitor();
model.accept(mima);
Vector3d[] result = new Vector3d[2];
result[0] = low;
result[1] = high;
return BoundingBox.of(result);
return BoundingBox.of(mima.getMinMaxExtent());
}
public static BoundingBox calculateBoundingBoxFromPoints(List<? extends Vector3d> points) {
......@@ -151,30 +143,39 @@ public class BoundingBoxCalculator {
}
}
private static void findMinMax(Vector3d low, Vector3d high, CityObject co) {
for (Geometry geom : co.getGeometries()) {
if (geom.getVertices() == null) {
geom.updateVertices();
}
for (Vertex v : geom.getVertices()) {
if (v.getX() < low.getX()) {
low.setX(v.getX());
}
if (v.getX() > high.getX()) {
high.setX(v.getX());
}
if (v.getY() < low.getY()) {
low.setY(v.getY());
}
if (v.getY() > high.getY()) {
high.setY(v.getY());
}
if (v.getZ() < low.getZ()) {
low.setZ(v.getZ());
}
if (v.getZ() > high.getZ()) {
high.setZ(v.getZ());
CityObjectCollector collector = new CityObjectCollector();
co.accept(collector);
for (CityObject object : collector.getCityObjects()) {
for (Geometry geom : object.getGeometries()) {
if (geom.getVertices() == null) {
geom.updateVertices();
}
findMinMax(low, high, geom);
}
}
}
private static void findMinMax(Vector3d low, Vector3d high, Geometry geom) {
for (Vertex v : geom.getVertices()) {
if (v.getX() < low.getX()) {
low.setX(v.getX());
}
if (v.getX() > high.getX()) {
high.setX(v.getX());
}
if (v.getY() < low.getY()) {
low.setY(v.getY());
}
if (v.getY() > high.getY()) {
high.setY(v.getY());
}
if (v.getZ() < low.getZ()) {
low.setZ(v.getZ());
}
if (v.getZ() > high.getZ()) {
high.setZ(v.getZ());
}
}
}
......
package de.hft.stuttgart.citydoctor2.utils;
import java.io.Serial;
/**
* This Throwable signals that a requested CheckError was found.
*/
public class CheckErrorFound extends Throwable {
@Serial
private static final long serialVersionUID = -2066876428396230230L;
public CheckErrorFound() {
super("CheckError was found", null, true, false);
}
}
......@@ -41,175 +41,174 @@ import java.util.Set;
*/
public final class CityGmlUtils {
private CityGmlUtils() {
// util class
}
public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
Polygon cdPoly, ParserConfiguration config) {
if (cdPoly.getExteriorRing() == null) {
return null;
}
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
// exterior ring
LinearRing extLr = cdPoly.getExteriorRing();
if (extLr.getVertices().size() < 3) {
// this ring does not have enough points in it
// this leads to errors when exporting therefore ignore it
return null;
}
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
// interior rings
for (LinearRing lr : cdPoly.getInnerRings()) {
gmlLr = createGmlRing(factory, config, lr);
if (lr.getVertices().size() < 3) {
// this ring does not have enough points in it
// this leads to errors when exporting therefore ignore it
return null;
}
gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
}
gmlPoly.setId(cdPoly.getGmlId().getGmlString());
return gmlPoly;
}
public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) {
surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config)));
}
for (CompositeCollection cc : comp.getChildComposites()) {
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config)));
}
if (surfaces.isEmpty()) {
return null;
}
return new CompositeSurface(surfaces);
}
public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
ParserConfiguration config, LinearRing lr) {
ProjCoordinate p1 = new ProjCoordinate();
ProjCoordinate p2 = new ProjCoordinate();
List<Double> ringValues = new ArrayList<>();
BasicCoordinateTransform trans = config.getOriginalTransform();
for (Vertex v : lr.getVertices()) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
if (trans != null) {
p1.x = x;
p1.y = y;
trans.transform(p1, p2);
x = p2.x;
y = p2.y;
z = z * config.getFromMeters();
}
ringValues.add(x);
ringValues.add(y);
ringValues.add(z);
}
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
gmlLr.setId(lr.getGmlId().getGmlString());
return gmlLr;
}
public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
if (geom.getType() != GeometryType.SOLID) {
throw new IllegalArgumentException("Only solids are allowed");
}
CompositeSurface comp = new CompositeSurface();
List<SurfaceProperty> surfaceMember = comp.getSurfaceMembers();
for (Polygon cdPoly : geom.getPolygons()) {
if (!cdPoly.isLink()) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaceMember.add(new SurfaceProperty(gmlPoly));
}
} else {
// add reference to polygon
surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
}
}
if (surfaceMember.isEmpty()) {
return null;
}
Solid solid = new Solid();
Shell shell = new Shell(surfaceMember);
solid.setExterior(new ShellProperty(shell));
solid.setId(geom.getGmlId().getGmlString());
return solid;
}
public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory,
ParserConfiguration config) {
if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) {
throw new IllegalArgumentException("This can only handle MultiSurfaces");
}
List<SurfaceProperty> surfaces = new ArrayList<>();
Set<CompositeCollection> compositeCollections = new HashSet<>();
for (Polygon cdPoly : geom.getPolygons()) {
if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) {
compositeCollections.add(conc.getPartOfComposite());
} else if (!cdPoly.isLink()) {
// is not part of a boundary surface
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
} else {
// add reference to polygon
surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
}
}
for (CompositeCollection cdPoly : compositeCollections) {
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config)));
}
if (surfaces.isEmpty()) {
return null;
}
MultiSurface ms = new MultiSurface(surfaces);
ms.setId(geom.getGmlId().getGmlString());
return ms;
}
public static MultiSurface createMultiSurface(List<Polygon> polygons, GeometryFactory factory,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (Polygon cdPoly : polygons) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
}
if (surfaces.isEmpty()) {
return null;
}
return new MultiSurface(surfaces);
}
public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (Polygon cdPoly : geom.getPolygons()) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
}
if (surfaces.isEmpty()) {
return null;
}
return new CompositeSurface(surfaces);
}
private CityGmlUtils() {
// util class
}
public static org.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
Polygon cdPoly, ParserConfiguration config) {
if (cdPoly.getExteriorRing() == null) {
return null;
}
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
// exterior ring
LinearRing extLr = cdPoly.getExteriorRing();
if (extLr.getVertices().size() < 3) {
// this ring does not have enough points in it
// this leads to errors when exporting therefore ignore it
return null;
}
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
gmlPoly.setExterior(new AbstractRingProperty(gmlLr));
// interior rings
for (LinearRing lr : cdPoly.getInnerRings()) {
gmlLr = createGmlRing(factory, config, lr);
if (lr.getVertices().size() < 3) {
// this ring does not have enough points in it
// this leads to errors when exporting therefore ignore it
return null;
}
gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
}
gmlPoly.setId(cdPoly.getGmlId().getGmlString());
return gmlPoly;
}
public static CompositeSurface createGmlComposite(GeometryFactory factory, CompositeCollection comp,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (ConcretePolygon cd : comp.getNonRecursiveCompositeMembers()) {
surfaces.add(new SurfaceProperty(createGmlPolygon(factory, cd, config)));
}
for (CompositeCollection cc : comp.getChildComposites()) {
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cc, config)));
}
if (surfaces.isEmpty()) {
return null;
}
return new CompositeSurface(surfaces);
}
public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
ParserConfiguration config, LinearRing lr) {
ProjCoordinate p1 = new ProjCoordinate();
ProjCoordinate p2 = new ProjCoordinate();
List<Double> ringValues = new ArrayList<>();
BasicCoordinateTransform trans = config.getOriginalTransform();
for (Vertex v : lr.getVertices()) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
if (trans != null) {
p1.x = x;
p1.y = y;
trans.transform(p1, p2);
x = p2.x;
y = p2.y;
z = z * config.getFromMeters();
}
ringValues.add(x);
ringValues.add(y);
ringValues.add(z);
}
org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
gmlLr.setId(lr.getGmlId().getGmlString());
return gmlLr;
}
public static Solid createSolid(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
if (geom.getType() != GeometryType.SOLID) {
throw new IllegalArgumentException("Only solids are allowed");
}
CompositeSurface comp = new CompositeSurface();
List<SurfaceProperty> surfaceMember = comp.getSurfaceMembers();
for (Polygon cdPoly : geom.getPolygons()) {
if (!cdPoly.isLink()) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
config);
if (gmlPoly != null) {
surfaceMember.add(new SurfaceProperty(gmlPoly));
}
} else {
// add reference to polygon
surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
}
}
if (surfaceMember.isEmpty()) {
return null;
}
Solid solid = new Solid();
Shell shell = new Shell(surfaceMember);
solid.setExterior(new ShellProperty(shell));
solid.setId(geom.getGmlId().getGmlString());
return solid;
}
public static MultiSurface createMultiSurface(Geometry geom, GeometryFactory factory, ParserConfiguration config) {
if (geom.getType() != GeometryType.MULTI_SURFACE && geom.getType() != GeometryType.COMPOSITE_SURFACE) {
throw new IllegalArgumentException("This can only handle MultiSurfaces");
}
List<SurfaceProperty> surfaces = new ArrayList<>();
Set<CompositeCollection> compositeCollections = new HashSet<>();
for (Polygon cdPoly : geom.getPolygons()) {
if (cdPoly instanceof ConcretePolygon conc && conc.isCompositeMember()) {
compositeCollections.add(conc.getPartOfComposite());
} else if (!cdPoly.isLink()) {
// is not part of a boundary surface
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly,
config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
} else {
// add reference to polygon
surfaces.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
}
}
for (CompositeCollection cdPoly : compositeCollections) {
surfaces.add(new SurfaceProperty(createGmlComposite(factory, cdPoly, config)));
}
if (surfaces.isEmpty()) {
return null;
}
MultiSurface ms = new MultiSurface(surfaces);
ms.setId(geom.getGmlId().getGmlString());
return ms;
}
public static MultiSurface createMultiSurface(List<Polygon> polygons, GeometryFactory factory,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (Polygon cdPoly : polygons) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
}
if (surfaces.isEmpty()) {
return null;
}
return new MultiSurface(surfaces);
}
public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
ParserConfiguration config) {
List<SurfaceProperty> surfaces = new ArrayList<>();
for (Polygon cdPoly : geom.getPolygons()) {
org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
if (gmlPoly != null) {
surfaces.add(new SurfaceProperty(gmlPoly));
}
}
if (surfaces.isEmpty()) {
return null;
}
return new CompositeSurface(surfaces);
}
}
......@@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Copy {
......@@ -36,7 +37,7 @@ public class Copy {
}
@SuppressWarnings("unchecked")
public static <T extends Copyable> T copy(T original) {
public static <T extends Serializable> T copy(T original) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(out);
......
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