/*- * 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 . */ 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.model.gml.geometry.primitives.Triangle; import org.citygml4j.model.gml.geometry.primitives.TrianglePatchArrayProperty; import org.citygml4j.model.gml.geometry.primitives.TriangulatedSurface; import org.citygml4j.util.walker.GeometryWalker; import org.locationtech.proj4j.BasicCoordinateTransform; import org.locationtech.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.Localization; import de.hft.stuttgart.citydoctor2.utils.Pair; /** * * @author Matthias Betz * */ public class GeometryMapper extends GeometryWalker { private static final Logger logger = LogManager.getLogger(GeometryMapper.class); private Geometry geom; private ParserConfiguration config; private Map vertexMap; private Map polygons; private List> 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 polygons, List> linkedPolygons, Map 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()) { if (logger.isWarnEnabled()) { logger.warn(Localization.getText("GeometryMapper.emptyPolygon")); } return; } ConcretePolygon cdPoly = new ConcretePolygon(); addPolygonToAvailablePolygons(gmlPoly, 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 addPolygonToAvailablePolygons(org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly, ConcretePolygon cdPoly) { if (gmlPoly.isSetId()) { cdPoly.setGmlId(new GmlId(gmlPoly.getId())); if (polygons.put(gmlPoly.getId(), cdPoly) != null) { logger.warn("Found already existing polygon ID {}, duplicate IDs are not valid", gmlPoly.getId()); } } } 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 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 points = linearRing.getPosOrPointPropertyOrPointRep(); for (PosOrPointPropertyOrPointRep point : points) { if (!point.isSetPos()) { throw new UnsupportedOperationException("Cannot parse points for: " + linearRing.getId()); } DirectPosition pos = point.getPos(); List 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 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 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 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 polygons, List> linkedPolygons, Map 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 mapTriangulatedSurface(Geometry geom, TriangulatedSurface surface, ParserConfiguration config, Map polygons, List> linkedPolygons, Map vertexMap) { if (surface == null) { return; } TrianglePatchArrayProperty trianglePatches = surface.getTrianglePatches(); if (trianglePatches == null) { return; } GeometryMapper mapper = new GeometryMapper(geom, config, polygons, linkedPolygons, vertexMap); List triangles = trianglePatches.getTriangle(); for (Triangle t : triangles) { if (t == null) { continue; } ConcretePolygon poly = new ConcretePolygon(); LinearRing ext = new LinearRing(LinearRingType.EXTERIOR); poly.setExteriorRing(ext); mapper.mapRing(t.getExterior(), ext); geom.addPolygon(poly); } } public static void mapLod0MultiSurface(MultiSurfaceProperty lod0ms, CityObject co, ParserConfiguration config, Map polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 polygons, List> linkedPolygons, Map 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 lod1Geometry, CityObject co, ParserConfiguration config, Map polygons, List> linkedPolygons, Map 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 lod2Geometry, CityObject co, ParserConfiguration config, Map polygons, List> linkedPolygons, Map 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 lod3Geometry, CityObject co, ParserConfiguration config, Map polygons, List> linkedPolygons, Map 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 lod4Geometry, CityObject co, ParserConfiguration config, Map polygons, List> linkedPolygons, Map 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 gp, GeometryMapper geom, BuildingInstallation coBi) { if (gp == null || !gp.isSetGeometry()) { return; } parseIntoGeometry(gp.getGeometry(), geom, null, coBi); } public Geometry getGeometry() { return geom; } }