org.heigit.ohsome.ohsomeapi.inputprocessing.GeometryBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ohsome-api Show documentation
Show all versions of ohsome-api Show documentation
A public Web-RESTful-API for "ohsome" OpenStreetMap history data.
The newest version!
package org.heigit.ohsome.ohsomeapi.inputprocessing;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Stream;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import org.geojson.GeoJsonObject;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.heigit.ohsome.ohsomeapi.exception.BadRequestException;
import org.heigit.ohsome.ohsomeapi.exception.ExceptionMessages;
import org.heigit.ohsome.ohsomeapi.exception.NotFoundException;
import org.heigit.ohsome.oshdb.OSHDBBoundingBox;
import org.heigit.ohsome.oshdb.util.geometry.OSHDBGeometryBuilder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.wololo.jts2geojson.GeoJSONReader;
/**
* Includes methods to create and manipulate geometries derived from the boundary input parameters.
*/
public class GeometryBuilder {
GeometryFactory gf;
private final ProcessingData processingData;
public GeometryBuilder() {
this.processingData = null;
}
/**
* Creates a unified Geometry
object out of the content of the given
* String
array.
*
* @param bboxes String
array containing the lon/lat coordinates of the bounding
* boxes. Each bounding box must consist of 2 lon/lat coordinate pairs (bottom-left and
* top-right).
* @return Geometry
object representing the unified bounding boxes.
* @throws BadRequestException if bboxes coordinates are invalid
*/
public Geometry createBboxes(String[] bboxes) {
try {
Geometry unifiedBbox;
OSHDBBoundingBox bbox;
gf = new GeometryFactory();
double minLon = Double.parseDouble(bboxes[0]);
double minLat = Double.parseDouble(bboxes[1]);
double maxLon = Double.parseDouble(bboxes[2]);
double maxLat = Double.parseDouble(bboxes[3]);
bbox = OSHDBBoundingBox.bboxWgs84Coordinates(minLon, minLat, maxLon, maxLat);
unifiedBbox = gf.createGeometry(OSHDBGeometryBuilder.getGeometry(bbox));
ArrayList geometryList = new ArrayList<>();
geometryList.add(OSHDBGeometryBuilder.getGeometry(bbox));
for (int i = 4; i < bboxes.length; i += 4) {
minLon = Double.parseDouble(bboxes[i]);
minLat = Double.parseDouble(bboxes[i + 1]);
maxLon = Double.parseDouble(bboxes[i + 2]);
maxLat = Double.parseDouble(bboxes[i + 3]);
bbox = OSHDBBoundingBox.bboxWgs84Coordinates(minLon, minLat, maxLon, maxLat);
geometryList.add(OSHDBGeometryBuilder.getGeometry(bbox));
unifiedBbox = unifiedBbox.union(OSHDBGeometryBuilder.getGeometry(bbox));
}
Geometry result = unifyPolys(geometryList);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(unifiedBbox);
return result;
} catch (NumberFormatException e) {
throw new BadRequestException("Apart from the custom ids, the bboxeses array must contain double-parseable values " + "in the following order: minLon, minLat, maxLon, maxLat.");
}
}
/**
* Creates a Geometry
object around the coordinates of the given String
* array.
*
* @param bpoints String
array containing the lon/lat coordinates of the point at [0]
* and [1] and the size of the buffer at [2].
* @return Geometry
object representing (a) circular polygon(s) around the given
* bounding point(s).
* @throws BadRequestException if bcircle coordinates or radius are invalid
*/
public Geometry createCircularPolygons(String[] bpoints) {
GeometryFactory geomFact = new GeometryFactory();
Geometry buffer;
Geometry geom;
CoordinateReferenceSystem sourceCrs;
CoordinateReferenceSystem targetCrs;
MathTransform transform = null;
ArrayList geometryList = new ArrayList<>();
InputProcessingUtils utils = new InputProcessingUtils();
try {
for (int i = 0; i < bpoints.length; i += 3) {
sourceCrs = CRS.decode("EPSG:4326", true);
targetCrs = CRS.decode(utils.findEpsg(Double.parseDouble(bpoints[i]), Double.parseDouble(bpoints[i + 1])), true);
transform = CRS.findMathTransform(sourceCrs, targetCrs, false);
Point p = geomFact.createPoint(new Coordinate(Double.parseDouble(bpoints[i]), Double.parseDouble(bpoints[i + 1])));
buffer = JTS.transform(p, transform).buffer(Double.parseDouble(bpoints[i + 2]));
transform = CRS.findMathTransform(targetCrs, sourceCrs, false);
geom = JTS.transform(buffer, transform);
if (bpoints.length == 3) {
if (!utils.isWithin(geom)) {
throw new NotFoundException(ExceptionMessages.BOUNDARY_NOT_IN_DATA_EXTRACT);
}
geometryList.add(geom);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(geom);
return geom;
}
geometryList.add(geom);
}
Geometry result = unifyPolys(geometryList);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(result);
return result;
} catch (NumberFormatException | FactoryException | MismatchedDimensionException | TransformException | ArrayIndexOutOfBoundsException e) {
throw new BadRequestException("Each bcircle must consist of a lon/lat coordinate pair plus a buffer in meters.");
}
}
/**
* Creates a Polygon
out of the coordinates in the given array. If more polygons are
* given, a union of the polygons is applied and a MultiPolygon
is created.
*
* @param bpolys String
array containing the lon/lat coordinates of the bounding
* polygon(s).
* @return Geometry
object representing a Polygon
object, if only one
* polygon was given or a MultiPolygon
object, if more than one were given.
* @throws BadRequestException if bpolys coordinates are invalid
*/
public Geometry createBpolys(String[] bpolys) {
GeometryFactory geomFact = new GeometryFactory();
Geometry bpoly;
ArrayList coords = new ArrayList<>();
ArrayList geometryList = new ArrayList<>();
InputProcessingUtils utils = new InputProcessingUtils();
if (bpolys[0].equals(bpolys[bpolys.length - 2]) && bpolys[1].equals(bpolys[bpolys.length - 1])) {
try {
for (int i = 0; i < bpolys.length; i += 2) {
coords.add(new Coordinate(Double.parseDouble(bpolys[i]), Double.parseDouble(bpolys[i + 1])));
}
bpoly = geomFact.createPolygon(coords.toArray(new Coordinate[] {}));
} catch (IllegalArgumentException e) {
throw new BadRequestException(ExceptionMessages.BPOLYS_FORMAT);
}
if (!utils.isWithin(bpoly)) {
throw new NotFoundException(ExceptionMessages.BOUNDARY_NOT_IN_DATA_EXTRACT);
}
geometryList.add(bpoly);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(bpoly);
return bpoly;
}
Coordinate firstPoint = null;
try {
for (int i = 0; i < bpolys.length; i += 2) {
if (firstPoint != null && firstPoint.x == Double.parseDouble(bpolys[i]) && firstPoint.y == Double.parseDouble(bpolys[i + 1])) {
Polygon poly;
coords.add(new Coordinate(Double.parseDouble(bpolys[i]), Double.parseDouble(bpolys[i + 1])));
poly = geomFact.createPolygon(coords.toArray(new Coordinate[] {}));
geometryList.add(poly);
coords.clear();
firstPoint = null;
} else {
Coordinate coord = new Coordinate(Double.parseDouble(bpolys[i]), Double.parseDouble(bpolys[i + 1]));
coords.add(coord);
if (firstPoint == null) {
firstPoint = coord;
}
}
}
Geometry result = unifyPolys(geometryList);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(result);
return result;
} catch (NumberFormatException | MismatchedDimensionException e) {
throw new BadRequestException(ExceptionMessages.BPOLYS_FORMAT);
}
}
/**
* Creates a Geometry object from the given GeoJSON String, which is derived from the metadata.
*
* @throws RuntimeException if the derived GeoJSON cannot be converted to a Geometry
*/
public void createGeometryFromMetadataGeoJson(String geoJson) {
GeoJSONReader reader = new GeoJSONReader();
try {
ProcessingData.setDataPolyGeom(reader.read(geoJson));
} catch (Exception e) {
throw new RuntimeException("The GeoJSON that is derived out of the metadata, cannot be " + "converted. Please use a different data file and contact an admin about this issue.");
}
}
/**
* Creates a Geometry object from the given GeoJSON String. It must be of type 'FeatureCollection'
* and its features must be of type 'Polygon' or 'Multipolygon'.
*
* @throws BadRequestException if the given GeoJSON String cannot be converted to a Geometry, it
* is not of the type 'FeatureCollection', or if the provided custom id(s) cannot be
* parsed
*/
public Geometry createGeometryFromGeoJson(String geoJson, InputProcessor inputProcessor) {
ArrayList geometryList = new ArrayList<>();
JsonObject root = null;
JsonArray features;
Serializable[] boundaryIds;
GeoJsonObject[] geoJsonGeoms;
try (JsonReader jsonReader = Json.createReader(new StringReader(geoJson))) {
root = jsonReader.readObject();
features = root.getJsonArray("features");
boundaryIds = new Serializable[features.size()];
geoJsonGeoms = new GeoJsonObject[features.size()];
} catch (Exception e) {
throw new BadRequestException("Error in reading the provided GeoJSON. The given GeoJSON has " + "to be of the type \'FeatureCollection\'. Please take a look at the documentation page " + "for the bpolys parameter to see an example of a fitting GeoJSON input file.");
}
int count = 0;
for (JsonValue featureVal : features) {
JsonObject feature = featureVal.asJsonObject();
JsonObject properties = feature.getJsonObject("properties");
try {
if (feature.containsKey("id")) {
boundaryIds[count] = createBoundaryIdFromJsonObjectId(feature, inputProcessor);
} else if (properties != null && properties.containsKey("id")) {
boundaryIds[count] = createBoundaryIdFromJsonObjectId(properties, inputProcessor);
} else {
boundaryIds[count] = "feature" + (count + 1);
}
count++;
} catch (BadRequestException e) {
throw e;
} catch (Exception e) {
throw new BadRequestException("The provided custom id(s) could not be parsed.");
}
try {
JsonObject geomObj = feature.getJsonObject("geometry");
checkGeometryTypeOfFeature(geomObj);
GeoJSONReader reader = new GeoJSONReader();
Geometry currentResult = reader.read(geomObj.toString());
geometryList.add(currentResult);
geoJsonGeoms[count - 1] = new ObjectMapper().readValue(geomObj.toString(), GeoJsonObject.class);
} catch (Exception e) {
throw new BadRequestException("The provided GeoJSON cannot be converted. Please take a " + "look at the documentation page for the bpolys parameter to see an example of a " + "fitting GeoJSON input file.");
}
}
Geometry result = unifyPolys(geometryList);
processingData.setGeoJsonGeoms(geoJsonGeoms);
processingData.setBoundaryList(geometryList);
processingData.setRequestGeom(result);
InputProcessingUtils util = inputProcessor.getUtils();
util.setBoundaryIds(boundaryIds);
return result;
}
/**
* Computes the union of the given geometries and checks if it is completely within the underlying
* data extract.
*
* @param geometries Collection
containing the geometries to unify
* @return unified geometries
* @throws NotFoundException if the unified Geometry does not lie completely within the underlying
* data extract
*/
private Geometry unifyPolys(Collection geometries) {
GeometryFactory geometryFactory = new GeometryFactory();
Polygon[] polys = geometries.stream().flatMap(geo -> {
if (geo instanceof MultiPolygon) {
int num = geo.getNumGeometries();
ArrayList parts = new ArrayList<>(num);
for (int i = 0; i < num; i++) {
parts.add((Polygon) geo.getGeometryN(i));
}
return parts.stream();
} else {
return Stream.of(geo);
}
}).toArray(Polygon[]::new);
MultiPolygon mp = geometryFactory.createMultiPolygon(polys);
// merge all input geometries to single (multi) polygon
Geometry result = mp.union();
InputProcessingUtils utils = new InputProcessingUtils();
if (!utils.isWithin(result)) {
throw new NotFoundException(ExceptionMessages.BOUNDARY_NOT_IN_DATA_EXTRACT);
}
return result;
}
/**
* Creates a boundary ID value from the 'id' field in the given JsonObject
.
*
* @param jsonObject JsonObject
where the 'id' value is extracted from
* @param inputProcessor used for {@link
* org.heigit.ohsome.ohsomeapi.inputprocessing.InputProcessingUtils
* #checkCustomBoundaryId(String) checkCustomBoundaryId}
* @return Object
having the custom id of type String
or
* Integer
*/
private Serializable createBoundaryIdFromJsonObjectId(JsonObject jsonObject, InputProcessor inputProcessor) {
if (jsonObject.get("id").getValueType().compareTo(JsonValue.ValueType.STRING) == 0) {
String id = jsonObject.getString("id");
if ("csv".equalsIgnoreCase(processingData.getFormat())) {
inputProcessor.getUtils().checkCustomBoundaryId(id);
}
return id;
} else {
return jsonObject.getInt("id");
}
}
/**
* Checks the geometry of the given JsonObject
on its type. If it's not of type
* Polygon or Multipolygon, an exception is thrown.
*
* @param geomObj JsonObject
to check
* @throws BadRequestException if the given JsonObject
is not of type Polygon or
* Multipolygon
*/
private void checkGeometryTypeOfFeature(JsonObject geomObj) {
if (!geomObj.getString("type").equals("Polygon") && !geomObj.getString("type").equals("MultiPolygon")) {
throw new BadRequestException("The geometry of each feature in the GeoJSON has to be of type \'Polygon\' " + "or \'MultiPolygon\'.");
}
}
public ProcessingData getprocessingData() {
return processingData;
}
@java.lang.SuppressWarnings("all")
public GeometryBuilder(final ProcessingData processingData) {
this.processingData = processingData;
}
}