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