Commit 690d78b9 authored by Matthias Betz's avatar Matthias Betz
Browse files

initial commit for osm data enricher

parent fb3bf74b
# 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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hft.stuttgart</groupId>
<artifactId>enrich-citygml-with-osm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.citygml4j/citygml4j-core -->
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-core</artifactId>
<version>3.0.0-rc.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.locationtech.proj4j/proj4j -->
<dependency>
<groupId>org.locationtech.proj4j</groupId>
<artifactId>proj4j</artifactId>
<version>1.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.19.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.citygml4j/citygml4j-xml -->
<dependency>
<groupId>org.citygml4j</groupId>
<artifactId>citygml4j-xml</artifactId>
<version>3.0.0-rc.5</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
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 + "]";
}
}
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<String> 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<GreenArea> newGreenAreas = new ArrayList<>();
System.out.println("Fit data in bounding box");
fitToBoundingBox(osmData);
System.out.println("Filter intersecting areas");
List<GreenArea> 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<GreenArea> 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<GreenArea> greenAreas = osmData.getGreenAreas();
List<GreenArea> 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<TreePoint> 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<WaterArea> 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<GreenArea> 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<Coordinate> 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<Double> 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<Double> 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<Double> 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<String> 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<String> 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<String> 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<String> 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<List<Coordinate>> innerRings = new ArrayList<>();
List<Coordinate> 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<Coordinate> 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<org.locationtech.jts.geom.LinearRing> innerLinearRings = new ArrayList<>();
for (List<Coordinate> 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<org.locationtech.jts.geom.LinearRing> innerLinearRings = new ArrayList<>();
// for (List<Coordinate> 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<Coordinate> 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<String> 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<Coordinate> 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<TreePoint> treePoints, Node node, Set<String> 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<Coordinate> 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;
}
}
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<TreePoint> treePoints = new ArrayList<>();
private List<GreenArea> greenAreas = new ArrayList<>();
private List<TreeRow> treeRows = new ArrayList<>();
private List<Waterway> waterways = new ArrayList<>();
private List<WaterArea> waterAreas = new ArrayList<>();
public void setBoundingBox(Polygon boundingBox) {
this.boundingBox = boundingBox;
}
public List<WaterArea> getWaterAreas() {
return waterAreas;
}
public List<Waterway> getWaterways() {
return waterways;
}
public Polygon getBoundingBox() {
return boundingBox;
}
public List<GreenArea> getGreenAreas() {
return greenAreas;
}
public List<TreePoint> getTreePoints() {
return treePoints;
}
public List<TreeRow> getTreeRows() {
return treeRows;
}
}
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<Coordinate> 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<Coordinate> 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<Coordinate> 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<Coordinate> 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<List<Coordinate>> 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<Coordinate> 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<Coordinate> 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<Coordinate> topRing = ringCoordinates.get(i);
List<Coordinate> 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<Coordinate> 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<Coordinate> trunkWestCoordinateList) {
return GreenEnricher.geomFactory
.createPolygon(trunkWestCoordinateList.toArray(new Coordinate[trunkWestCoordinateList.size()]));
}
}
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 + "]";
}
}
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 + "]";
}
}
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 + "]";
}
}
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 + "]";
}
}
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