org.openstreetmap.atlas.geography.geojson.GeoJsonBuilder Maven / Gradle / Ivy
package org.openstreetmap.atlas.geography.geojson;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.openstreetmap.atlas.exception.CoreException;
import org.openstreetmap.atlas.geography.Location;
import org.openstreetmap.atlas.geography.PolyLine;
import org.openstreetmap.atlas.geography.Polygon;
import org.openstreetmap.atlas.utilities.collections.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* @author matthieun
* @author cuthbertm
* @author mgostintsev
*/
public class GeoJsonBuilder
{
/**
* @author matthieun
* @author mgostintsev
*/
public enum GeoJsonType
{
POINT("Point"),
LINESTRING("LineString"),
POLYGON("Polygon"),
MULTI_POINT("MultiPoint"),
MULTI_LINESTRING("MultiLineString"),
MULTI_POLYGON("MultiPolygon");
private final String type;
public static GeoJsonType forType(final String type)
{
for (final GeoJsonType value : values())
{
if (value.getType().equals(type))
{
return value;
}
}
throw new CoreException("Invalid geoJson type: {}", type);
}
GeoJsonType(final String type)
{
this.type = type;
}
public String getType()
{
return this.type;
}
}
/**
* Java bean to store the geometry (as an {@link Iterable} of {@link Location}s) and all the
* tags as a {@link String} to {@link String} {@link Map}
*
* @author matthieun
*/
public static class LocationIterableProperties
{
private final Iterable locations;
private final Map properties;
public LocationIterableProperties(final Iterable locations,
final Map properties)
{
this.locations = locations;
this.properties = properties;
}
public Iterable getLocations()
{
return this.locations;
}
public Map getProperties()
{
return this.properties;
}
}
public static final String COORDINATES = "coordinates";
public static final String FEATURE = "Feature";
public static final String FEATURES = "features";
public static final String FEATURE_COLLECTION = "FeatureCollection";
public static final String GEOMETRIES = "geometries";
public static final String GEOMETRY = "geometry";
public static final String GEOMETRY_COLLECTION = "GeometryCollection";
public static final String PROPERTIES = "properties";
public static final String TYPE = "type";
private static final Logger logger = LoggerFactory.getLogger(GeoJsonBuilder.class);
private final int logFrequency;
public GeoJsonBuilder()
{
this.logFrequency = -1;
}
public GeoJsonBuilder(final int logFrequency)
{
this.logFrequency = logFrequency;
}
public GeoJsonObject create(final GeoJsonType type, final Location... locations)
{
return this.create(Iterables.iterable(locations), type);
}
/**
* Creates a GeoJson Feature containing a Geometry
*
* @param locations
* geometry coordinates
* @param type
* geometry type
* @return a GeoJson Feature
*/
public GeoJsonObject create(final Iterable locations, final GeoJsonType type)
{
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE);
final JsonArray coordinates = new JsonArray();
switch (type)
{
case POINT:
{
final Location location = locations.iterator().next();
coordinates.add(new JsonPrimitive(location.getLongitude().asDegrees()));
coordinates.add(new JsonPrimitive(location.getLatitude().asDegrees()));
break;
}
case LINESTRING:
case MULTI_POINT:
case MULTI_LINESTRING:
case MULTI_POLYGON:
{
for (final Location location : locations)
{
final JsonArray locationArray = new JsonArray();
locationArray.add(new JsonPrimitive(location.getLongitude().asDegrees()));
locationArray.add(new JsonPrimitive(location.getLatitude().asDegrees()));
coordinates.add(locationArray);
}
break;
}
case POLYGON:
{
final JsonArray locationArray = new JsonArray();
for (final Location location : locations)
{
final JsonArray locationArray2 = new JsonArray();
locationArray2.add(new JsonPrimitive(location.getLongitude().asDegrees()));
locationArray2.add(new JsonPrimitive(location.getLatitude().asDegrees()));
locationArray.add(locationArray2);
}
coordinates.add(locationArray);
break;
}
default:
throw new CoreException("Unrecognized object type {}", type);
}
final JsonObject geometry = new JsonObject();
geometry.addProperty(TYPE, type.getType());
geometry.add(COORDINATES, coordinates);
result.add(GEOMETRY, geometry);
return new GeoJsonObject(result);
}
/**
* Creates a GeoJson FeatureCollection containing a list of Features
*
* @param objects
* used to build each Feature
* @return a GeoJson FeatureCollection
*/
public GeoJsonObject create(final Iterable objects)
{
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE_COLLECTION);
final JsonArray features = new JsonArray();
int counter = 0;
for (final LocationIterableProperties object : objects)
{
if (this.logFrequency > 0 && ++counter % this.logFrequency == 0)
{
logger.info("Processed {} features.", counter);
}
features.add(create(object));
}
result.add(FEATURES, features);
return new GeoJsonObject(result);
}
/**
* Creates a Point type GeoJson Feature
*
* @param location
* geometry
* @return a Feature
*/
public GeoJsonObject create(final Location location)
{
return this.create(location, GeoJsonType.POINT);
}
/**
* Creates a Json Feature from a {@link LocationIterableProperties}
*
* @param object
* {@link LocationIterableProperties}
* @return a GeoJson Feature
*/
public JsonObject create(final LocationIterableProperties object)
{
final Iterable geometry = object.getLocations();
final Map properties = object.getProperties();
if (geometry instanceof Location)
{
return create((Location) geometry).withNewProperties(properties).jsonObject();
}
else if (geometry instanceof Polygon)
{
return create((Polygon) geometry).withNewProperties(properties).jsonObject();
}
else if (geometry instanceof PolyLine)
{
return create((PolyLine) geometry).withNewProperties(properties).jsonObject();
}
else
{
throw new CoreException("Unrecognized object type {}",
geometry.getClass().getSimpleName());
}
}
/**
* Creates a Polygon type GeoJson Feature
*
* @param polygon
* geometry
* @return a GeoJson Feature
*/
public GeoJsonObject create(final Polygon polygon)
{
return this.create(polygon.closedLoop(), GeoJsonType.POLYGON);
}
/**
* Creates a LineString type GeoJson Feature
*
* @param polyLine
* geometry
* @return a GeoJson Feature
*/
public GeoJsonObject create(final PolyLine polyLine)
{
return this.create(polyLine, GeoJsonType.LINESTRING);
}
/**
* Creates a GeoJson FeatureCollection containing a list of GeoJsonObject Features
*
* @param objects
* the features
* @return a GeoJson FeatureCollection
*/
public GeoJsonObject createFeatureCollection(final Iterable objects)
{
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE_COLLECTION);
final JsonArray features = new JsonArray();
int counter = 0;
for (final GeoJsonObject object : objects)
{
if (this.logFrequency > 0 && ++counter % this.logFrequency == 0)
{
logger.info("Processed {} features.", counter);
}
if (!Optional.ofNullable(object.jsonObject().get(TYPE))
.filter(jsonObject -> jsonObject.getAsString().equals(FEATURE)).isPresent())
{
throw new CoreException("Illegal GeoJson Type for Feature collection");
}
features.add(object.jsonObject());
}
result.add(FEATURES, features);
return new GeoJsonObject(result);
}
/**
* Creates a GeoJson FeatureCollection from an iterable of GeoJsonObject
*
* @param geoJsonObjects
* a iterable of GeoJsonObject
* @return a GeoJson FeatureCollection
*/
public GeoJsonObject createFromGeoJson(final Iterable geoJsonObjects)
{
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE_COLLECTION);
final JsonArray features = new JsonArray();
int counter = 0;
for (final GeoJsonObject object : geoJsonObjects)
{
if (this.logFrequency > 0 && ++counter % this.logFrequency == 0)
{
logger.info("Processed {} features.", counter);
}
features.add(object.jsonObject());
}
result.add(FEATURES, features);
return new GeoJsonObject(result);
}
/**
* Creates a GeometryCollection type Feature containing geometries derived from a collection of
* {@link LocationIterableProperties}. Note: feature parameters are not present
* in the resulting GeometryCollection and must be handled separately to avoid data loss.
*
* @param objects
* used to build each geometry
* @return a GeoJson Feature
*/
public GeoJsonObject createGeometryCollection(
final Iterable objects)
{
final JsonObject geometryCollection = new JsonObject();
geometryCollection.addProperty(TYPE, GEOMETRY_COLLECTION);
final Map>> geometryMap = new HashMap<>();
int counter = 0;
for (final LocationIterableProperties object : objects)
{
if (this.logFrequency > 0 && ++counter % this.logFrequency == 0)
{
logger.info("Processed {} geometries.", counter);
}
final Iterable geometry = object.getLocations();
final GeoJsonType geoJsonType;
if (geometry instanceof Location)
{
geoJsonType = GeoJsonType.POINT;
}
else if (geometry instanceof Polygon)
{
geoJsonType = GeoJsonType.POLYGON;
}
else if (geometry instanceof PolyLine)
{
geoJsonType = GeoJsonType.LINESTRING;
}
else
{
throw new CoreException("Unrecognized object type {}",
geometry.getClass().getSimpleName());
}
geometryMap.computeIfAbsent(geoJsonType, key -> new ArrayList<>()).add(geometry);
}
final JsonArray geometries = new JsonArray();
// Point vs MultiPoint
if (geometryMap.containsKey(GeoJsonType.POINT))
{
final List> points = geometryMap.get(GeoJsonType.POINT);
if (points.size() > 1)
{
geometries.add(create(Iterables.translate(points, Iterables::head),
GeoJsonType.MULTI_POINT).jsonObject().getAsJsonObject(GEOMETRY));
}
else if (points.size() == 1)
{
geometries.add(create(Iterables.head(points), GeoJsonType.POINT).jsonObject()
.getAsJsonObject(GEOMETRY));
}
}
// Polygon vs MultiPolygon
if (geometryMap.containsKey(GeoJsonType.POLYGON))
{
final List> polygons = geometryMap.get(GeoJsonType.POLYGON);
if (polygons.size() > 1)
{
geometries.add(createMultiPolygons(Iterables.stream(polygons).map(Polygon::new))
.jsonObject().getAsJsonObject(GEOMETRY));
}
else if (polygons.size() == 1)
{
geometries.add(create(Iterables.head(polygons), GeoJsonType.POLYGON).jsonObject()
.getAsJsonObject(GEOMETRY));
}
}
// LineString vs MultLineString
if (geometryMap.containsKey(GeoJsonType.LINESTRING))
{
final List> multiPolylines = geometryMap.get(GeoJsonType.LINESTRING);
if (multiPolylines.size() > 1)
{
geometries.add(
createMultiLineStrings(Iterables.stream(multiPolylines).map(PolyLine::new))
.jsonObject().getAsJsonObject(GEOMETRY));
}
else if (multiPolylines.size() == 1)
{
geometries.add(create(Iterables.head(multiPolylines), GeoJsonType.LINESTRING)
.jsonObject().getAsJsonObject(GEOMETRY));
}
}
geometryCollection.add(GEOMETRIES, geometries);
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE);
result.add(GEOMETRY, geometryCollection);
return new GeoJsonObject(result);
}
/**
* Creates a MultiLineString type GeoJson Feature
*
* @param polyLines
* geometry
* @return a GeoJson Feature
*/
public GeoJsonObject createMultiLineStrings(final Iterable polyLines)
{
// Create the coordinates for each polyline
final List objects = new ArrayList<>();
for (final PolyLine polygon : polyLines)
{
objects.add(this.create(polygon, GeoJsonType.MULTI_LINESTRING));
}
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE);
final JsonArray coordinates = new JsonArray();
// Add the coordinates back for the entire object
for (final GeoJsonObject object : objects)
{
coordinates
.add(object.jsonObject().getAsJsonObject(GEOMETRY).getAsJsonArray(COORDINATES));
}
final JsonObject geometry = new JsonObject();
geometry.addProperty(TYPE, GeoJsonType.MULTI_LINESTRING.getType());
geometry.add(COORDINATES, coordinates);
result.add(GEOMETRY, geometry);
return new GeoJsonObject(result);
}
/**
* Creates a MultiPolygon type GeoJson Feature
*
* @param polygons
* geometries
* @return a GeoJson Feature
*/
public GeoJsonObject createMultiPolygons(final Iterable polygons)
{
// Create the coordinates for each polygon
final List objects = new ArrayList<>();
for (final Polygon polygon : polygons)
{
objects.add(this.create(polygon.closedLoop(), GeoJsonType.MULTI_POLYGON));
}
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE);
final JsonArray coordinates = new JsonArray();
// Add the coordinates back for the entire object
for (final GeoJsonObject object : objects)
{
final JsonArray subCoordinates = new JsonArray();
subCoordinates
.add(object.jsonObject().getAsJsonObject(GEOMETRY).getAsJsonArray(COORDINATES));
coordinates.add(subCoordinates);
}
final JsonObject geometry = new JsonObject();
geometry.addProperty(TYPE, GeoJsonType.MULTI_POLYGON.getType());
geometry.add(COORDINATES, coordinates);
result.add(GEOMETRY, geometry);
return new GeoJsonObject(result);
}
/**
* Creates multipolygon from {@link Iterable} of {@link Polygon}s where first polygon is assumed
* to be the outer ring and the rest are inner.
*
* @param polygons
* an iterable of polygons where the first is assumed to be the outer polygon in a
* multipolygon
* @return a MultiPolygon geojson feature with one polygon that geometrically represents a
* single outer Atlas Multipolygon
*/
public GeoJsonObject createOneOuterMultiPolygon(final Iterable polygons)
{
// Create the coordinates for each polygon
final List objects = new ArrayList<>();
for (final Polygon polygon : polygons)
{
objects.add(this.create(polygon.closedLoop(), GeoJsonType.MULTI_POLYGON));
}
final JsonObject result = new JsonObject();
result.addProperty(TYPE, FEATURE);
final JsonArray coordinates = new JsonArray();
// Add the coordinates back for the entire object
for (final GeoJsonObject object : objects)
{
coordinates
.add(object.jsonObject().getAsJsonObject(GEOMETRY).getAsJsonArray(COORDINATES));
}
final JsonArray newCoordinates = new JsonArray();
newCoordinates.add(coordinates);
final JsonObject geometry = new JsonObject();
geometry.addProperty(TYPE, GeoJsonType.MULTI_POLYGON.getType());
geometry.add(COORDINATES, newCoordinates);
result.add(GEOMETRY, geometry);
result.add(PROPERTIES, new JsonObject());
return new GeoJsonObject(result);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy