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);