diff --git a/enrich-citygml-with-greenarea/.gitignore b/enrich-citygml-with-greenarea/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f190e4bc19841cf1aa289fc09f5b260ea70f0b82
--- /dev/null
+++ b/enrich-citygml-with-greenarea/.gitignore
@@ -0,0 +1,274 @@
+
+# Created by https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij
+# Edit at https://www.gitignore.io/?templates=java,maven,macos,linux,eclipse,windows,netbeans,intellij
+
+
+# User specific
+.sonarlint/
+Servers/
+
+### Eclipse ###
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+### Eclipse Patch ###
+# Eclipse Core
+.project
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Annotation Processing
+.apt_generated
+
+.sts4-cache/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+### NetBeans ###
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij
diff --git a/enrich-citygml-with-greenarea/pom.xml b/enrich-citygml-with-greenarea/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..344cc07e79d5a466a269f55134d5c554764639db
--- /dev/null
+++ b/enrich-citygml-with-greenarea/pom.xml
@@ -0,0 +1,47 @@
+
+ 4.0.0
+ de.hft.stuttgart
+ enrich-citygml-with-osm
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 17
+ 17
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.2
+ test
+
+
+
+ org.citygml4j
+ citygml4j-core
+ 3.0.0-rc.5
+
+
+
+ org.locationtech.proj4j
+ proj4j
+ 1.1.4
+
+
+
+ org.locationtech.jts
+ jts-core
+ 1.19.0
+
+
+
+
+ org.citygml4j
+ citygml4j-xml
+ 3.0.0-rc.5
+
+
+
\ No newline at end of file
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenArea.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenArea.java
new file mode 100644
index 0000000000000000000000000000000000000000..91328115dc727bc7e0314f75dc06f46e916b0d71
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenArea.java
@@ -0,0 +1,29 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import org.locationtech.jts.geom.Polygon;
+
+public class GreenArea {
+
+ private Polygon area;
+
+ public GreenArea() {
+ }
+
+ public GreenArea(Polygon area) {
+ this.area = area;
+ }
+
+ public void setArea(Polygon area) {
+ this.area = area;
+ }
+
+ public Polygon getArea() {
+ return area;
+ }
+
+ @Override
+ public String toString() {
+ return "GreenArea [area=" + area + "]";
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenEnricher.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenEnricher.java
new file mode 100644
index 0000000000000000000000000000000000000000..101ac9eca5f881474de38115d2a86af16dffbebe
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/GreenEnricher.java
@@ -0,0 +1,721 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.citygml4j.core.model.CityGMLVersion;
+import org.citygml4j.core.model.building.Building;
+import org.citygml4j.core.model.core.AbstractCityObject;
+import org.citygml4j.core.model.core.AbstractCityObjectProperty;
+import org.citygml4j.core.model.core.AbstractFeature;
+import org.citygml4j.core.model.core.CityModel;
+import org.citygml4j.core.model.vegetation.PlantCover;
+import org.citygml4j.core.model.vegetation.SolitaryVegetationObject;
+import org.citygml4j.core.model.waterbody.WaterBody;
+import org.citygml4j.core.visitor.ObjectWalker;
+import org.citygml4j.xml.CityGMLContext;
+import org.citygml4j.xml.CityGMLContextException;
+import org.citygml4j.xml.module.citygml.CoreModule;
+import org.citygml4j.xml.reader.CityGMLInputFactory;
+import org.citygml4j.xml.reader.CityGMLReadException;
+import org.citygml4j.xml.reader.CityGMLReader;
+import org.citygml4j.xml.writer.CityGMLOutputFactory;
+import org.citygml4j.xml.writer.CityGMLWriteException;
+import org.citygml4j.xml.writer.CityGMLWriter;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.proj4j.BasicCoordinateTransform;
+import org.locationtech.proj4j.CRSFactory;
+import org.locationtech.proj4j.CoordinateReferenceSystem;
+import org.locationtech.proj4j.ProjCoordinate;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+import org.xmlobjects.gml.model.geometry.DirectPosition;
+import org.xmlobjects.gml.model.geometry.DirectPositionList;
+import org.xmlobjects.gml.model.geometry.Envelope;
+import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
+import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
+import org.xmlobjects.gml.model.geometry.primitives.AbstractRingProperty;
+import org.xmlobjects.gml.model.geometry.primitives.LinearRing;
+import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;
+
+public class GreenEnricher {
+
+ private static final int BOUNDING_BOX_INCREASE_IN_M = 100;
+
+ // in degrees
+ // add around 30m ( 1" = 30m -> 0.00277 = 900m)
+ private static final double BOUNDING_BOX_INCREASE = 0.00277;
+
+ private static final String OSM_STRING = """
+ [bbox:{{bbox}}];
+ (
+ nwr["leisure"="garden"];
+ nwr["landuse"="forest"];
+ nwr["natural"="tree"];
+ nwr["leisure"="park"];
+ nwr["natural"="tree_row"];
+ nwr["natural"="wood"];
+ nwr["landuse"="grass"];
+ nwr["landuse"="orchard"];
+ nwr["natural"="scrub"];
+ nwr["landuse"="meadow"];
+ nwr["landuse"="farmland"];
+ nwr["landuse"="allotments"];
+ nwr["natural"="water"];
+ nwr["water"="river"];
+ nwr["type"="waterway"];
+ nwr["waterway"="stream"];
+ nwr["water"="pond"];
+ );
+ out geom;""";
+
+ private static final URI OVERPASS_API_URI = URI.create("http://www.overpass-api.de/api/interpreter");
+ private static final CRSFactory CRS_FACTORY = new CRSFactory();
+ private static CoordinateReferenceSystem sourceCRS;
+ private static CoordinateReferenceSystem targetCRS = CRS_FACTORY.createFromName("EPSG:4326");
+ private static BasicCoordinateTransform transform;
+ private static BasicCoordinateTransform backTransform;
+ public static GeometryFactory geomFactory = new GeometryFactory();
+
+ public static void main(String[] args) throws IOException, CityGMLContextException, CityGMLReadException,
+ InterruptedException, ParserConfigurationException, SAXException, CityGMLWriteException {
+ System.out.println("Reading CityGML file");
+ Path inFile = Paths.get(args[0]);
+ CityModel cityModel = readCityGml(inFile);
+
+ String epsgCode = extractEpsgCode(cityModel);
+ sourceCRS = CRS_FACTORY.createFromName(epsgCode);
+ transform = new BasicCoordinateTransform(sourceCRS, targetCRS);
+ backTransform = new BasicCoordinateTransform(targetCRS, sourceCRS);
+
+ OsmData osmData = new OsmData();
+ String boundingBoxString = extractAndConvertBoundingBox(cityModel, epsgCode, osmData);
+// HttpResponse response = getOsmData(boundingBoxString);
+// Files.write(Path.of("osm_response.xml"), response.body().getBytes(StandardCharsets.UTF_8));
+// String osmResponse = response.body();
+ String osmResponse = Files.readString(Paths.get("osm_response.xml"));
+
+ System.out.println("Parsing OSM response");
+ parseOsmResponse(osmResponse, osmData);
+ createBoundingBox(cityModel, osmData);
+
+// List newGreenAreas = new ArrayList<>();
+
+ System.out.println("Fit data in bounding box");
+ fitToBoundingBox(osmData);
+
+ System.out.println("Filter intersecting areas");
+ List greenAreas = osmData.getGreenAreas();
+ removeDuplicateAreas(greenAreas);
+
+ convertGreenAreasToCityGML(cityModel, greenAreas);
+ convertWaterAreasToCityGML(cityModel, osmData);
+
+ for (Waterway waterWay : osmData.getWaterways()) {
+ }
+
+ for (TreePoint tp : osmData.getTreePoints()) {
+ double trunkRadius = 0.2;
+ double trunkHeight = 1;
+ Coordinate coordinate = tp.getPoint().getCoordinate();
+ if (Double.isNaN(coordinate.z)) {
+ coordinate.z = 0;
+ }
+ MultiSurface generatedTree = TreeGenerator.generateTree(coordinate, trunkRadius, trunkHeight, 5, 5, 10);
+ SolitaryVegetationObject cover = new SolitaryVegetationObject();
+ cover.setId(UUID.randomUUID().toString());
+ cover.setLod2MultiSurface(new MultiSurfaceProperty(generatedTree));
+ cityModel.getCityObjectMembers().add(new AbstractCityObjectProperty(cover));
+ }
+//
+// for (TreeRow tr : osmData.getTreeRows()) {
+// System.out.println(tr);
+// }
+
+ clampToGround(cityModel);
+
+ String inputString = inFile.getFileName().toString();
+ String inputPathWithoutFileEnding = inputString.substring(0, inputString.lastIndexOf('.'));
+ Path outputPath = Paths.get(inputPathWithoutFileEnding + "_with_greens.gml");
+ System.out.println("Writing output file.");
+ writeCityGML(cityModel, outputPath);
+ System.out.println("Done");
+
+ }
+
+ private static void convertWaterAreasToCityGML(CityModel cityModel, OsmData osmData) {
+ for (WaterArea waterArea : osmData.getWaterAreas()) {
+ WaterBody wb = new WaterBody();
+ org.xmlobjects.gml.model.geometry.primitives.Polygon poly = convertToCityGmlPoly(waterArea.getArea());
+ if (poly == null) {
+ System.out.println("Skipping WaterBody " + waterArea.getArea());
+ continue;
+ }
+ MultiSurface ms = new MultiSurface();
+ wb.setId(UUID.randomUUID().toString());
+ ms.getSurfaceMember().add(new SurfaceProperty(poly));
+ wb.setLod0MultiSurface(new MultiSurfaceProperty(ms));
+ cityModel.getCityObjectMembers().add(new AbstractCityObjectProperty(wb));
+ }
+ }
+
+ private static void convertGreenAreasToCityGML(CityModel cityModel, List greenAreas) {
+ for (GreenArea ga : greenAreas) {
+ org.xmlobjects.gml.model.geometry.primitives.Polygon poly = convertToCityGmlPoly(ga.getArea());
+ if (poly == null) {
+ System.out.println("Skipping " + ga.getArea());
+ continue;
+ }
+ PlantCover cover = new PlantCover();
+ MultiSurface ms = new MultiSurface();
+ cover.setId(UUID.randomUUID().toString());
+ ms.getSurfaceMember().add(new SurfaceProperty(poly));
+ cover.setLod2MultiSurface(new MultiSurfaceProperty(ms));
+ cityModel.getCityObjectMembers().add(new AbstractCityObjectProperty(cover));
+ }
+ }
+
+ private static void fitToBoundingBox(OsmData osmData) {
+ List greenAreas = osmData.getGreenAreas();
+ List newGreenAreas = new ArrayList<>();
+ for (GreenArea greenArea : greenAreas) {
+ Polygon area = greenArea.getArea();
+ Geometry intersection = area.intersection(osmData.getBoundingBox());
+ if (intersection instanceof MultiPolygon multi) {
+ Polygon poly1 = (Polygon) multi.getGeometryN(0);
+ greenArea.setArea(poly1);
+ for (int k = 1; k < multi.getNumGeometries(); k++) {
+ GreenArea newGreenArea = new GreenArea();
+ newGreenArea.setArea((Polygon) multi.getGeometryN(k));
+ newGreenAreas.add(newGreenArea);
+ }
+ } else {
+ greenArea.setArea((Polygon) intersection);
+ }
+ }
+ greenAreas.addAll(newGreenAreas);
+
+ clipWaterAreasToBoundingBox(osmData);
+
+ for (Iterator iterator = osmData.getTreePoints().iterator(); iterator.hasNext();) {
+ TreePoint tp = iterator.next();
+ if (!osmData.getBoundingBox().contains(tp.getPoint())) {
+ iterator.remove();
+ }
+ }
+
+ }
+
+ private static void clipWaterAreasToBoundingBox(OsmData osmData) {
+ List newWaterAreas = new ArrayList<>();
+ for (WaterArea waterArea : osmData.getWaterAreas()) {
+ Polygon area = waterArea.getArea();
+ Geometry intersection = area.intersection(osmData.getBoundingBox());
+ if (intersection instanceof MultiPolygon multi) {
+ Polygon poly1 = (Polygon) multi.getGeometryN(0);
+ waterArea.setArea(poly1);
+ for (int k = 1; k < multi.getNumGeometries(); k++) {
+ WaterArea newWaterArea = new WaterArea();
+ newWaterArea.setArea((Polygon) multi.getGeometryN(k));
+ newWaterAreas.add(newWaterArea);
+ }
+ } else {
+ waterArea.setArea((Polygon) intersection);
+ }
+ }
+ }
+
+ private static void removeDuplicateAreas(List greenAreas) {
+ for (int i = 0; i < greenAreas.size(); i++) {
+ for (int j = i + 1; j < greenAreas.size(); j++) {
+ GreenArea area1 = greenAreas.get(i);
+ GreenArea area2 = greenAreas.get(j);
+ if (area1.getArea().intersects(area2.getArea())) {
+ Geometry difference = area1.getArea().difference(area2.getArea());
+ System.out.println(difference);
+ if (difference instanceof MultiPolygon) {
+ MultiPolygon multi = (MultiPolygon) difference;
+ Polygon poly1 = (Polygon) multi.getGeometryN(0);
+ area1.setArea(poly1);
+ for (int k = 1; k < multi.getNumGeometries(); k++) {
+ GreenArea newGreenArea = new GreenArea();
+ newGreenArea.setArea((Polygon) multi.getGeometryN(k));
+ greenAreas.add(newGreenArea);
+ }
+ } else {
+ area1.setArea((Polygon) difference);
+ }
+
+ }
+ }
+ }
+ }
+
+ private static void createBoundingBox(CityModel cityModel, OsmData osmData) {
+ // TODO Auto-generated method stub
+
+ }
+
+// private static MultiSurface generateTree(Point p) {
+// double radiusTrunk = 0.2;
+//
+// MultiSurface result = new MultiSurface();
+//
+// // trunk
+// Coordinate center = p.getCoordinate();
+// List coords = new ArrayList<>();
+// coords.add(new Coordinate(center.x, center.y + radiusTrunk));
+// coords.add(new Coordinate(center.x + radiusTrunk / 2, center.y + Math.cos(0.5) * radiusTrunk));
+// }
+
+ private static void clampToGround(CityModel cityModel) {
+ for (AbstractCityObjectProperty afp : cityModel.getCityObjectMembers()) {
+ AbstractCityObject af = afp.getObject();
+ if (af instanceof Building b) {
+ Envelope envelope = b.computeEnvelope();
+ Double lowestZ = envelope.getLowerCorner().getValue().get(2);
+ b.accept(new ObjectWalker() {
+ @Override
+ public void visit(LinearRing linearRing) {
+ List values = linearRing.getControlPoints().getPosList().getValue();
+ for (int i = 2; i < values.size(); i = i + 3) {
+ values.set(i, values.get(i) - lowestZ);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ public static org.xmlobjects.gml.model.geometry.primitives.Polygon convertToCityGmlPoly(Polygon area) {
+ if (area.getExteriorRing().getCoordinates().length == 0) {
+ return null;
+ }
+ org.xmlobjects.gml.model.geometry.primitives.Polygon result = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
+ LinearRing lr = new LinearRing();
+ result.setExterior(new AbstractRingProperty(lr));
+ DirectPositionList posList = new DirectPositionList();
+ lr.getControlPoints().setPosList(posList);
+ List values = posList.getValue();
+ for (Coordinate coord : area.getExteriorRing().getCoordinates()) {
+ values.add(coord.x);
+ values.add(coord.y);
+ if (Double.isNaN(coord.z)) {
+ coord.z = 0;
+ }
+ values.add(coord.z);
+ }
+
+ for (int i = 0; i < area.getNumInteriorRing(); i++) {
+ LinearRing innerRing = new LinearRing();
+ result.getInterior().add(new AbstractRingProperty(innerRing));
+ DirectPositionList innerPosList = new DirectPositionList();
+ innerRing.getControlPoints().setPosList(innerPosList);
+ List innerValues = innerPosList.getValue();
+ Coordinate[] coordinates = area.getInteriorRingN(i).getCoordinates();
+ for (Coordinate coord : coordinates) {
+ innerValues.add(coord.x);
+ innerValues.add(coord.y);
+ if (Double.isNaN(coord.z)) {
+ coord.z = 0;
+ }
+ innerValues.add(coord.z);
+ }
+ }
+
+ return result;
+ }
+
+ private static HttpResponse getOsmData(String boundingBoxString) throws IOException, InterruptedException {
+ String data = OSM_STRING.replace("{{bbox}}", boundingBoxString);
+ String body = URLEncoder.encode("data", StandardCharsets.UTF_8) + "="
+ + URLEncoder.encode(data, StandardCharsets.UTF_8);
+
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder().uri(OVERPASS_API_URI)
+ .POST(HttpRequest.BodyPublishers.ofString(body)).build();
+ System.out.println("Retrieving OSM data of bounding box: " + boundingBoxString);
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() != 200) {
+ System.err.println("Failed to retrieve OSM data. Return code: " + response.statusCode());
+ System.exit(1);
+ }
+ return response;
+ }
+
+ private static void writeCityGML(CityModel cityModel, Path outputPath)
+ throws CityGMLWriteException, CityGMLContextException {
+ CityGMLContext context = CityGMLContext.newInstance();
+ CityGMLVersion version = CityGMLVersion.v2_0;
+ CityGMLOutputFactory out = context.createCityGMLOutputFactory(version);
+
+ try (CityGMLWriter writer = out.createCityGMLWriter(outputPath, StandardCharsets.UTF_8.name())) {
+ writer.withIndent(" ").withDefaultSchemaLocations().withDefaultPrefixes()
+ .withDefaultNamespace(CoreModule.of(version).getNamespaceURI()).write(cityModel);
+ }
+ }
+
+ private static OsmData parseOsmResponse(String osmResponse, OsmData osmData)
+ throws ParserConfigurationException, SAXException, IOException {
+ Document document = createDomFromOsmResponse(osmResponse);
+
+ // root element
+ Node osmItem = document.getChildNodes().item(0);
+ NodeList childNodes = osmItem.getChildNodes();
+
+ Set usedIds = new HashSet<>();
+
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (parseTreeIfPossible(osmData.getTreePoints(), node, usedIds)) {
+ continue;
+ }
+ if (parseGreenAreasIfPossible(osmData, node, usedIds)) {
+ continue;
+ }
+ parseGreenRelationsIfPossible(osmData, node, usedIds);
+ }
+
+ return osmData;
+ }
+
+ private static boolean parseGreenRelationsIfPossible(OsmData data, Node node, Set usedIds) {
+ if (!"relation".equals(node.getNodeName())) {
+ return false;
+ }
+ String nodeValue = node.getAttributes().getNamedItem("id").getNodeValue();
+ System.out.println("Relation: " + nodeValue);
+ NodeList childNodes = node.getChildNodes();
+ List> innerRings = new ArrayList<>();
+ List outerRing = new ArrayList<>();
+ boolean water = false;
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node boundsOrMember = childNodes.item(i);
+ if (!"member".equals(boundsOrMember.getNodeName())) {
+ if ("tag".equals(boundsOrMember.getNodeName())) {
+ if ("water".equals(boundsOrMember.getAttributes().getNamedItem("v").getNodeValue())) {
+ water = true;
+ }
+ }
+ continue;
+ }
+ Node member = boundsOrMember;
+ String role = member.getAttributes().getNamedItem("role").getNodeValue();
+ if (!"way".equals(member.getAttributes().getNamedItem("type").getNodeValue())) {
+ continue;
+ }
+ String referenceId = member.getAttributes().getNamedItem("ref").getNodeValue();
+ if (usedIds.contains(referenceId)) {
+ System.out.println("Relation contains way that is already used " + referenceId);
+ continue;
+ }
+ List ringCoords;
+ if ("inner".equals(role)) {
+ ringCoords = new ArrayList<>();
+ innerRings.add(ringCoords);
+ } else if ("outer".equals(role)) {
+ // outer ring
+ outerRing.clear();
+ ringCoords = outerRing;
+ } else if ("main_stream".equals(role)) {
+ ringCoords = outerRing;
+ } else if ("side_stream".equals(role)) {
+ ringCoords = outerRing;
+ } else {
+ System.out.println("Unknown role: " + role);
+ // probably waterway
+ continue;
+ }
+ NodeList memberNodes = member.getChildNodes();
+ for (int memberNodesIndex = 0; memberNodesIndex < memberNodes.getLength(); memberNodesIndex++) {
+ Node nd = memberNodes.item(memberNodesIndex);
+ if ("nd".equals(nd.getNodeName())) {
+ double lat = Double.parseDouble(nd.getAttributes().getNamedItem("lat").getNodeValue());
+ double lon = Double.parseDouble(nd.getAttributes().getNamedItem("lon").getNodeValue());
+ ProjCoordinate converted = convertCoordinatesFrom84(lon, lat);
+ ringCoords.add(new Coordinate(converted.x, converted.y));
+ }
+ }
+ }
+
+ if (water) {
+ org.locationtech.jts.geom.LinearRing outerLinearRing = geomFactory
+ .createLinearRing(outerRing.toArray(new Coordinate[outerRing.size()]));
+ // create the inner rings
+ List innerLinearRings = new ArrayList<>();
+ for (List innerRing : innerRings) {
+ org.locationtech.jts.geom.LinearRing innerLinearRing = geomFactory
+ .createLinearRing(innerRing.toArray(new Coordinate[innerRing.size()]));
+ innerLinearRings.add(innerLinearRing);
+ }
+
+ Polygon polygon = geomFactory.createPolygon(outerLinearRing,
+ innerLinearRings.toArray(new org.locationtech.jts.geom.LinearRing[innerLinearRings.size()]));
+
+ data.getWaterAreas().add(new WaterArea(polygon));
+ }
+
+// validateRing(outerRing);
+ // create the outer ring
+// org.locationtech.jts.geom.LinearRing outerLinearRing = geomFactory
+// .createLinearRing(outerRing.toArray(new Coordinate[outerRing.size()]));
+//
+// // create the inner rings
+// List innerLinearRings = new ArrayList<>();
+// for (List innerRing : innerRings) {
+// org.locationtech.jts.geom.LinearRing innerLinearRing = geomFactory
+// .createLinearRing(innerRing.toArray(new Coordinate[innerRing.size()]));
+// innerLinearRings.add(innerLinearRing);
+// }
+//
+// if (outerRing.isEmpty()) {
+// return false;
+// }
+//
+// // create the polygon
+// Polygon polygon = geomFactory.createPolygon(outerLinearRing,
+// innerLinearRings.toArray(new org.locationtech.jts.geom.LinearRing[innerLinearRings.size()]));
+//
+// data.getGreenAreas().add(new GreenArea(polygon));
+ return true;
+
+ }
+
+ private static void validateRing(List outerRing) {
+ if (outerRing.isEmpty()) {
+ return;
+ }
+ if (!outerRing.get(0).equals(outerRing.get(outerRing.size() - 1))) {
+ // close ring
+ outerRing.add(outerRing.get(0));
+ }
+ }
+
+ private static boolean parseGreenAreasIfPossible(OsmData data, Node node, Set usedIds) {
+ if (!"way".equals(node.getNodeName())) {
+ return false;
+ }
+
+ boolean water = false;
+ String id = node.getAttributes().getNamedItem("id").getNodeValue();
+ if (usedIds.contains(id)) {
+ System.out.println("Already used way id " + id);
+ return true;
+ }
+
+ usedIds.add(id);
+ NodeList childNodes = node.getChildNodes();
+ List coordinates = new ArrayList<>();
+ boolean line = false;
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if ("nd".equals(child.getNodeName())) {
+ double lat = Double.parseDouble(getTagValue(child, "lat"));
+ double lon = Double.parseDouble(getTagValue(child, "lon"));
+ ProjCoordinate converted = convertCoordinatesFrom84(lon, lat);
+ coordinates.add(new Coordinate(converted.x, converted.y));
+ } else if ("tag".equals(child.getNodeName())) {
+// if (existTagWithValue(child, "k", "natural") && existTagWithValue(child, "v", "tree_row")) {
+// line = true;
+// }
+ if ((existTagWithValue(child, "k", "natural") && existTagWithValue(child, "v", "water"))
+ || existTagWithValue(child, "k", "waterway")) {
+ water = true;
+ }
+ }
+ }
+
+ if (!coordinates.get(0).equals(coordinates.get(coordinates.size() - 1))) {
+ // assume line if start and end coordinate do not match?
+ line = true;
+ }
+
+ if (water) {
+ if (line) {
+ LineString lineString = geomFactory
+ .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+ data.getWaterways().add(new Waterway(lineString));
+ } else {
+ Polygon polygon = geomFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
+ data.getWaterAreas().add(new WaterArea(polygon));
+ }
+ } else {
+ if (line) {
+ LineString lineString = geomFactory
+ .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+ data.getTreeRows().add(new TreeRow(lineString));
+ } else {
+ Polygon polygon = geomFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
+ data.getGreenAreas().add(new GreenArea(polygon));
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean existTagWithValue(Node node, String key, String value) {
+ return value.equals(getTagValue(node, key));
+ }
+
+ private static String getTagValue(Node node, String tagName) {
+ Node namedItem = node.getAttributes().getNamedItem(tagName);
+ if (namedItem == null) {
+ return null;
+ }
+ return namedItem.getNodeValue();
+ }
+
+ private static boolean parseTreeIfPossible(List treePoints, Node node, Set usedIds) {
+ if (!"node".equals(node.getNodeName())) {
+ return false;
+ }
+ String id = node.getAttributes().getNamedItem("id").getNodeValue();
+ if (usedIds.contains(id)) {
+ System.out.println("Already used node id " + id);
+ return true;
+ }
+ usedIds.add(id);
+ double lat = Double.parseDouble(node.getAttributes().getNamedItem("lat").getNodeValue());
+ double lon = Double.parseDouble(node.getAttributes().getNamedItem("lon").getNodeValue());
+ ProjCoordinate converted = convertCoordinatesFrom84(lon, lat);
+ Point point = geomFactory.createPoint(new Coordinate(converted.x, converted.y));
+ treePoints.add(new TreePoint(point));
+ return true;
+ }
+
+ private static Document createDomFromOsmResponse(String body)
+ throws ParserConfigurationException, SAXException, IOException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ // to be compliant, completely disable DOCTYPE declaration:
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ // or completely disable external entities declarations:
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ // or prohibit the use of all protocols by external entities:
+ factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+ factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ ByteArrayInputStream input = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
+ return builder.parse(input);
+ }
+
+ private static CityModel readCityGml(Path inFile) throws CityGMLContextException, CityGMLReadException {
+ CityGMLContext context = CityGMLContext.newInstance();
+ CityGMLInputFactory in = context.createCityGMLInputFactory();
+ try (CityGMLReader reader = in.createCityGMLReader(inFile)) {
+ AbstractFeature feature = reader.next();
+ if (feature instanceof CityModel cm) {
+ return cm;
+ }
+ }
+ throw new IllegalStateException("CityGML does not contain a CityModel feature type");
+ }
+
+ private static String extractEpsgCode(CityModel cityModel) {
+ Envelope envelope = cityModel.getBoundedBy().getEnvelope();
+ String srsName = envelope.getSrsName();
+ return parseEpsg(srsName);
+ }
+
+ private static String parseEpsg(String srsName) {
+ int utmIndex = srsName.indexOf("UTM");
+ if (utmIndex > 0) {
+ return "EPSG:258" + srsName.substring(utmIndex + 3, utmIndex + 5);
+ }
+ Pattern p = Pattern.compile("EPSG:+([0-9]+)");
+ Matcher m = p.matcher(srsName);
+ if (m.find()) {
+ return "EPSG:" + m.group(1);
+ }
+ return srsName;
+ }
+
+ private static String extractAndConvertBoundingBox(CityModel cityModel, String epsgCode, OsmData osmData) {
+ Envelope calculatedEnvelope = cityModel.computeEnvelope();
+
+ DirectPosition lowerCorner = calculatedEnvelope.getLowerCorner();
+ ProjCoordinate p1 = new ProjCoordinate(lowerCorner.getValue().get(0), lowerCorner.getValue().get(1));
+ ProjCoordinate lowerCornerProjected = new ProjCoordinate();
+ transform.transform(p1, lowerCornerProjected);
+
+ DirectPosition upperCorner = calculatedEnvelope.getUpperCorner();
+ p1.x = upperCorner.getValue().get(0);
+ p1.y = upperCorner.getValue().get(1);
+ ProjCoordinate upperCornerProjected = new ProjCoordinate();
+ transform.transform(p1, upperCornerProjected);
+
+ lowerCornerProjected.x -= BOUNDING_BOX_INCREASE;
+ lowerCornerProjected.y -= BOUNDING_BOX_INCREASE;
+ upperCornerProjected.x += BOUNDING_BOX_INCREASE;
+ upperCornerProjected.y += BOUNDING_BOX_INCREASE;
+
+ List bboxCoordinates = new ArrayList<>();
+ bboxCoordinates.add(new Coordinate(lowerCorner.getValue().get(0) - BOUNDING_BOX_INCREASE_IN_M,
+ lowerCorner.getValue().get(1) - BOUNDING_BOX_INCREASE_IN_M));
+ bboxCoordinates.add(new Coordinate(lowerCorner.getValue().get(0) - BOUNDING_BOX_INCREASE_IN_M,
+ upperCorner.getValue().get(1) + BOUNDING_BOX_INCREASE_IN_M));
+ bboxCoordinates.add(new Coordinate(upperCorner.getValue().get(0) + BOUNDING_BOX_INCREASE_IN_M,
+ upperCorner.getValue().get(1) + BOUNDING_BOX_INCREASE_IN_M));
+ bboxCoordinates.add(new Coordinate(upperCorner.getValue().get(0) + BOUNDING_BOX_INCREASE_IN_M,
+ lowerCorner.getValue().get(1) - BOUNDING_BOX_INCREASE_IN_M));
+ bboxCoordinates.add(new Coordinate(lowerCorner.getValue().get(0) - BOUNDING_BOX_INCREASE_IN_M,
+ lowerCorner.getValue().get(1) - BOUNDING_BOX_INCREASE_IN_M));
+
+ Polygon poly = geomFactory.createPolygon(bboxCoordinates.toArray(new Coordinate[bboxCoordinates.size()]));
+ osmData.setBoundingBox(poly);
+
+ return lowerCornerProjected.y + "," + lowerCornerProjected.x + "," + upperCornerProjected.y + ","
+ + upperCornerProjected.x;
+ }
+
+ private static ProjCoordinate convertCoordinatesFrom84(double x, double y) {
+ ProjCoordinate p1 = new ProjCoordinate(x, y);
+ ProjCoordinate result = new ProjCoordinate();
+ backTransform.transform(p1, result);
+ return result;
+ }
+
+ private static ProjCoordinate convertCoordinatesTo84(DirectPosition point) {
+ double x = point.getValue().get(0);
+ double y = point.getValue().get(1);
+ return convertCoordinatesTo84(x, y);
+ }
+
+ private static ProjCoordinate convertCoordinatesTo84(double x, double y) {
+ ProjCoordinate p1 = new ProjCoordinate(x, y);
+ ProjCoordinate result = new ProjCoordinate();
+ transform.transform(p1, result);
+ return result;
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/OsmData.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/OsmData.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ae87234c69238e0d6964678d8793b9f2cffd6a3
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/OsmData.java
@@ -0,0 +1,46 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.locationtech.jts.geom.Polygon;
+
+public class OsmData {
+
+ private Polygon boundingBox;
+
+ private List treePoints = new ArrayList<>();
+ private List greenAreas = new ArrayList<>();
+ private List treeRows = new ArrayList<>();
+ private List waterways = new ArrayList<>();
+ private List waterAreas = new ArrayList<>();
+
+ public void setBoundingBox(Polygon boundingBox) {
+ this.boundingBox = boundingBox;
+ }
+
+ public List getWaterAreas() {
+ return waterAreas;
+ }
+
+ public List getWaterways() {
+ return waterways;
+ }
+
+ public Polygon getBoundingBox() {
+ return boundingBox;
+ }
+
+ public List getGreenAreas() {
+ return greenAreas;
+ }
+
+ public List getTreePoints() {
+ return treePoints;
+ }
+
+ public List getTreeRows() {
+ return treeRows;
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeGenerator.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..198b9afa5d195c2e260a058ccd482a9482fad2c4
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeGenerator.java
@@ -0,0 +1,147 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Polygon;
+import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
+import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;
+
+public class TreeGenerator {
+
+ private static final double TRUNK_INCREASE_FACTOR = 1.2;
+ public static MultiSurface generateTree(Coordinate point, double trunkRadius, double trunkHeight,
+ double crownXRadius, double crownYRadius, double crownHeight) {
+
+ List trunkWestCoordinateList = new ArrayList<>();
+ trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
+ double drawTrunkHeight = trunkHeight * TRUNK_INCREASE_FACTOR;
+ trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, drawTrunkHeight));
+ trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, drawTrunkHeight));
+ trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
+ trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
+ Polygon trunkWestPolygon = convertPolygon(trunkWestCoordinateList);
+ var trunkWestConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkWestPolygon);
+ MultiSurface ms = new MultiSurface();
+ ms.getSurfaceMember().add(new SurfaceProperty(trunkWestConvertedPolygon));
+
+ List trunkEastCoordinateList = new ArrayList<>();
+ trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
+ trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, 0));
+ trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, drawTrunkHeight));
+ trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, drawTrunkHeight));
+ trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
+ Polygon trunkEastPolygon = convertPolygon(trunkEastCoordinateList);
+ var trunkEastConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkEastPolygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(trunkEastConvertedPolygon));
+
+ List trunkSouthCoordinateList = new ArrayList<>();
+ trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
+ trunkSouthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
+ trunkSouthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, drawTrunkHeight));
+ trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, drawTrunkHeight));
+ trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
+ Polygon trunkSouthPolygon = convertPolygon(trunkSouthCoordinateList);
+ var trunkSouthConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkSouthPolygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(trunkSouthConvertedPolygon));
+
+ List trunkNorthCoordinateList = new ArrayList<>();
+ trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
+ trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, drawTrunkHeight));
+ trunkNorthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, drawTrunkHeight));
+ trunkNorthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, 0));
+ trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
+ Polygon trunkNorthPolygon = convertPolygon(trunkNorthCoordinateList);
+ var trunkNorthConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkNorthPolygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(trunkNorthConvertedPolygon));
+
+ /*-
+ * Spheroid formula:
+ * x = a * sin(thetha) * cos(phi)
+ * y = b * sin(thetha) * sin(phi)
+ * z = c * cos(thetha)
+ *
+ * 0 <= theta <= pi
+ * 0 <= phi < 2 pi
+ */
+
+ // create crown
+ // divide by 2 to get radius, height is diameter
+ double c = crownHeight / 2D;
+
+ double a = crownXRadius;
+ double b = crownYRadius;
+
+ int numberOfThetaRings = 5;
+ int numberOfPhiRings = 10;
+
+ double thetaDelta = Math.PI / (numberOfThetaRings - 1);
+ double phiDelta = Math.PI * 2 / numberOfPhiRings;
+
+ List> ringCoordinates = new ArrayList<>();
+
+ // top point triangles
+ Coordinate topPoint = new Coordinate(point.x, point.y, point.z + c + trunkHeight + c);
+ ringCoordinates.add(Collections.singletonList(topPoint));
+ for (int i = 1; i < numberOfThetaRings - 1; i++) {
+ double theta = i * thetaDelta;
+ List coords = new ArrayList<>();
+ ringCoordinates.add(coords);
+ for (int j = 0; j < numberOfPhiRings; j++) {
+ double phi = j * phiDelta;
+ double x = a * Math.sin(theta) * Math.cos(phi) + point.x;
+ double y = b * Math.sin(theta) * Math.sin(phi) + point.y;
+ double z = c * Math.cos(theta) + point.z + trunkHeight + c;
+ Coordinate p1 = new Coordinate(x, y, z);
+ coords.add(p1);
+ }
+ }
+ Coordinate bottomPoint = new Coordinate(point.x, point.y, point.z + trunkHeight);
+ ringCoordinates.add(Collections.singletonList(bottomPoint));
+
+ List secondRing = ringCoordinates.get(1);
+ for (int i = 0; i < secondRing.size(); i++) {
+ Coordinate[] coordArray = new Coordinate[] { topPoint, secondRing.get(i), secondRing.get((i + 1) % secondRing.size()), topPoint };
+ Polygon polygon = GreenEnricher.geomFactory.createPolygon(coordArray);
+ var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
+ }
+ for (int i = 1; i < ringCoordinates.size() - 2; i++) {
+ List topRing = ringCoordinates.get(i);
+ List bottomRing = ringCoordinates.get(i + 1);
+ for (int j = 0; j < topRing.size(); j++) {
+ int nextRingIndex = (j + 1) % topRing.size();
+ Coordinate p1 = topRing.get(j);
+ Coordinate p2 = topRing.get(nextRingIndex);
+ Coordinate p3 = bottomRing.get(j);
+ Coordinate p4 = bottomRing.get(nextRingIndex);
+ Coordinate[] coordArray = new Coordinate[] { p1, p3, p4, p2, p1 };
+ Polygon polygon = GreenEnricher.geomFactory.createPolygon(coordArray);
+ var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
+ }
+ }
+
+ // bottom point triangles
+ List bottomRing = ringCoordinates.get(ringCoordinates.size() - 2);
+ for (int i = 0; i < bottomRing.size(); i++) {
+ int nextRingIndex = (i + 1) % bottomRing.size();
+ Coordinate p1 = bottomRing.get(i);
+ Coordinate p2 = bottomRing.get(nextRingIndex);
+ Coordinate[] coordArray = new Coordinate[] { p2, p1, bottomPoint, p2 };
+ Polygon polygon = GreenEnricher.geomFactory.createPolygon(coordArray);
+ var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
+ ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
+ }
+
+ return ms;
+ }
+
+ private static Polygon convertPolygon(List trunkWestCoordinateList) {
+ return GreenEnricher.geomFactory
+ .createPolygon(trunkWestCoordinateList.toArray(new Coordinate[trunkWestCoordinateList.size()]));
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreePoint.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreePoint.java
new file mode 100644
index 0000000000000000000000000000000000000000..6460f8288fd97155b27f5b1bf1ddb1ecebf94bee
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreePoint.java
@@ -0,0 +1,29 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import org.locationtech.jts.geom.Point;
+
+public class TreePoint {
+
+ private Point point;
+
+ public TreePoint() {
+ }
+
+ public TreePoint(Point point) {
+ this.point = point;
+ }
+
+ public void setPoint(Point point) {
+ this.point = point;
+ }
+
+ public Point getPoint() {
+ return point;
+ }
+
+ @Override
+ public String toString() {
+ return "TreePoint [point=" + point + "]";
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeRow.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeRow.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8715731294329d820056b752b05686c8b4de1eb
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/TreeRow.java
@@ -0,0 +1,29 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import org.locationtech.jts.geom.LineString;
+
+public class TreeRow {
+
+ private LineString line;
+
+ public TreeRow() {
+ }
+
+ public TreeRow(LineString line) {
+ this.line = line;
+ }
+
+ public LineString getLine() {
+ return line;
+ }
+
+ public void setLine(LineString line) {
+ this.line = line;
+ }
+
+ @Override
+ public String toString() {
+ return "TreeRow [line=" + line + "]";
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/WaterArea.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/WaterArea.java
new file mode 100644
index 0000000000000000000000000000000000000000..70d03ddb23b8d557463bc52c84a7fe9b0c24f5ff
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/WaterArea.java
@@ -0,0 +1,29 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import org.locationtech.jts.geom.Polygon;
+
+public class WaterArea {
+
+ private Polygon area;
+
+ public WaterArea() {
+ }
+
+ public WaterArea(Polygon area) {
+ this.area = area;
+ }
+
+ public void setArea(Polygon area) {
+ this.area = area;
+ }
+
+ public Polygon getArea() {
+ return area;
+ }
+
+ @Override
+ public String toString() {
+ return "WaterArea [area=" + area + "]";
+ }
+
+}
diff --git a/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/Waterway.java b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/Waterway.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba38af9a41ef8c9c2d32f008b3425acf5530334f
--- /dev/null
+++ b/enrich-citygml-with-greenarea/src/main/java/de/hft/stuttgart/citygml/green/osm/Waterway.java
@@ -0,0 +1,30 @@
+package de.hft.stuttgart.citygml.green.osm;
+
+import org.locationtech.jts.geom.LineString;
+
+public class Waterway {
+
+ private LineString line;
+
+ public Waterway() {
+ }
+
+ public Waterway(LineString line) {
+ super();
+ this.line = line;
+ }
+
+ public void setLine(LineString line) {
+ this.line = line;
+ }
+
+ public LineString getLine() {
+ return line;
+ }
+
+ @Override
+ public String toString() {
+ return "Waterway [line=" + line + "]";
+ }
+
+}