Commit 5d40c7b6 authored by Matthias Betz's avatar Matthias Betz
Browse files

CityDoctor2 validation open source release

parents
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.vegetation.AbstractVegetationObject;
import org.citygml4j.model.citygml.vegetation.PlantCover;
import org.citygml4j.model.citygml.vegetation.SolitaryVegetationObject;
import org.citygml4j.model.gml.geometry.aggregates.MultiSolid;
import org.citygml4j.model.gml.geometry.aggregates.MultiSolidProperty;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.citygml4j.model.gml.geometry.primitives.Solid;
import org.citygml4j.model.gml.geometry.primitives.SolidProperty;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
/**
* Representation of CityGML vegetation objects
*
* @author Matthias Betz
*
*/
public class Vegetation extends CityObject {
private static final long serialVersionUID = -5136358065541704146L;
public enum VegetationType {
SOLITARY_VEGETATION_OBJECT, PLANT_COVER
}
private AbstractVegetationObject citygmlVegetation;
private VegetationType type;
public Vegetation(VegetationType type) {
this.type = type;
}
@Override
public FeatureType getFeatureType() {
return FeatureType.VEGETATION;
}
@Override
public void reCreateGeometries(GMLGeometryFactory factory, ParserConfiguration config) {
for (Geometry geom : getGeometries()) {
if (geom.getType() == GeometryType.MULTI_SURFACE) {
MultiSurface ms = CityGmlUtils.createMultiSurface(geom, factory, config);
if (type == VegetationType.SOLITARY_VEGETATION_OBJECT) {
SolitaryVegetationObject svo = (SolitaryVegetationObject) citygmlVegetation;
setMultiSurfaceAccordingToLod(svo, ms, geom.getLod());
} else {
PlantCover pc = (PlantCover) citygmlVegetation;
setMultiSurfaceAccordingToLod(pc, ms, geom.getLod());
}
} else {
Solid solid = CityGmlUtils.createSolid(geom, factory, config);
if (type == VegetationType.SOLITARY_VEGETATION_OBJECT) {
SolitaryVegetationObject svo = (SolitaryVegetationObject) citygmlVegetation;
setSolidAccordingToLod(svo, solid, geom.getLod());
} else {
PlantCover pc = (PlantCover) citygmlVegetation;
setSolidAccordingToLod(pc, solid, geom.getLod());
}
}
}
}
private void setSolidAccordingToLod(PlantCover pc, Solid solid, Lod lod) {
switch (lod) {
case LOD1:
pc.setLod1MultiSolid(new MultiSolidProperty(new MultiSolid(solid)));
break;
case LOD2:
pc.setLod2MultiSolid(new MultiSolidProperty(new MultiSolid(solid)));
break;
case LOD3:
pc.setLod3MultiSolid(new MultiSolidProperty(new MultiSolid(solid)));
break;
case LOD4:
pc.setLod4MultiSolid(new MultiSolidProperty(new MultiSolid(solid)));
break;
default:
throw new IllegalStateException("Cannot set Solid with lod to PlantCover:" + lod);
}
}
private void setSolidAccordingToLod(SolitaryVegetationObject svo, Solid solid, Lod lod) {
switch (lod) {
case LOD1:
svo.setLod1Geometry(new SolidProperty(solid));
break;
case LOD2:
svo.setLod2Geometry(new SolidProperty(solid));
break;
case LOD3:
svo.setLod3Geometry(new SolidProperty(solid));
break;
case LOD4:
svo.setLod4Geometry(new SolidProperty(solid));
break;
default:
throw new IllegalStateException("Cannot set Solid with lod to SolitaryVegetationObject:" + lod);
}
}
private void setMultiSurfaceAccordingToLod(PlantCover pc, MultiSurface ms, Lod lod) {
switch (lod) {
case LOD1:
pc.setLod1MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD2:
pc.setLod2MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD3:
pc.setLod3MultiSurface(new MultiSurfaceProperty(ms));
break;
case LOD4:
pc.setLod4MultiSurface(new MultiSurfaceProperty(ms));
break;
default:
throw new IllegalStateException("Cannot set MultiSurface with lod to PlantCover:" + lod);
}
}
private void setMultiSurfaceAccordingToLod(SolitaryVegetationObject svo, MultiSurface ms, Lod lod) {
switch (lod) {
case LOD1:
svo.setLod1Geometry(new MultiSurfaceProperty(ms));
break;
case LOD2:
svo.setLod2Geometry(new MultiSurfaceProperty(ms));
break;
case LOD3:
svo.setLod3Geometry(new MultiSurfaceProperty(ms));
break;
case LOD4:
svo.setLod4Geometry(new MultiSurfaceProperty(ms));
break;
default:
throw new IllegalStateException("Cannot set MultiSurface with lod to SolitaryVegetationObject:" + lod);
}
}
@Override
public void accept(Check c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
}
}
@Override
public AbstractCityObject getGmlObject() {
return citygmlVegetation;
}
@Override
public void unsetGmlGeometries() {
if (type == VegetationType.SOLITARY_VEGETATION_OBJECT) {
SolitaryVegetationObject svo = (SolitaryVegetationObject) citygmlVegetation;
svo.unsetLod1Geometry();
svo.unsetLod2Geometry();
svo.unsetLod3Geometry();
svo.unsetLod4Geometry();
} else {
PlantCover pc = (PlantCover) citygmlVegetation;
pc.unsetLod1MultiSurface();
pc.unsetLod2MultiSurface();
pc.unsetLod3MultiSurface();
pc.unsetLod4MultiSurface();
}
}
public void setGmlObject(AbstractVegetationObject avo) {
citygmlVegetation = avo;
}
public VegetationType getVegetationType() {
return type;
}
@Override
public String toString() {
return "Vegetation [id=" + getGmlId() + "]";
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;
/**
* Contains the vertex information of a point in a linear ring
*
* @author Matthias Betz
*
*/
public class Vertex extends Vector3d {
private static final long serialVersionUID = -5525361920397934892L;
private List<SerializablePair<Geometry, HashSet<Polygon>>> adjacentPolygons = new ArrayList<>(2);
private List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);
private List<Vertex> neighbors;
public Vertex(double x, double y, double z) {
super(x, y, z);
}
public Vertex(Vector3d vec) {
super(vec);
}
public void addNeighbor(Vertex vec) {
if (neighbors == null) {
neighbors = new ArrayList<>(1);
}
neighbors.add(vec);
}
public List<Vertex> getNeighbors() {
if (neighbors == null) {
return Collections.emptyList();
}
return neighbors;
}
private Set<LinearRing> getAdjacentRingsWithoutNeightbor(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) {
for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
if (adjacency.getValue0() == geom) {
if (neighbors == null || neighbors.isEmpty()) {
return adjacency.getValue1();
} else {
Set<LinearRing> linearRings = new HashSet<>();
linearRings.addAll(adjacency.getValue1());
for (Vertex neighbor : neighbors) {
linearRings.addAll(neighbor.getAdjacentRingsWithoutNeightbor(geom));
}
return linearRings;
}
}
}
throw new IllegalStateException("Requested adjacent rings with Geometry not containing this vertex");
}
void addAdjacentRing(Polygon poly, LinearRing ring, Geometry geom) {
findAdjacentRingsForGeometry(geom).add(ring);
findAdjacentPolygonsForGeometry(geom).add(poly);
}
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> findAdjacentPolygonsForGeometry(Geometry geom) {
HashSet<Polygon> adjacendPolygonsSet = null;
for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
if (adjacency.getValue0() == geom) {
adjacendPolygonsSet = adjacency.getValue1();
}
}
if (adjacendPolygonsSet == null) {
adjacendPolygonsSet = new HashSet<>(4);
adjacentPolygons.add(new SerializablePair<>(geom, adjacendPolygonsSet));
}
return adjacendPolygonsSet;
}
public Set<Polygon> getAdjacentPolygonsWithoutNeighbor(Geometry geom) {
for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
if (adjacency.getValue0() == geom) {
return adjacency.getValue1();
}
}
throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
}
public Set<Polygon> getAdjacentPolygons(Geometry geom) {
for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
if (adjacency.getValue0() == geom) {
if (neighbors == null || neighbors.isEmpty()) {
return adjacency.getValue1();
} else {
Set<Polygon> polygons = new HashSet<>();
polygons.addAll(adjacency.getValue1());
for (Vertex neighbor : neighbors) {
polygons.addAll(neighbor.getAdjacentPolygonsWithoutNeighbor(geom));
}
return polygons;
}
}
}
throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
return prime * result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
return getClass() == obj.getClass();
}
@Override
public String toString() {
return "Vertex [x=" + getX() + ", y=" + getY() + ", z=" + getZ() + "]";
}
public void clearAdjacentRings(Geometry geometry) {
findAdjacentRingsForGeometry(geometry).clear();
findAdjacentPolygonsForGeometry(geometry).clear();
}
void removeAdjacency(LinearRing lr, Polygon p, Geometry geom) {
findAdjacentPolygonsForGeometry(geom).remove(p);
findAdjacentRingsForGeometry(geom).remove(lr);
}
public boolean equalsWithNeighbors(Vertex other) {
if (this.equals(other)) {
return true;
}
for (Vertex v : other.getNeighbors()) {
if (this.equals(v)) {
return true;
}
}
return checkNeighbors(other);
}
private boolean checkNeighbors(Vertex other) {
if (neighbors != null) {
for (Vertex v : neighbors) {
if (v.equals(other)) {
return true;
}
for (Vertex otherNeighbor : other.getNeighbors()) {
if (otherNeighbor.equals(v)) {
return true;
}
}
}
}
return false;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.waterbody.WaterBody;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.CityGmlUtils;
/**
* Represents cityGML water body objects.
*
* @author Matthias Betz
*
*/
public class WaterObject extends CityObject {
private static final long serialVersionUID = -3821060595086337424L;
private WaterBody gmlWater;
@Override
public void reCreateGeometries(GMLGeometryFactory factory, ParserConfiguration config) {
for (Geometry geom : getGeometries()) {
if (geom.getType() == GeometryType.MULTI_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.setLod1MultiSurface(new MultiSurfaceProperty(ms));
} else {
throw new IllegalStateException(
"Cannot add MultiSurface geometry with lod to WaterBody:" + geom.getLod());
}
} else {
throw new IllegalStateException("Cannot add Solid geometry to WaterBody");
}
}
}
@Override
public FeatureType getFeatureType() {
return FeatureType.WATER;
}
@Override
public void accept(Check c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
}
}
@Override
public AbstractCityObject getGmlObject() {
return gmlWater;
}
@Override
public void unsetGmlGeometries() {
gmlWater.unsetLod0MultiSurface();
gmlWater.unsetLod1MultiSurface();
gmlWater.unsetLod1Solid();
gmlWater.unsetLod2Solid();
gmlWater.unsetLod3Solid();
gmlWater.unsetLod4Solid();
}
public void setGmlObject(WaterBody waterBody) {
gmlWater = waterBody;
}
@Override
public String toString() {
return "WaterObject [id=" + getGmlId() + "]";
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.mapper;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.model.citygml.bridge.AbstractBridge;
import org.citygml4j.model.citygml.bridge.Bridge;
import org.citygml4j.model.citygml.bridge.BridgePart;
import org.citygml4j.model.citygml.building.BuildingInstallationProperty;
import org.citygml4j.model.citygml.building.BuildingPartProperty;
import org.citygml4j.model.citygml.building.OpeningProperty;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.citygml.landuse.LandUse;
import org.citygml4j.model.citygml.transportation.AuxiliaryTrafficArea;
import org.citygml4j.model.citygml.transportation.AuxiliaryTrafficAreaProperty;
import org.citygml4j.model.citygml.transportation.Railway;
import org.citygml4j.model.citygml.transportation.Road;
import org.citygml4j.model.citygml.transportation.Square;
import org.citygml4j.model.citygml.transportation.Track;
import org.citygml4j.model.citygml.transportation.TrafficArea;
import org.citygml4j.model.citygml.transportation.TrafficAreaProperty;
import org.citygml4j.model.citygml.transportation.TransportationComplex;
import org.citygml4j.model.citygml.vegetation.PlantCover;
import org.citygml4j.model.citygml.vegetation.SolitaryVegetationObject;
import org.citygml4j.model.citygml.waterbody.WaterBody;
import org.citygml4j.model.gml.geometry.AbstractGeometry;
import org.citygml4j.model.gml.geometry.GeometryProperty;
import org.citygml4j.model.gml.geometry.complexes.CompositeSurface;
import org.citygml4j.model.gml.geometry.primitives.AbstractSolid;
import org.citygml4j.util.walker.FeatureWalker;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject.BridgeType;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingInstallation;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
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.OpeningType;
import de.hft.stuttgart.citydoctor2.datastructure.SurfaceFeatureType;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject.TransportationType;
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.Pair;
/**
* This walker maps all cityGML4j features to the respective CityDoctor data
* structure.
*
* @author Matthias Betz
*
*/
public class FeatureMapper extends FeatureWalker {
private static Logger logger = LogManager.getLogger(FeatureMapper.class);
private CityDoctorModel model;
private ParserConfiguration config;
private double neighborDistance;
public FeatureMapper(ParserConfiguration config, File file) {
this.config = config;
neighborDistance = 1.8d / Math.pow(10, config.getNumberOfRoundingPlaces());
model = new CityDoctorModel(config, file);
}
@Override
public void visit(WaterBody waterBody) {
WaterObject wo = new WaterObject();
model.addWater(wo);
wo.setGmlObject(waterBody);
if (waterBody.isSetId()) {
wo.setGmlId(new GmlId(waterBody.getId()));
}
mapWaterBodyGeometries(waterBody, wo);
}
private void mapWaterBodyGeometries(WaterBody waterBody, WaterObject wo) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
GeometryMapper.mapLod0MultiSurface(waterBody.getLod0MultiSurface(), wo, config, polygons, linkedPolygons,
vertexMap);
waterBody.unsetLod0MultiSurface();
GeometryMapper.mapLod1MultiSurface(waterBody.getLod1MultiSurface(), wo, config, polygons, linkedPolygons,
vertexMap);
waterBody.unsetLod1MultiSurface();
GeometryMapper.mapLod1Solid(waterBody.getLod1Solid(), wo, config, polygons, linkedPolygons, vertexMap);
waterBody.unsetLod1Solid();
GeometryMapper.mapLod2Solid(waterBody.getLod2Solid(), wo, config, polygons, linkedPolygons, vertexMap);
waterBody.unsetLod2Solid();
GeometryMapper.mapLod3Solid(waterBody.getLod3Solid(), wo, config, polygons, linkedPolygons, vertexMap);
waterBody.unsetLod3Solid();
GeometryMapper.mapLod4Solid(waterBody.getLod4Solid(), wo, config, polygons, linkedPolygons, vertexMap);
waterBody.unsetLod4Solid();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(wo);
}
private void createLinkedPolygons(Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons) {
for (Pair<String, Geometry> link : linkedPolygons) {
ConcretePolygon concPoly = polygons.get(link.getValue0());
if (concPoly == null) {
logger.warn("Polygon {} is referenced but not found in feature polygons", link.getValue0());
continue;
}
LinkedPolygon lPoly = new LinkedPolygon(concPoly, link.getValue1());
link.getValue1().addPolygon(lPoly);
}
}
private void updateEdgesAndVertices(CityObject co) {
for (Geometry geom : co.getGeometries()) {
geom.updateVertices();
KDTree tree = new KDTree();
for (Vertex v : geom.getVertices()) {
tree.add(v);
}
for (Vertex v : geom.getVertices()) {
List<Vertex> nodesInRange = tree.getNodesInRange(v, neighborDistance);
for (Vertex neighbor : nodesInRange) {
if (neighbor == v) {
continue;
}
v.addNeighbor(neighbor);
}
}
geom.updateEdges();
}
}
@Override
public void visit(PlantCover plantCover) {
Vegetation veg = new Vegetation(VegetationType.PLANT_COVER);
model.addVegetation(veg);
mapPlantCover(plantCover, veg);
}
private void mapPlantCover(PlantCover plantCover, Vegetation veg) {
veg.setGmlObject(plantCover);
if (plantCover.isSetId()) {
veg.setGmlId(new GmlId(plantCover.getId()));
}
mapPlantCoverGeometries(plantCover, veg);
}
private void mapPlantCoverGeometries(PlantCover plantCover, Vegetation veg) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod1MultiSurface(plantCover.getLod1MultiSurface(), veg, config, polygons, linkedPolygons,
vertexMap);
plantCover.unsetLod1MultiSurface();
GeometryMapper.mapLod2MultiSurface(plantCover.getLod2MultiSurface(), veg, config, polygons, linkedPolygons,
vertexMap);
plantCover.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(plantCover.getLod3MultiSurface(), veg, config, polygons, linkedPolygons,
vertexMap);
plantCover.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(plantCover.getLod4MultiSurface(), veg, config, polygons, linkedPolygons,
vertexMap);
plantCover.unsetLod4MultiSurface();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(veg);
}
@Override
public void visit(SolitaryVegetationObject solitaryVegetationObject) {
Vegetation veg = new Vegetation(VegetationType.SOLITARY_VEGETATION_OBJECT);
model.addVegetation(veg);
mapSolitaryVegObject(solitaryVegetationObject, veg);
}
private void mapSolitaryVegObject(SolitaryVegetationObject svo, Vegetation veg) {
veg.setGmlObject(svo);
if (svo.isSetId()) {
veg.setGmlId(new GmlId(svo.getId()));
}
mapSolitaryVegGeometries(svo, veg);
}
private void mapSolitaryVegGeometries(SolitaryVegetationObject svo, Vegetation veg) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod1Geometry(svo.getLod1Geometry(), veg, config, polygons, linkedPolygons, vertexMap);
svo.unsetLod1Geometry();
GeometryMapper.mapLod2Geometry(svo.getLod2Geometry(), veg, config, polygons, linkedPolygons, vertexMap);
svo.unsetLod2Geometry();
GeometryMapper.mapLod3Geometry(svo.getLod3Geometry(), veg, config, polygons, linkedPolygons, vertexMap);
svo.unsetLod3Geometry();
GeometryMapper.mapLod4Geometry(svo.getLod4Geometry(), veg, config, polygons, linkedPolygons, vertexMap);
svo.unsetLod4Geometry();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(veg);
}
@Override
public void visit(LandUse landUse) {
LandObject lo = new LandObject(landUse);
model.addLand(lo);
mapLandUse(landUse, lo);
}
private void mapLandUse(LandUse landUse, LandObject lo) {
lo.setGmlObject(landUse);
if (landUse.isSetId()) {
lo.setGmlId(new GmlId(landUse.getId()));
}
mapLandUseGeometries(landUse, lo);
}
private void mapLandUseGeometries(LandUse landUse, LandObject lo) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod0MultiSurface(landUse.getLod0MultiSurface(), lo, config, polygons, linkedPolygons,
vertexMap);
landUse.unsetLod0MultiSurface();
GeometryMapper.mapLod1MultiSurface(landUse.getLod1MultiSurface(), lo, config, polygons, linkedPolygons,
vertexMap);
landUse.unsetLod1MultiSurface();
GeometryMapper.mapLod2MultiSurface(landUse.getLod2MultiSurface(), lo, config, polygons, linkedPolygons,
vertexMap);
landUse.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(landUse.getLod3MultiSurface(), lo, config, polygons, linkedPolygons,
vertexMap);
landUse.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(landUse.getLod4MultiSurface(), lo, config, polygons, linkedPolygons,
vertexMap);
landUse.unsetLod4MultiSurface();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(lo);
}
@Override
public void visit(Road road) {
TransportationObject trans = new TransportationObject(TransportationType.ROAD);
mapTransportationComplex(road, trans);
}
@Override
public void visit(Railway railway) {
TransportationObject trans = new TransportationObject(TransportationType.RAILWAY);
mapTransportationComplex(railway, trans);
}
@Override
public void visit(Square square) {
TransportationObject trans = new TransportationObject(TransportationType.SQUARE);
mapTransportationComplex(square, trans);
}
@Override
public void visit(Track track) {
TransportationObject trans = new TransportationObject(TransportationType.TRACK);
mapTransportationComplex(track, trans);
}
@Override
public void visit(AuxiliaryTrafficArea auxiliaryTrafficArea) {
TransportationObject trans = new TransportationObject(TransportationType.AUXILLIARY_TRAFFIC_AREA);
mapAuxTrafficArea(auxiliaryTrafficArea, trans);
}
@Override
public void visit(TransportationComplex tc) {
TransportationObject trans = new TransportationObject(TransportationType.TRANSPORTATION_COMPLEX);
model.addTransportation(trans);
mapTransportationComplex(tc, trans);
if (tc.isSetAuxiliaryTrafficArea()) {
for (AuxiliaryTrafficAreaProperty atap : tc.getAuxiliaryTrafficArea()) {
if (atap.isSetAuxiliaryTrafficArea()) {
TransportationObject subTrans = new TransportationObject(
TransportationType.AUXILLIARY_TRAFFIC_AREA);
trans.addChild(subTrans);
mapAuxTrafficArea(atap.getAuxiliaryTrafficArea(), subTrans);
}
}
}
if (tc.isSetTrafficArea()) {
for (TrafficAreaProperty tap : tc.getTrafficArea()) {
if (tap.isSetTrafficArea()) {
TransportationObject subTrans = new TransportationObject(TransportationType.TRAFFIC_AREA);
trans.addChild(subTrans);
mapTrafficArea(tap.getTrafficArea(), subTrans);
}
}
}
}
@Override
public void visit(TrafficArea trafficArea) {
TransportationObject trans = new TransportationObject(TransportationType.TRAFFIC_AREA);
model.addTransportation(trans);
mapTrafficArea(trafficArea, trans);
}
private void mapTrafficArea(TrafficArea ta, TransportationObject to) {
to.setGmlObject(ta);
if (ta.isSetId()) {
to.setGmlId(new GmlId(ta.getId()));
}
mapTrafficAreaGeometries(ta, to);
}
private void mapTrafficAreaGeometries(TrafficArea ta, TransportationObject to) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod2MultiSurface(ta.getLod2MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ta.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(ta.getLod3MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ta.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(ta.getLod4MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ta.unsetLod4MultiSurface();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(to);
}
private void mapAuxTrafficArea(AuxiliaryTrafficArea ata, TransportationObject to) {
to.setGmlObject(ata);
if (ata.isSetId()) {
to.setGmlId(new GmlId(ata.getId()));
}
mapAuxTrafficAreaGeometries(ata, to);
}
private void mapAuxTrafficAreaGeometries(AuxiliaryTrafficArea ata, TransportationObject to) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod2MultiSurface(ata.getLod2MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ata.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(ata.getLod3MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ata.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(ata.getLod4MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
ata.unsetLod4MultiSurface();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(to);
}
private void mapTransportationComplex(TransportationComplex tc, TransportationObject to) {
model.addTransportation(to);
to.setGmlObject(tc);
if (tc.isSetId()) {
to.setGmlId(new GmlId(tc.getId()));
}
mapTransportationComplexGeometries(tc, to);
}
private void mapTransportationComplexGeometries(TransportationComplex tc, TransportationObject to) {
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
GeometryMapper.mapLod1MultiSurface(tc.getLod1MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
tc.unsetLod1MultiSurface();
GeometryMapper.mapLod2MultiSurface(tc.getLod2MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
tc.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(tc.getLod3MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
tc.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(tc.getLod4MultiSurface(), to, config, polygons, linkedPolygons, vertexMap);
tc.unsetLod4MultiSurface();
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(to);
}
@Override
public void visit(org.citygml4j.model.citygml.building.Building building) {
Building coBuilding = new Building();
model.addBuilding(coBuilding);
mapAbstractBuilding(building, coBuilding);
if (building.isSetConsistsOfBuildingPart()) {
for (BuildingPartProperty bpp : building.getConsistsOfBuildingPart()) {
if (bpp.isSetBuildingPart()) {
BuildingPart coBuildingPart = new BuildingPart(coBuilding);
mapAbstractBuilding(bpp.getBuildingPart(), coBuildingPart);
}
}
}
}
private void mapAbstractBuilding(org.citygml4j.model.citygml.building.AbstractBuilding ab,
AbstractBuilding coBuilding) {
coBuilding.setGmlObject(ab);
if (ab.isSetId()) {
coBuilding.setGmlId(new GmlId(ab.getId()));
}
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
mapBuildingMultiSurfaceGeometries(ab, coBuilding, polygons, linkedPolygons, vertexMap);
mapBuildingSolidGeometries(ab, coBuilding, polygons, linkedPolygons, vertexMap);
// handle boundary surfaces
if (ab.isSetBoundedBySurface()) {
for (org.citygml4j.model.citygml.building.BoundarySurfaceProperty bsp : ab.getBoundedBySurface()) {
mapBuildingSurface(bsp, coBuilding, polygons, linkedPolygons, vertexMap);
}
}
// handle outer building installations
if (ab.isSetOuterBuildingInstallation()) {
for (BuildingInstallationProperty bip : ab.getOuterBuildingInstallation()) {
mapBuildingInstallation(bip, coBuilding, polygons, linkedPolygons, vertexMap);
}
}
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(coBuilding);
}
private void mapBuildingInstallation(BuildingInstallationProperty bip, AbstractBuilding coBuilding,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (!bip.isSetBuildingInstallation()) {
return;
}
BuildingInstallation coBi = new BuildingInstallation();
coBuilding.addBuildingInstallation(coBi);
org.citygml4j.model.citygml.building.BuildingInstallation gmlBi = bip.getBuildingInstallation();
coBi.setGmlObject(gmlBi);
if (gmlBi.isSetLod2Geometry()) {
GeometryProperty<? extends AbstractGeometry> abstractGeometry = gmlBi.getLod2Geometry();
GeometryType type = extractGeometryType(abstractGeometry);
Geometry geom = new Geometry(type, Lod.LOD2);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBuildingInstallationIntoGeometry(abstractGeometry, mapper, coBi);
coBi.addGeometry(geom);
gmlBi.unsetLod2Geometry();
}
if (gmlBi.isSetLod3Geometry()) {
GeometryProperty<? extends AbstractGeometry> abstractGeometry = gmlBi.getLod3Geometry();
GeometryType type = extractGeometryType(abstractGeometry);
Geometry geom = new Geometry(type, Lod.LOD3);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBuildingInstallationIntoGeometry(abstractGeometry, mapper, coBi);
coBi.addGeometry(geom);
gmlBi.unsetLod3Geometry();
}
if (gmlBi.isSetLod4Geometry()) {
GeometryProperty<? extends AbstractGeometry> abstractGeometry = gmlBi.getLod4Geometry();
GeometryType type = extractGeometryType(abstractGeometry);
Geometry geom = new Geometry(type, Lod.LOD4);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBuildingInstallationIntoGeometry(abstractGeometry, mapper, coBi);
coBi.addGeometry(geom);
gmlBi.unsetLod4Geometry();
}
// handle boundary surfaces
if (gmlBi.isSetBoundedBySurface()) {
for (org.citygml4j.model.citygml.building.BoundarySurfaceProperty bsp : gmlBi.getBoundedBySurface()) {
mapBuildingInstallationSurface(bsp, coBi, polygons, linkedPolygons, vertexMap);
}
}
updateEdgesAndVertices(coBi);
}
private GeometryType extractGeometryType(GeometryProperty<? extends AbstractGeometry> agp) {
AbstractGeometry abstractGeometry = agp.getObject();
if (abstractGeometry instanceof CompositeSurface) {
return GeometryType.COMPOSITE_SURFACE;
} else if (abstractGeometry instanceof AbstractSolid) {
return GeometryType.SOLID;
}
return GeometryType.MULTI_SURFACE;
}
private void mapBuildingSolidGeometries(org.citygml4j.model.citygml.building.AbstractBuilding ab,
AbstractBuilding coBuilding, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
GeometryMapper.mapLod1Solid(ab.getLod1Solid(), coBuilding, config, polygons, linkedPolygons, vertexMap);
ab.unsetLod1Solid();
GeometryMapper.mapLod2Solid(ab.getLod2Solid(), coBuilding, config, polygons, linkedPolygons, vertexMap);
ab.unsetLod2Solid();
GeometryMapper.mapLod3Solid(ab.getLod3Solid(), coBuilding, config, polygons, linkedPolygons, vertexMap);
ab.unsetLod3Solid();
GeometryMapper.mapLod4Solid(ab.getLod4Solid(), coBuilding, config, polygons, linkedPolygons, vertexMap);
ab.unsetLod4Solid();
}
private void mapBuildingMultiSurfaceGeometries(org.citygml4j.model.citygml.building.AbstractBuilding ab,
AbstractBuilding coBuilding, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
GeometryMapper.mapLod1MultiSurface(ab.getLod1MultiSurface(), coBuilding, config, polygons, linkedPolygons,
vertexMap);
ab.unsetLod1MultiSurface();
GeometryMapper.mapLod2MultiSurface(ab.getLod2MultiSurface(), coBuilding, config, polygons, linkedPolygons,
vertexMap);
ab.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(ab.getLod3MultiSurface(), coBuilding, config, polygons, linkedPolygons,
vertexMap);
ab.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(ab.getLod4MultiSurface(), coBuilding, config, polygons, linkedPolygons,
vertexMap);
ab.unsetLod4MultiSurface();
}
private void mapBuildingSurface(org.citygml4j.model.citygml.building.BoundarySurfaceProperty bsp,
AbstractBuilding coBuilding, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (!bsp.isSetBoundarySurface()) {
return;
}
org.citygml4j.model.citygml.building.AbstractBoundarySurface abs = bsp.getBoundarySurface();
BoundarySurfaceType surfaceType = mapSurfaceType(abs);
BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, surfaceType, abs);
if (abs.isSetId()) {
bs.setGmlId(new GmlId(abs.getId()));
}
coBuilding.addBoundarySurface(bs);
createBoundarySurfaceGeometries(abs, bs, null, polygons, linkedPolygons, vertexMap);
mapBuildingOpenings(abs.getOpening(), bs, polygons, linkedPolygons, vertexMap);
updateEdgesAndVertices(bs);
}
private void mapBuildingOpenings(List<OpeningProperty> openings, BoundarySurface bs,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (openings == null || openings.isEmpty()) {
return;
}
for (org.citygml4j.model.citygml.building.OpeningProperty op : openings) {
if (!op.isSetOpening()) {
continue;
}
org.citygml4j.model.citygml.building.AbstractOpening ap = op.getOpening();
OpeningType type = mapOpeningType(ap);
Opening opening = new Opening(type, SurfaceFeatureType.BUILDING, bs, ap);
bs.addOpening(opening);
createOpeningGeometries(ap, opening, polygons, linkedPolygons, vertexMap);
updateEdgesAndVertices(opening);
}
}
private void createOpeningGeometries(org.citygml4j.model.citygml.building.AbstractOpening ap, Opening opening,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (ap.isSetLod3MultiSurface()) {
GeometryMapper.mapLod3MultiSurface(ap.getLod3MultiSurface(), opening, config, polygons, linkedPolygons,
vertexMap);
ap.unsetLod3MultiSurface();
}
if (ap.isSetLod4MultiSurface()) {
GeometryMapper.mapLod4MultiSurface(ap.getLod3MultiSurface(), opening, config, polygons, linkedPolygons,
vertexMap);
ap.unsetLod4MultiSurface();
}
}
private OpeningType mapOpeningType(org.citygml4j.model.citygml.building.AbstractOpening ap) {
if (ap instanceof org.citygml4j.model.citygml.building.Door) {
return OpeningType.DOOR;
}
if (ap instanceof org.citygml4j.model.citygml.building.Window) {
return OpeningType.WINDOW;
}
return OpeningType.UNKNOWN;
}
private void mapBuildingInstallationSurface(org.citygml4j.model.citygml.building.BoundarySurfaceProperty bsp,
BuildingInstallation coBi, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (!bsp.isSetBoundarySurface()) {
return;
}
org.citygml4j.model.citygml.building.AbstractBoundarySurface abs = bsp.getBoundarySurface();
BoundarySurfaceType surfaceType = mapSurfaceType(abs);
BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, surfaceType, abs);
if (abs.isSetId()) {
bs.setGmlId(new GmlId(abs.getId()));
}
coBi.addBoundarySurface(bs);
createBoundarySurfaceGeometries(abs, bs, coBi, polygons, linkedPolygons, vertexMap);
mapBuildingOpenings(abs.getOpening(), bs, polygons, linkedPolygons, vertexMap);
updateEdgesAndVertices(bs);
}
private void createBoundarySurfaceGeometries(org.citygml4j.model.citygml.building.AbstractBoundarySurface abs,
BoundarySurface bs, BuildingInstallation coBi, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (abs.isSetLod2MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod2MultiSurface(), mapper, bs, coBi);
bs.addGeometry(geom);
abs.unsetLod2MultiSurface();
}
if (abs.isSetLod3MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD3);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod3MultiSurface(), mapper, bs, coBi);
bs.addGeometry(geom);
abs.unsetLod3MultiSurface();
}
if (abs.isSetLod4MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD4);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod4MultiSurface(), mapper, bs, coBi);
bs.addGeometry(geom);
abs.unsetLod4MultiSurface();
}
}
private void createBoundarySurfaceGeometries(org.citygml4j.model.citygml.bridge.AbstractBoundarySurface abs,
BoundarySurface bs, Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (abs.isSetLod2MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod2MultiSurface(), mapper, bs, null);
bs.addGeometry(geom);
abs.unsetLod2MultiSurface();
}
if (abs.isSetLod3MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD3);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod3MultiSurface(), mapper, bs, null);
bs.addGeometry(geom);
abs.unsetLod3MultiSurface();
}
if (abs.isSetLod4MultiSurface()) {
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD4);
GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
GeometryMapper.mapBoundarySurfaceIntoGeometry(abs.getLod4MultiSurface(), mapper, bs, null);
bs.addGeometry(geom);
abs.unsetLod4MultiSurface();
}
}
private BoundarySurfaceType mapSurfaceType(org.citygml4j.model.citygml.building.AbstractBoundarySurface abs) {
switch (abs.getCityGMLClass()) {
case BUILDING_WALL_SURFACE:
return BoundarySurfaceType.WALL;
case BUILDING_ROOF_SURFACE:
return BoundarySurfaceType.ROOF;
case BUILDING_GROUND_SURFACE:
return BoundarySurfaceType.GROUND;
case BUILDING_CLOSURE_SURFACE:
return BoundarySurfaceType.CLOSURE;
case BUILDING_FLOOR_SURFACE:
return BoundarySurfaceType.OUTER_FLOOR;
case OUTER_BUILDING_FLOOR_SURFACE:
return BoundarySurfaceType.OUTER_FLOOR;
case BUILDING_CEILING_SURFACE:
return BoundarySurfaceType.OUTER_CEILING;
case OUTER_BUILDING_CEILING_SURFACE:
return BoundarySurfaceType.OUTER_CEILING;
default:
return BoundarySurfaceType.UNDEFINED;
}
}
@Override
public void visit(Bridge bridge) {
BridgeObject coBridge = new BridgeObject(BridgeType.BRIDGE, bridge);
mapBridge(bridge, coBridge);
}
@Override
public void visit(BridgePart bridgePart) {
BridgeObject coBridge = new BridgeObject(BridgeType.BRIDGE_PART, bridgePart);
mapBridge(bridgePart, coBridge);
}
private void mapBridge(AbstractBridge aBridge, BridgeObject coBridge) {
model.addBridge(coBridge);
coBridge.setGmlObject(aBridge);
if (aBridge.isSetId()) {
coBridge.setGmlId(new GmlId(aBridge.getId()));
}
Map<String, ConcretePolygon> polygons = new HashMap<>();
List<Pair<String, Geometry>> linkedPolygons = new ArrayList<>();
Map<Vertex, Vertex> vertexMap = new HashMap<>();
// map geometries
mapBridgeMultiSurfaceGeometries(aBridge, coBridge, polygons, linkedPolygons, vertexMap);
mapBridgeSolidGeometries(aBridge, coBridge, polygons, linkedPolygons, vertexMap);
// handle boundary surfaces
if (aBridge.isSetBoundedBySurface()) {
for (org.citygml4j.model.citygml.bridge.BoundarySurfaceProperty bsp : aBridge.getBoundedBySurface()) {
mapBridgeSurface(bsp, coBridge, polygons, linkedPolygons, vertexMap);
}
}
createLinkedPolygons(polygons, linkedPolygons);
updateEdgesAndVertices(coBridge);
}
private void mapBridgeMultiSurfaceGeometries(AbstractBridge aBridge, BridgeObject coBridge,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
GeometryMapper.mapLod1MultiSurface(aBridge.getLod1MultiSurface(), coBridge, config, polygons, linkedPolygons,
vertexMap);
aBridge.unsetLod1MultiSurface();
GeometryMapper.mapLod2MultiSurface(aBridge.getLod2MultiSurface(), coBridge, config, polygons, linkedPolygons,
vertexMap);
aBridge.unsetLod2MultiSurface();
GeometryMapper.mapLod3MultiSurface(aBridge.getLod3MultiSurface(), coBridge, config, polygons, linkedPolygons,
vertexMap);
aBridge.unsetLod3MultiSurface();
GeometryMapper.mapLod4MultiSurface(aBridge.getLod4MultiSurface(), coBridge, config, polygons, linkedPolygons,
vertexMap);
aBridge.unsetLod4MultiSurface();
}
private void mapBridgeSolidGeometries(AbstractBridge aBridge, BridgeObject coBridge,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
GeometryMapper.mapLod1Solid(aBridge.getLod1Solid(), coBridge, config, polygons, linkedPolygons, vertexMap);
aBridge.unsetLod1Solid();
GeometryMapper.mapLod2Solid(aBridge.getLod2Solid(), coBridge, config, polygons, linkedPolygons, vertexMap);
aBridge.unsetLod2Solid();
GeometryMapper.mapLod3Solid(aBridge.getLod3Solid(), coBridge, config, polygons, linkedPolygons, vertexMap);
aBridge.unsetLod3Solid();
GeometryMapper.mapLod4Solid(aBridge.getLod4Solid(), coBridge, config, polygons, linkedPolygons, vertexMap);
aBridge.unsetLod4Solid();
}
private void mapBridgeSurface(org.citygml4j.model.citygml.bridge.BoundarySurfaceProperty bsp, BridgeObject coBridge,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (!bsp.isSetBoundarySurface()) {
return;
}
org.citygml4j.model.citygml.bridge.AbstractBoundarySurface abs = bsp.getBoundarySurface();
BoundarySurfaceType surfaceType = mapSurfaceType(abs);
BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BRIDGE, surfaceType, abs);
bs.setGmlObject(abs);
coBridge.addBoundarySurface(bs);
mapBridgeOpenings(abs.getOpening(), bs, polygons, linkedPolygons, vertexMap);
createBoundarySurfaceGeometries(abs, bs, polygons, linkedPolygons, vertexMap);
updateEdgesAndVertices(bs);
}
private void mapBridgeOpenings(List<org.citygml4j.model.citygml.bridge.OpeningProperty> openings,
BoundarySurface bs, Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (openings == null || openings.isEmpty()) {
return;
}
for (org.citygml4j.model.citygml.bridge.OpeningProperty op : openings) {
if (!op.isSetOpening()) {
continue;
}
org.citygml4j.model.citygml.bridge.AbstractOpening ap = op.getOpening();
OpeningType type = mapOpeningType(ap);
Opening opening = new Opening(type, SurfaceFeatureType.BRIDGE, bs, ap);
bs.addOpening(opening);
createOpeningGeometries(ap, opening, polygons, linkedPolygons, vertexMap);
updateEdgesAndVertices(opening);
}
}
private void createOpeningGeometries(org.citygml4j.model.citygml.bridge.AbstractOpening ap, Opening opening,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (ap.isSetLod3MultiSurface()) {
GeometryMapper.mapLod3MultiSurface(ap.getLod3MultiSurface(), opening, config, polygons, linkedPolygons,
vertexMap);
ap.unsetLod3MultiSurface();
}
if (ap.isSetLod4MultiSurface()) {
GeometryMapper.mapLod4MultiSurface(ap.getLod3MultiSurface(), opening, config, polygons, linkedPolygons,
vertexMap);
ap.unsetLod4MultiSurface();
}
}
private OpeningType mapOpeningType(org.citygml4j.model.citygml.bridge.AbstractOpening ap) {
if (ap instanceof org.citygml4j.model.citygml.bridge.Door) {
return OpeningType.DOOR;
}
if (ap instanceof org.citygml4j.model.citygml.bridge.Window) {
return OpeningType.WINDOW;
}
return OpeningType.UNKNOWN;
}
private BoundarySurfaceType mapSurfaceType(org.citygml4j.model.citygml.bridge.AbstractBoundarySurface abs) {
switch (abs.getCityGMLClass()) {
case BRIDGE_CEILING_SURFACE:
return BoundarySurfaceType.CEILING;
case BRIDGE_CLOSURE_SURFACE:
return BoundarySurfaceType.CLOSURE;
case BRIDGE_FLOOR_SURFACE:
return BoundarySurfaceType.FLOOR;
case BRIDGE_GROUND_SURFACE:
return BoundarySurfaceType.GROUND;
case BRIDGE_ROOF_SURFACE:
return BoundarySurfaceType.ROOF;
case BRIDGE_WALL_SURFACE:
return BoundarySurfaceType.WALL;
case INTERIOR_BRIDGE_WALL_SURFACE:
return BoundarySurfaceType.INTERIOR_WALL;
case OUTER_BRIDGE_CEILING_SURFACE:
return BoundarySurfaceType.OUTER_CEILING;
case OUTER_BUILDING_FLOOR_SURFACE:
return BoundarySurfaceType.OUTER_FLOOR;
default:
return BoundarySurfaceType.UNDEFINED;
}
}
public CityDoctorModel getModel() {
return model;
}
public void setCityModel(CityModel cModel) {
model.setCityModel(cModel);
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.mapper;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.model.gml.geometry.AbstractGeometry;
import org.citygml4j.model.gml.geometry.GeometryProperty;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.citygml4j.model.gml.geometry.complexes.CompositeSurface;
import org.citygml4j.model.gml.geometry.primitives.AbstractRing;
import org.citygml4j.model.gml.geometry.primitives.AbstractRingProperty;
import org.citygml4j.model.gml.geometry.primitives.Coord;
import org.citygml4j.model.gml.geometry.primitives.DirectPosition;
import org.citygml4j.model.gml.geometry.primitives.DirectPositionList;
import org.citygml4j.model.gml.geometry.primitives.PosOrPointPropertyOrPointRep;
import org.citygml4j.model.gml.geometry.primitives.SolidProperty;
import org.citygml4j.model.gml.geometry.primitives.SurfaceProperty;
import org.citygml4j.util.walker.GeometryWalker;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.ProjCoordinate;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingInstallation;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Pair;
/**
*
* @author Matthias Betz
*
*/
public class GeometryMapper extends GeometryWalker {
private static Logger logger = LogManager.getLogger(GeometryMapper.class);
private Geometry geom;
private ParserConfiguration config;
private Map<Vertex, Vertex> vertexMap;
private Map<String, ConcretePolygon> polygons;
private List<Pair<String, Geometry>> linkedPolygons;
private LinearRing currentRing;
private BoundarySurface bs;
private BuildingInstallation bi;
private ProjCoordinate p1 = new ProjCoordinate();
private ProjCoordinate p2 = new ProjCoordinate();
public GeometryMapper(Geometry geom, ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertices) {
this.geom = geom;
this.config = config;
vertexMap = vertices;
this.polygons = polygons;
this.linkedPolygons = linkedPolygons;
}
public void setBoundarySurface(BoundarySurface bs) {
this.bs = bs;
}
public void setBuildingInstallation(BuildingInstallation bi) {
this.bi = bi;
}
@Override
public void visit(MultiSurface multiSurface) {
super.visit(multiSurface);
for (SurfaceProperty sp : multiSurface.getSurfaceMember()) {
if (sp.isSetHref()) {
String linkedPoly = sp.getHref();
if (linkedPoly.charAt(0) == '#') {
linkedPoly = linkedPoly.substring(1);
}
linkedPolygons.add(new Pair<>(linkedPoly, geom));
}
}
}
@Override
public void visit(CompositeSurface compositeSurface) {
super.visit(compositeSurface);
for (SurfaceProperty sp : compositeSurface.getSurfaceMember()) {
if (sp.isSetHref()) {
String linkedPoly = sp.getHref();
if (linkedPoly.charAt(0) == '#') {
linkedPoly = linkedPoly.substring(1);
}
linkedPolygons.add(new Pair<>(linkedPoly, geom));
}
}
}
@Override
public void visit(org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly) {
if (!gmlPoly.isSetExterior()) {
logger.warn("Found polygon without exterior ring, ignoring");
return;
}
ConcretePolygon cdPoly = new ConcretePolygon();
if (gmlPoly.isSetId()) {
cdPoly.setGmlId(new GmlId(gmlPoly.getId()));
polygons.put(gmlPoly.getId(), cdPoly);
}
geom.addPolygon(cdPoly);
if (bs != null) {
// polygon is part of a boundary surface
cdPoly.setPartOfSurface(bs);
if (bi != null) {
// polygon is part of a boundary surface in a building installation
cdPoly.setPartOfInstallation(bi);
}
} else {
if (bi != null) {
// polygon is only part of a building installation
cdPoly.setPartOfInstallation(bi);
}
}
// parse rings
LinearRing extRing = new LinearRing(LinearRingType.EXTERIOR);
cdPoly.setExteriorRing(extRing);
mapRing(gmlPoly.getExterior(), extRing);
if (gmlPoly.isSetInterior()) {
for (AbstractRingProperty arp : gmlPoly.getInterior()) {
if (arp.isSetRing()) {
LinearRing innerRing = new LinearRing(LinearRingType.INTERIOR);
cdPoly.addInteriorRing(innerRing);
mapRing(arp, innerRing);
}
}
}
}
private void mapRing(AbstractRingProperty gmlRing, LinearRing cdRing) {
AbstractRing ringGeometry = gmlRing.getRing();
if (ringGeometry.isSetId()) {
cdRing.setGmlId(new GmlId(ringGeometry.getId()));
}
currentRing = cdRing;
// jump to LinearRing or Ring visit
ringGeometry.accept(this);
}
@Override
public void visit(org.citygml4j.model.gml.geometry.primitives.LinearRing linearRing) {
if (linearRing.isSetCoord()) {
List<Coord> coords = linearRing.getCoord();
for (Coord coord : coords) {
createVertex(coord.getX(), coord.getY(), coord.getZ());
}
}
if (linearRing.isSetPosList()) {
parsePosList(linearRing);
}
if (linearRing.isSetPosOrPointPropertyOrPointRep()) {
parsePoints(linearRing);
}
}
private void parsePoints(org.citygml4j.model.gml.geometry.primitives.LinearRing linearRing) {
List<PosOrPointPropertyOrPointRep> points = linearRing.getPosOrPointPropertyOrPointRep();
for (PosOrPointPropertyOrPointRep point : points) {
if (!point.isSetPos()) {
throw new UnsupportedOperationException("Cannot parse points for: " + linearRing.getId());
}
DirectPosition pos = point.getPos();
List<Double> coords = pos.getValue();
int dimension = getDimension(linearRing, pos, coords);
switch (dimension) {
case 1:
createVertex(coords.get(0), 0, 0);
break;
case 2:
createVertex(coords.get(0), coords.get(1), 0);
break;
case 3:
createVertex(coords.get(0), coords.get(1), coords.get(2));
break;
default:
throw new UnsupportedOperationException("Cannot parse Coordinates with dimension:" + dimension);
}
}
}
private void parsePosList(org.citygml4j.model.gml.geometry.primitives.LinearRing linearRing) {
DirectPositionList directPosList = linearRing.getPosList();
if (directPosList.isSetValue()) {
List<Double> coords = directPosList.getValue();
int dimension = getDimension(linearRing, directPosList, coords);
switch (dimension) {
case 1:
for (int i = 0; i < coords.size(); i++) {
createVertex(coords.get(i), 0, 0);
}
break;
case 2:
for (int i = 0; i < coords.size(); i = i + 2) {
createVertex(coords.get(i + 0), coords.get(i + 1), 0);
}
break;
case 3:
for (int i = 0; i < coords.size(); i = i + 3) {
createVertex(coords.get(i + 0), coords.get(i + 1), coords.get(i + 2));
}
break;
default:
throw new UnsupportedOperationException("Cannot parse Coordinates with dimension:" + dimension);
}
}
}
private int getDimension(org.citygml4j.model.gml.geometry.primitives.LinearRing linearRing, DirectPosition pos,
List<Double> coords) {
int dimension = 3;
if (pos.isSetSrsDimension()) {
dimension = pos.getSrsDimension();
}
if (coords.size() % dimension != 0) {
throw new IllegalStateException("Number of coordinates do not match dimension: " + dimension + ", ring: "
+ linearRing.getId() + ", polygon: " + currentRing.getParent().getGmlId().getGmlString());
}
return dimension;
}
private int getDimension(org.citygml4j.model.gml.geometry.primitives.LinearRing linearRing,
DirectPositionList directPosList, List<Double> coords) {
int dimension = 3;
if (directPosList.isSetSrsDimension()) {
dimension = directPosList.getSrsDimension();
}
if (coords.size() % dimension != 0) {
throw new IllegalStateException("Number of coordinates do not match dimension: " + dimension + ", ring: "
+ linearRing.getId() + ", polygon: " + currentRing.getParent().getGmlId().getGmlString());
}
return dimension;
}
private void createVertex(double x, double y, double z) {
// transform into utm, if available
BasicCoordinateTransform trans = config.getTargetTransform();
if (trans != null) {
p1.setValue(x, y);
trans.transform(p1, p2);
x = p2.x;
y = p2.y;
z = z / config.getFromMetres();
}
x = round(x, config.getNumberOfRoundingPlaces());
y = round(y, config.getNumberOfRoundingPlaces());
z = round(z, config.getNumberOfRoundingPlaces());
Vertex v = new Vertex(x, y, z);
Vertex duplicate = vertexMap.get(v);
if (duplicate == null) {
vertexMap.put(v, v);
} else {
v = duplicate;
}
currentRing.addVertex(v);
}
private double round(double value, int places) {
if (places < 0) {
throw new IllegalArgumentException();
}
BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
private static GeometryMapper parseGeometry(AbstractGeometry gmlG, Geometry geom, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (gmlG.isSetId()) {
geom.setGmlId(new GmlId(gmlG.getId()));
}
co.addGeometry(geom);
GeometryMapper geomMapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap);
gmlG.accept(geomMapper);
return geomMapper;
}
private static void parseIntoGeometry(AbstractGeometry geometry, GeometryMapper geomMapper, BoundarySurface bs,
BuildingInstallation coBi) {
geomMapper.setBoundarySurface(bs);
geomMapper.setBuildingInstallation(coBi);
geometry.accept(geomMapper);
}
public static void mapLod0MultiSurface(MultiSurfaceProperty lod0ms, CityObject co, ParserConfiguration config,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (lod0ms == null || !lod0ms.isSetMultiSurface()) {
return;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD0);
parseGeometry(lod0ms.getMultiSurface(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod1MultiSurface(MultiSurfaceProperty lod1ms, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod1ms == null || !lod1ms.isSetMultiSurface()) {
return null;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD1);
return parseGeometry(lod1ms.getMultiSurface(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod2MultiSurface(MultiSurfaceProperty lod2MultiSurface, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod2MultiSurface == null || !lod2MultiSurface.isSetMultiSurface()) {
return null;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2);
return parseGeometry(lod2MultiSurface.getMultiSurface(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod3MultiSurface(MultiSurfaceProperty lod3MultiSurface, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod3MultiSurface == null || !lod3MultiSurface.isSetMultiSurface()) {
return null;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD3);
return parseGeometry(lod3MultiSurface.getMultiSurface(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod4MultiSurface(MultiSurfaceProperty lod4MultiSurface, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod4MultiSurface == null || !lod4MultiSurface.isSetMultiSurface()) {
return null;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD4);
return parseGeometry(lod4MultiSurface.getMultiSurface(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod1Solid(SolidProperty lod1Solid, CityObject co, ParserConfiguration config,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (lod1Solid == null || !lod1Solid.isSetSolid()) {
return null;
}
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD1);
return parseGeometry(lod1Solid.getSolid(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod2Solid(SolidProperty lod2Solid, CityObject co, ParserConfiguration config,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (lod2Solid == null || !lod2Solid.isSetSolid()) {
return null;
}
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD2);
return parseGeometry(lod2Solid.getSolid(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod3Solid(SolidProperty lod3Solid, CityObject co, ParserConfiguration config,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (lod3Solid == null || !lod3Solid.isSetSolid()) {
return null;
}
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD3);
return parseGeometry(lod3Solid.getSolid(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static GeometryMapper mapLod4Solid(SolidProperty lod4Solid, CityObject co, ParserConfiguration config,
Map<String, ConcretePolygon> polygons, List<Pair<String, Geometry>> linkedPolygons,
Map<Vertex, Vertex> vertexMap) {
if (lod4Solid == null || !lod4Solid.isSetSolid()) {
return null;
}
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD4);
return parseGeometry(lod4Solid.getSolid(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static void mapLod1Geometry(GeometryProperty<? extends AbstractGeometry> lod1Geometry, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod1Geometry == null || !lod1Geometry.isSetGeometry()) {
return;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD1);
parseGeometry(lod1Geometry.getGeometry(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static void mapLod2Geometry(GeometryProperty<? extends AbstractGeometry> lod2Geometry, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod2Geometry == null || !lod2Geometry.isSetGeometry()) {
return;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD2);
parseGeometry(lod2Geometry.getGeometry(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static void mapLod3Geometry(GeometryProperty<? extends AbstractGeometry> lod3Geometry, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod3Geometry == null || !lod3Geometry.isSetGeometry()) {
return;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD3);
parseGeometry(lod3Geometry.getGeometry(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static void mapLod4Geometry(GeometryProperty<? extends AbstractGeometry> lod4Geometry, CityObject co,
ParserConfiguration config, Map<String, ConcretePolygon> polygons,
List<Pair<String, Geometry>> linkedPolygons, Map<Vertex, Vertex> vertexMap) {
if (lod4Geometry == null || !lod4Geometry.isSetGeometry()) {
return;
}
Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, Lod.LOD4);
parseGeometry(lod4Geometry.getGeometry(), geom, co, config, polygons, linkedPolygons, vertexMap);
}
public static void mapBoundarySurfaceIntoGeometry(MultiSurfaceProperty msp, GeometryMapper geom, BoundarySurface bs,
BuildingInstallation coBi) {
if (msp == null || !msp.isSetGeometry()) {
return;
}
parseIntoGeometry(msp.getGeometry(), geom, bs, coBi);
}
public static void mapBuildingInstallationIntoGeometry(GeometryProperty<? extends AbstractGeometry> gp,
GeometryMapper geom, BuildingInstallation coBi) {
if (gp == null || !gp.isSetGeometry()) {
return;
}
parseIntoGeometry(gp.getGeometry(), geom, null, coBi);
}
public Geometry getGeometry() {
return geom;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.List;
/**
* Utility class for calculating covariance matrices
*
* @author Matthias Betz
*
*/
public class CovarianceMatrix {
private CovarianceMatrix() {
}
/**
* Calculates the covariance matrix of the given points, with the given expected
* values.
*
* @param vertices the vertices for which the matrix is calculated
* @param expected the expected values
* @return the covariance 3 x 3 matrix
*/
public static double[][] calculateCovarianceMatrix(List<? extends Vector3d> vertices, double[] expected) {
if (expected.length != 3) {
throw new IllegalArgumentException("for 3D points 3 expected values have to be provided");
}
double[][] covValues = new double[3][3];
for (Vector3d v : vertices) {
double xdiff = v.getX() - expected[0];
double ydiff = v.getY() - expected[1];
double zdiff = v.getZ() - expected[2];
covValues[0][0] += xdiff * xdiff;
covValues[0][1] += xdiff * ydiff;
covValues[0][2] += xdiff * zdiff;
covValues[1][1] += ydiff * ydiff;
covValues[1][2] += ydiff * zdiff;
covValues[2][2] += zdiff * zdiff;
}
// the covariance matrix is symmetric, so we can fill in the remaining values
covValues[1][0] = covValues[0][1];
covValues[2][0] = covValues[0][2];
covValues[2][1] = covValues[1][2];
return covValues;
}
/**
* see {@link CovarianceMatrix#calculateCovarianceMatrix(List, double[])}
*
* @param vertices the vertices for which the matrix is calculated
* @param expected the expected values as a vector
* @return the covariance 3 x 3 matrix
*/
public static double[][] calculateCovarianceMatrix(List<? extends Vector3d> vertices, Vector3d expected) {
return calculateCovarianceMatrix(vertices, expected.getCoordinates());
}
public static Vector3d getCentroid(List<? extends Vector3d> vertices) {
if (vertices.isEmpty()) {
throw new IllegalArgumentException("Vertices may not be empty to calculate centroid");
}
// Move vertices to origin to avoid numeric instabilities
Vector3d start = vertices.get(0);
// get the center of all points
double meanX = 0.0;
double meanY = 0.0;
double meanZ = 0.0;
for (Vector3d v : vertices) {
meanX += v.getX() - start.getX();
meanY += v.getY() - start.getY();
meanZ += v.getZ() - start.getZ();
}
meanX = meanX / vertices.size() + start.getX();
meanY = meanY / vertices.size() + start.getY();
meanZ = meanZ / vertices.size() + start.getZ();
return new Vector3d(meanX, meanY, meanZ);
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* Result of a distance calculation between two points
*
* @author Matthias Betz
*
*/
public class DistanceResult {
private Vector3d point1;
private Vector3d point2;
private double distance;
public DistanceResult(Vector3d point1, Vector3d point2, double distance) {
super();
this.point1 = point1;
this.point2 = point2;
this.distance = distance;
}
public Vector3d getPoint1() {
return point1;
}
public Vector3d getPoint2() {
return point2;
}
public double getDistance() {
return distance;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* A two dimensional edge between two points
*
* @author Matthias Betz
*
*/
public class Edge2d {
private static final double EPSILON = 0.0000001;
private static final double ONE_MINUS_EPSILON = 1 - EPSILON;
private ProjectedVector2d a;
private ProjectedVector2d b;
public Edge2d(ProjectedVector2d a, ProjectedVector2d b) {
this.a = a;
this.b = b;
}
public boolean intersects(Edge2d other) {
double x1 = a.getX();
double x2 = b.getX();
double x3 = other.a.getX();
double x4 = other.b.getX();
double y1 = a.getY();
double y2 = b.getY();
double y3 = other.a.getY();
double y4 = other.b.getY();
double a1 = x2 - x1;
double b1 = x4 - x3;
double c1 = x3 - x1;
double a2 = y2 - y1;
double b2 = y4 - y3;
double c2 = y3 - y1;
double detA = b1 * a2 - a1 * b2;
double detA1 = b1 * c2 - c1 * b2;
double detA2 = a1 * c2 - c1 * a2;
if (detA == 0) {
// lines parallel or coincident
if (detA1 == 0 && detA2 == 0) {
// check if they are colinear
return checkForContainingPoint(other);
} else {
// parallel
return false;
}
}
double s = detA1 / detA;
double t = detA2 / detA;
return !(s < EPSILON || s > ONE_MINUS_EPSILON || t < EPSILON || t > ONE_MINUS_EPSILON);
}
private boolean checkForContainingPoint(Edge2d other) {
if (containsPoint(other.a)) {
return true;
}
if (containsPoint(other.b)) {
return true;
}
if (other.containsPoint(a)) {
return true;
}
return other.containsPoint(b);
}
public Vector3d projectPoint(double scalar) {
Vector3d dir = b.getOriginal().minus(a.getOriginal());
return a.getOriginal().plus(dir.mult(scalar));
}
public boolean containsPoint(Vector2d p) {
double xDir = b.getX() - a.getX();
if (xDir == 0) {
double yDir = b.getY() - a.getY();
if (yDir == 0) {
// line is one point
return p.equals(a);
}
double d = (p.getY() - a.getY()) / yDir;
return d < ONE_MINUS_EPSILON && d > EPSILON;
}
double d = (p.getX() - a.getX()) / xDir;
if (d < EPSILON || d > ONE_MINUS_EPSILON) {
return false;
}
// direction in y
double yDir = b.getY() - a.getY();
// check for colinearity
return a.getY() + d * yDir == p.getY();
}
@Override
public String toString() {
return "Edge2d [a=" + a + ", b=" + b + "]";
}
public Vector2d getA() {
return a;
}
public Vector2d getB() {
return b;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 : a.hashCode());
result = prime * result + ((b == null) ? 0 : b.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Edge2d other = (Edge2d) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
if (b == null) {
if (other.b != null)
return false;
} else if (!b.equals(other.b))
return false;
return true;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* Result of an intersection calculation
*
* @author Matthias Betz
*
*/
public class IntersectionPoint2d {
private Line2d line;
private double scalar;
public IntersectionPoint2d(Line2d line, double scalar) {
super();
this.line = line;
this.scalar = scalar;
}
public Vector2d getIntersectionPoint() {
return line.getPoint().plus(line.getDirection().mult(scalar));
}
public Line2d getLine() {
return line;
}
public double getScalar() {
return scalar;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* A two dimensional line with a starting point and a direction
*
* @author Matthias Betz
*
*/
public class Line2d {
private Vector2d p;
private Vector2d dir;
public Line2d(Vector2d p, Vector2d dir) {
this.p = p;
this.dir = dir;
}
public Vector2d getPoint() {
return p;
}
public Vector2d getDirection() {
return dir;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* A line described by a point and a direction
*
* @author Matthias Betz
*
*/
public class Line3d {
public static final double EPSILON = 0.00001;
private Vector3d point;
private Vector3d direction;
public Line3d(Vector3d point, Vector3d direction) {
super();
this.point = point;
this.direction = direction;
}
/**
* @return the point
*/
public Vector3d getPoint() {
return point;
}
/**
* @return the direction
*/
public Vector3d getDirection() {
return direction;
}
public double distanceToPoint(Vector3d v) {
double x = v.getX() - point.getX();
double y = v.getY() - point.getY();
double z = v.getZ() - point.getZ();
Vector3d temp = new Vector3d(x, y, z);
return direction.cross(temp).getLength();
}
public Vector3d intersection(Triangle3d triangle) {
Vector3d v0 = triangle.getP1();
Vector3d v1 = triangle.getP2();
Vector3d v2 = triangle.getP3();
Vector3d edge1 = v1.minus(v0);
Vector3d edge2 = v2.minus(v0);
Vector3d p = direction.cross(edge2);
double det = edge1.dot(p);
if (det > -EPSILON && det < EPSILON) {
return null;
}
double invDet = 1 / det;
Vector3d s = point.minus(v0);
double u = invDet * s.dot(p);
if (u < 0.0 || u > 1.0) {
return null;
}
Vector3d q = s.cross(edge1);
double v = invDet * direction.dot(q);
if (v < 0.0 || u + v > 1.0) {
return null;
}
double t = edge2.dot(q) * invDet;
return getPointOnLine(t);
}
private Vector3d getPointOnLine(double distanceFromOrigin) {
return point.plus(direction.mult(distanceFromOrigin));
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.Arrays;
/**
* A 3x3 Matrix used for rotation of vectors
*
* @author Matthias Betz
*
*/
public class Matrix3x3d {
private double[][] values;
/**
* Identity matrix
*/
public static final Matrix3x3d IDENTITY = new Matrix3x3d(1d, 1d, 1d);
/**
* Creates an empty matrix, set with all 0.
*/
public Matrix3x3d() {
values = new double[3][3];
}
private Matrix3x3d(double[][] values, boolean copy) {
if (values.length != 3 || values[0].length != 3 || values[1].length != 3 || values[2].length != 3) {
throw new IllegalArgumentException("Only 3x3 arrays allowed");
}
if (copy) {
this.values = new double[3][];
this.values[0] = Arrays.copyOf(values[0], 3);
this.values[1] = Arrays.copyOf(values[1], 3);
this.values[2] = Arrays.copyOf(values[2], 3);
} else {
this.values = values;
}
}
/**
* creates a new matrix with the given 3 x 3 array. The array will be copied.
*
* @param values the 3x3 array containing the matrix values
*/
public Matrix3x3d(double[][] values) {
this(values, true);
}
/**
* Adds two matrices. A + B
*
* @param other the other matrix
* @return a new matrix object containing the result.
*/
public Matrix3x3d plus(Matrix3x3d other) {
double[][] newValues = new double[3][3];
for (int row = 0; row < 3; row++) {
for (int column = 0; column < 3; column++) {
newValues[row][column] = values[row][column] + other.getValue(row, column);
}
}
return new Matrix3x3d(newValues, false);
}
/**
* Matrix multiplication with a scalar A * s.
*
* @param s the scalar
* @return the result in a new matrix object
*/
public Matrix3x3d mult(double s) {
double[][] newValues = new double[3][3];
for (int row = 0; row < 3; row++) {
for (int column = 0; column < 3; column++) {
newValues[row][column] = values[row][column] * s;
}
}
return new Matrix3x3d(newValues, false);
}
/**
* Matrix multiplication with another matrix A * B.
*
* @param other the other matrix
* @return the result in a new matrix object
*/
public Matrix3x3d mult(Matrix3x3d other) {
double[][] newValues = new double[3][3];
for (int row = 0; row < 3; row++) {
for (int column = 0; column < 3; column++) {
newValues[row][column] = values[row][0] * other.getValue(0, column);
newValues[row][column] += values[row][1] * other.getValue(1, column);
newValues[row][column] += values[row][2] * other.getValue(2, column);
}
}
return new Matrix3x3d(newValues, false);
}
/**
* Transposes this matrix
*
* @return A new matrix with the transposed values, this instance is not
* changed.
*/
public Matrix3x3d transpose() {
Matrix3x3d x = new Matrix3x3d();
double[][] copyValues = x.values;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
copyValues[j][i] = values[i][j];
}
}
return x;
}
/**
* Multiplies this matrix with a 3-dim vector.
*
* @param other the vector
* @return the result stored in a new vector
*/
public Vector3d mult(Vector3d other) {
double x = values[0][0] * other.getX() + values[0][1] * other.getY() + values[0][2] * other.getZ();
double y = values[1][0] * other.getX() + values[1][1] * other.getY() + values[1][2] * other.getZ();
double z = values[2][0] * other.getX() + values[2][1] * other.getY() + values[2][2] * other.getZ();
return new Vector3d(x, y, z);
}
/**
* Returns the value at the requested location
*
* @param row the row
* @param column the column
* @return the value
*/
public double getValue(int row, int column) {
return values[row][column];
}
private Matrix3x3d(double m00, double m11, double m22) {
this();
values[0][0] = m00;
values[1][1] = m11;
values[2][2] = m22;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
sb.append(Arrays.toString(values[i]));
sb.append('\n');
}
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(values);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Matrix3x3d other = (Matrix3x3d) obj;
return Arrays.deepEquals(values, other.values);
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
/**
* A Polygon that has been moved by an offset point. Used to avoid numerical
* inaccuracies.
*
* @author Matthias Betz
*
*/
public class MovedPolygon {
private Polygon original;
private MovedRing exteriorRing;
private List<MovedRing> innerRings;
public static MovedPolygon ofPolygon(Polygon p, Vector3d movedBy) {
MovedPolygon indPoly = new MovedPolygon();
indPoly.original = p;
indPoly.exteriorRing = MovedRing.ofRing(p.getExteriorRing(), movedBy);
for (LinearRing inner : p.getInnerRings()) {
indPoly.addInnerRing(MovedRing.ofRing(inner, movedBy));
}
return indPoly;
}
public Polygon getOriginal() {
return original;
}
public void setExteriorRing(MovedRing exteriorRing) {
this.exteriorRing = exteriorRing;
}
public MovedRing getExteriorRing() {
return exteriorRing;
}
public void addInnerRing(MovedRing ring) {
if (innerRings == null) {
innerRings = new ArrayList<>();
}
innerRings.add(ring);
}
public List<MovedRing> getInnerRings() {
if (innerRings == null) {
return Collections.emptyList();
}
return innerRings;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* A ring moved by an offset. Used to avoid numerical inaccuracies
*
* @author Matthias Betz
*
*/
public class MovedRing {
private List<Vector3d> vertices;
private LinearRing original;
public static MovedRing ofRing(LinearRing ring, Vector3d movedBy) {
MovedRing indRing = new MovedRing();
indRing.original = ring;
for (Vertex v : ring.getVertices()) {
Vector3d movedV = new Vector3d(v.getX() - movedBy.getX(), v.getY() - movedBy.getY(),
v.getZ() - movedBy.getZ());
indRing.addVertex(movedV);
}
return indRing;
}
public LinearRing getOriginal() {
return original;
}
public MovedRing() {
vertices = new ArrayList<>();
}
public void addVertex(Vector3d v) {
vertices.add(v);
}
public List<Vector3d> getVertices() {
return vertices;
}
/**
* Checks whether a point is inside this ring. A point on the edge does count as
* inside.
*
* @param v the point.
* @return true if the point is inside or on an edge, false if it is outside.
*/
public boolean isPointInside(Vector3d v) {
// project to 2d ring
List<Vector2d> projectedRing = new ArrayList<>();
Vector2d point;
Vector3d normal = calculateNormal();
double x = Math.abs(normal.getX());
double y = Math.abs(normal.getY());
double z = Math.abs(normal.getZ());
if (x > y && x > z) {
for (Vector3d vert : vertices) {
Vector2d projCoords = new Vector2d(vert.getY(), vert.getZ());
projectedRing.add(projCoords);
}
point = new Vector2d(v.getY(), v.getZ());
} else if (y > x && y > z) {
for (Vector3d vert : vertices) {
Vector2d projCoords = new Vector2d(vert.getX(), vert.getZ());
projectedRing.add(projCoords);
}
point = new Vector2d(v.getX(), v.getZ());
} else {
for (Vector3d vert : vertices) {
Vector2d projCoords = new Vector2d(vert.getX(), vert.getY());
projectedRing.add(projCoords);
}
point = new Vector2d(v.getX(), v.getY());
}
int t = -1;
for (int i = 0; i < projectedRing.size() - 1; i++) {
t = t * crossProdTest(point, projectedRing.get(i), projectedRing.get(i + 1));
if (t == 0) {
return true;
}
}
return t >= 0;
}
private int crossProdTest(Vector2d a, Vector2d b, Vector2d c) {
if (a.getY() == b.getY() && a.getY() == c.getY()) {
if ((b.getX() <= a.getX() && a.getX() <= c.getX()) || (c.getX() <= a.getX() && a.getX() <= b.getX())) {
return 0;
} else {
return 1;
}
}
if (a.getY() == b.getY() && a.getX() == b.getX()) {
return 0;
}
if (b.getY() > c.getY()) {
Vector2d temp = b;
b = c;
c = temp;
}
if (a.getY() <= b.getY() || a.getY() > c.getY()) {
return 1;
}
return calculateDelta(a, b, c);
}
private int calculateDelta(Vector2d a, Vector2d b, Vector2d c) {
double delta = (b.getX() - a.getX()) * (c.getY() - a.getY()) - (b.getY() - a.getY()) * (c.getX() - a.getX());
if (delta > 0) {
return -1;
} else if (delta < 0) {
return 1;
} else {
return 0;
}
}
/**
* Calculates the normal vector of the ring. Method by Newell. If the Newell
* method would return a (0, 0, 0) vector a cross product is formed from the
* first 3 vertices. If there are no 3 vertices available (1, 0, 0) is returned.
*
* @return the normal as a normalized vector
*/
public Vector3d calculateNormal() {
double[] coords = new double[3];
for (int i = 0; i < vertices.size() - 1; i++) {
Vector3d current = vertices.get(i + 0);
Vector3d next = vertices.get(i + 1);
coords[0] += (current.getZ() + next.getZ()) * (current.getY() - next.getY());
coords[1] += (current.getX() + next.getX()) * (current.getZ() - next.getZ());
coords[2] += (current.getY() + next.getY()) * (current.getX() - next.getX());
}
if (coords[0] == 0 && coords[1] == 0 && coords[2] == 0) {
// no valid normal vector found
if (vertices.size() < 3) {
// no three points, return x-axis
return new Vector3d(1, 0, 0);
}
Vector3d v1 = vertices.get(0);
Vector3d v2 = vertices.get(1);
Vector3d v3 = vertices.get(2);
return calculateNormalWithCross(v1, v2, v3);
}
Vector3d v = new Vector3d(coords);
v.normalize();
return v;
}
private Vector3d calculateNormalWithCross(Vector3d v1, Vector3d v2, Vector3d v3) {
Vector3d dir1 = v2.minus(v1);
Vector3d dir2 = v3.minus(v1);
Vector3d cross = dir1.cross(dir2);
return cross.normalize();
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.List;
import Jama.Matrix;
import Jama.SingularValueDecomposition;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* Utility class to calculate three dimensional regression lines of a list of
* vertices
*
* @author Matthias Betz
*
*/
public class OrthogonalRegressionLine {
private OrthogonalRegressionLine() {
}
public static Line3d getRegressionLine(List<Vertex> vertices) {
// get the center of all points, the line has to go through this point
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
// create covariance matrix (3 x 3) out of all used points
double[][] covMatrixValues = CovarianceMatrix.calculateCovarianceMatrix(vertices, centroid);
Matrix covMatrix = new Matrix(covMatrixValues);
SingularValueDecomposition svd = new SingularValueDecomposition(covMatrix);
Matrix direction = svd.getU().getMatrix(0, 2, 0, 0);
Vector3d dir = new Vector3d(direction.get(0, 0), direction.get(1, 0), direction.get(2, 0));
return new Line3d(centroid, dir);
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.List;
import Jama.EigenvalueDecomposition;
import Jama.Matrix;
/**
* Utility class to calculate regression planes
*
* @author Matthias Betz
*
*/
public class OrthogonalRegressionPlane {
private OrthogonalRegressionPlane() {
}
/**
* calculates a regression plane using the PCA algorithm
*
* @param vertices the vertices forming a plane
* @return the regression plane
*/
public static Plane calculateOrthogonalRegressionPlane(List<? extends Vector3d> vertices) {
// get the center of all points, the plane has to go through this point
Vector3d centroid = CovarianceMatrix.getCentroid(vertices);
EigenvalueDecomposition ed = decompose(vertices, centroid);
Vector3d eigenvalues = getEigenvalues(ed);
return calculatePlane(centroid, ed, eigenvalues);
}
public static Plane calculatePlane(Vector3d centroid, EigenvalueDecomposition ed, Vector3d eigenvalues) {
double eig0 = eigenvalues.getX();
double eig1 = eigenvalues.getY();
double eig2 = eigenvalues.getZ();
Matrix v = ed.getV();
Matrix eigenVector;
if (eig0 < eig1 && eig0 < eig2) {
// the first eigenvalue is the lowest
eigenVector = v.getMatrix(0, 2, 0, 0);
} else if (eig1 < eig0 && eig1 < eig2) {
// the second eigenvalue is the lowest
eigenVector = v.getMatrix(0, 2, 1, 1);
} else {
// the third eigenvalue is the lowest
eigenVector = v.getMatrix(0, 2, 2, 2);
}
// the eigenvector of the lowest eigenvalue is the normal of the plane
Vector3d normal = new Vector3d(eigenVector.get(0, 0), eigenVector.get(1, 0), eigenVector.get(2, 0));
return new Plane(normal, centroid);
}
public static Vector3d getEigenvalues(EigenvalueDecomposition ed) {
Matrix eigenValues = ed.getD();
// get the lowest eigenvalue and the corresponding eigenvector
double eig0 = eigenValues.get(0, 0);
double eig1 = eigenValues.get(1, 1);
double eig2 = eigenValues.get(2, 2);
return new Vector3d(eig0, eig1, eig2);
}
public static EigenvalueDecomposition decompose(List<? extends Vector3d> vertices, Vector3d centroid) {
// create covariance matrix (3 x 3) out of all used points
double[][] covMatrixValues = CovarianceMatrix.calculateCovarianceMatrix(vertices, centroid);
Matrix covMatrix = new Matrix(covMatrixValues);
// calculate eigenvalues and eigenvectors
return covMatrix.eig();
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.PlaneSegmentIntersection.Type;
/**
* A plane described by a normal vector and a point
*
* @author Matthias Betz
*
*/
public class Plane implements Serializable {
private static final long serialVersionUID = 1602547402497943364L;
private static final double EPSILON = 0.0001;
private Vector3d normal;
private Vector3d point;
private double d;
/**
* Constructs a new plane from a normal vector and a reference point.
*
* @param normal the normal vector
* @param p the reference point
*/
public Plane(Vector3d normal, Vector3d p) {
point = p;
this.normal = normal.copy();
this.normal.normalize();
d = this.normal.dot(point);
if (d < 0) {
this.normal = this.normal.mult(-1);
d = -d;
}
}
public PlaneSegmentIntersection intersects(Segment3d s) {
return intersects(s, EPSILON);
}
public PlaneSegmentIntersection intersects(Segment3d s, double intersectionEpsilon) {
Vector3d dir = s.getPointB().minus(s.getPointA());
Vector3d w = s.getPointA().minus(point);
double a = normal.dot(dir);
double n = -1 * normal.dot(w);
if (Math.abs(a) < intersectionEpsilon) {
// parallel
if (n == 0) {
return new PlaneSegmentIntersection(Type.IN_PLANE);
} else {
return new PlaneSegmentIntersection(Type.NO_INTERSECTION);
}
}
// not parallel
double si = n / a;
if (si < intersectionEpsilon || si > 1 - intersectionEpsilon) {
// intersection not in segment
return new PlaneSegmentIntersection(Type.NO_INTERSECTION);
}
// intersection in segment
// calculate intersection point
Vector3d intersection = s.getPointA().plus(dir.mult(si));
PlaneSegmentIntersection psi = new PlaneSegmentIntersection(Type.INTERSECTION_POINT);
psi.setPoint(intersection);
return psi;
}
/**
* calculate the distance of this plane to the given point.
*
* @param point the point
* @return the distance
*/
public double getDistance(Vector3d point) {
return Math.abs(getSignedDistance(point));
}
/**
* Calculates the signed distance of the point to this plane.
* @param point the point
* @return the signed distance
*/
public double getSignedDistance(Vector3d point) {
return normal.dot(point) - d;
}
public double getD() {
return d;
}
/**
* returns the intersection point of the line perpendicular to the plane through
* the given point.
*
* @param point the point to project
* @return the projected point
*/
public Vector3d projectPointToPlane(Vector3d point) {
double a = d - normal.dot(point);
double x = point.getX() + a * normal.getX();
double y = point.getY() + a * normal.getY();
double z = point.getZ() + a * normal.getZ();
return new Vector3d(x, y, z);
}
/**
* Getter for normal
*
* @return the normal vector of the plane
*/
public Vector3d getNormal() {
return normal;
}
/**
* Getter for point
*
* @return the reference point of the plane
*/
public Vector3d getPoint() {
return point;
}
public static Plane of(Polygon b) {
List<Vertex> vertices = new ArrayList<>(b.getExteriorRing().getVertices());
for (LinearRing inner : b.getInnerRings()) {
vertices.addAll(inner.getVertices());
}
return OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(vertices);
}
public static Plane of(MovedPolygon p) {
List<Vector3d> vertices = new ArrayList<>(p.getExteriorRing().getVertices());
for (MovedRing inner : p.getInnerRings()) {
vertices.addAll(inner.getVertices());
}
return OrthogonalRegressionPlane.calculateOrthogonalRegressionPlane(vertices);
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
/**
* Intersection result between a segment and a plane
*
* @author Matthias Betz
*
*/
public class PlaneSegmentIntersection {
public enum Type {
NO_INTERSECTION, IN_PLANE, INTERSECTION_POINT
}
private Type type;
private Vector3d point;
public PlaneSegmentIntersection(Type type) {
super();
this.type = type;
}
public Vector3d getPoint() {
return point;
}
public void setPoint(Vector3d point) {
this.point = point;
}
public Type getType() {
return type;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
/**
* A two dimensional polygon. Has an 2d exterior ring and interior rings
*
* @author Matthias Betz
*
*/
public class Polygon2d {
private Ring2d exterior;
private List<Ring2d> innerRings;
public static Polygon2d withProjection(Polygon poly) {
Vector3d normal = poly.calculateNormal();
int[] axis = getProjectionAxis(normal);
return projectTo2D(poly, axis);
}
public static Polygon2d withProjection(MovedPolygon poly, int[] projectionAxis) {
return projectTo2D(poly, projectionAxis);
}
public static Polygon2d withProjection(Polygon poly, int[] projectionAxis) {
return projectTo2D(poly, projectionAxis);
}
private static Polygon2d projectTo2D(Polygon p, int[] axis) {
List<Ring2d> interior = new ArrayList<>();
Ring2d exterior = projectRing(p.getExteriorRing(), axis);
for (LinearRing innerRing : p.getInnerRings()) {
interior.add(projectRing(innerRing, axis));
}
return new Polygon2d(exterior, interior);
}
private static Polygon2d projectTo2D(MovedPolygon p, int[] axis) {
List<Ring2d> interior = new ArrayList<>();
Ring2d exterior = projectRing(p.getExteriorRing(), axis);
for (MovedRing innerRing : p.getInnerRings()) {
interior.add(projectRing(innerRing, axis));
}
return new Polygon2d(exterior, interior);
}
public static int[] getProjectionAxis(Vector3d normal) {
double nx = Math.abs(normal.getX());
double ny = Math.abs(normal.getY());
double nz = Math.abs(normal.getZ());
int[] axis;
if (nx > ny && nx > nz) {
axis = new int[] { 1, 2 };
} else if (ny > nx && ny > nz) {
axis = new int[] { 0, 2 };
} else {
axis = new int[] { 0, 1 };
}
return axis;
}
private static Ring2d projectRing(LinearRing r, int[] axis) {
List<Vector2d> projectedVertices = new ArrayList<>();
for (Vertex v : r.getVertices()) {
projectedVertices.add(new Vector2d(v.getCoordinate(axis[0]), v.getCoordinate(axis[1])));
}
return new Ring2d(projectedVertices, r);
}
private static Ring2d projectRing(MovedRing r, int[] axis) {
List<Vector2d> projectedVertices = new ArrayList<>();
for (Vector3d v : r.getVertices()) {
projectedVertices.add(new Vector2d(v.getCoordinate(axis[0]), v.getCoordinate(axis[1])));
}
return new Ring2d(projectedVertices, r.getOriginal());
}
public static Polygon2d withRotationMatrix(Polygon poly) {
Matrix3x3d rotMatrix = PolygonUtils.calculateRotationMatrix(poly);
LinearRing lr = poly.getExteriorRing();
Ring2d exterior = createRing2d(rotMatrix, lr);
List<Ring2d> innerRings = new ArrayList<>();
for (LinearRing innerRing : poly.getInnerRings()) {
innerRings.add(createRing2d(rotMatrix, innerRing));
}
return new Polygon2d(exterior, innerRings);
}
/**
* Converts a Polygon to a 2d polygon. This will change the area of the polygon.
*
* @param ring a linear ring
*/
private Polygon2d(Ring2d exterior, List<Ring2d> interior) {
if (interior == null) {
interior = Collections.emptyList();
}
this.exterior = exterior;
this.innerRings = interior;
}
private static Ring2d createRing2d(Matrix3x3d rotMatrix, LinearRing lr) {
List<Vector2d> ringVertices = new ArrayList<>();
for (Vertex v : lr.getVertices()) {
Vector3d rotated = rotMatrix.mult(v);
Vector2d v2d = new Vector2d(rotated.getX(), rotated.getY());
ringVertices.add(v2d);
}
return new Ring2d(ringVertices, lr);
}
public Ring2d getExterior() {
return exterior;
}
public List<Ring2d> getInteriorRings() {
if (innerRings == null) {
return Collections.emptyList();
}
return innerRings;
}
}
/*-
* Copyright 2020 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
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.math;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
/**
* Utility class for calculation concerning polygons
*
* @author Matthias Betz
*
*/
public class PolygonUtils {
private PolygonUtils() {
// only static utility
}
public static Matrix3x3d calculateRotationMatrix(Polygon p) {
Vector3d normal = p.calculateNormal();
Vector3d zAxis = new Vector3d(0d, 0d, 1d);
Quaternion rot = Quaternion.fromToRotation(normal, zAxis);
return rot.toRotationMatrix();
}
}
Markdown is supported
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