edu.ucr.cs.bdlab.io.GeoJSONFeatureWriter Maven / Gradle / Ivy
/*
* Copyright 2018 University of California, Riverside
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.ucr.cs.bdlab.io;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import edu.ucr.cs.bdlab.geolite.Envelope;
import edu.ucr.cs.bdlab.geolite.GeometryCollection;
import edu.ucr.cs.bdlab.geolite.IFeature;
import edu.ucr.cs.bdlab.geolite.IGeometry;
import edu.ucr.cs.bdlab.geolite.Point;
import edu.ucr.cs.bdlab.geolite.twod.LineString2D;
import edu.ucr.cs.bdlab.geolite.twod.MultiLineString2D;
import edu.ucr.cs.bdlab.geolite.twod.MultiPolygon2D;
import edu.ucr.cs.bdlab.geolite.twod.Polygon2D;
import edu.ucr.cs.bdlab.util.OperationParam;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import java.io.IOException;
import java.io.OutputStream;
/**
* Writes {@link IFeature} values in GeoJSON format as explained in
* https://geojson.org/ and
* http://wiki.geojson.org/.
* The output is a file with one object that contains an attribute "FeatureCollection" that has all the
* features in an array as separate objects.
*/
@FeatureWriter.Metadata(extension = ".geojson", shortName = "geojson")
public class GeoJSONFeatureWriter extends FeatureWriter {
/**Whether to print the output using the pretty printer or not*/
@OperationParam(
description = "Set this flag to true to use the pretty printer",
defaultValue = "true"
)
public static final String UsePrettyPrinter = "GeoJSONFeatureWriter.UsePrettyPrinter";
/**The output stream*/
protected OutputStream out;
/**The JSON output stream writer*/
protected JsonGenerator jsonGenerator;
/**
* Initializes the writer to the given file path.
* @param geoJSONPath
* @param conf
* @throws IOException
*/
@Override
public void initialize(Path geoJSONPath, Configuration conf) throws IOException {
FileSystem fs = geoJSONPath.getFileSystem(conf);
out = fs.create(geoJSONPath);
this.initialize(out, conf);
}
/**
* Initialize for a given output stream.
* @param out
* @param conf
* @throws IOException
*/
@Override
public void initialize(OutputStream out, Configuration conf) throws IOException {
jsonGenerator = new JsonFactory().createGenerator(out);
//for pretty printing
if (conf.getBoolean(UsePrettyPrinter, true))
jsonGenerator.setPrettyPrinter(new DefaultPrettyPrinter());
writeHeader(jsonGenerator);
}
/**
* Writes the header of the GeoJSON file before any features are written
* @param jsonGenerator
* @throws IOException
*/
protected void writeHeader(JsonGenerator jsonGenerator) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("type", "FeatureCollection");
jsonGenerator.writeFieldName("features");
jsonGenerator.writeStartArray();
}
@Override
public void write(Object dummy, IFeature f) throws IOException, InterruptedException {
writeFeature(jsonGenerator, f);
}
public static void writeFeature(JsonGenerator jsonGenerator, IFeature feature) throws IOException {
try {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("type", "Feature");
if (feature.getNumAttributes() > 0) {
jsonGenerator.writeFieldName("properties");
jsonGenerator.writeStartObject();
for (int iAttr = 0; iAttr < feature.getNumAttributes(); iAttr++) {
Object value = feature.getAttributeValue(iAttr);
if (value != null) {
String name = feature.getAttributeName(iAttr);
if (name == null || name.length() == 0)
name = String.format("attr%d", iAttr);
// TODO write numeric data as numeric not string
jsonGenerator.writeStringField(name, value.toString());
}
}
jsonGenerator.writeEndObject();
}
// Write the geometry
IGeometry geom = feature.getGeometry();
if (geom != null && !geom.isEmpty()) {
jsonGenerator.writeFieldName("geometry");
writeGeometryValue(jsonGenerator, geom);
}
jsonGenerator.writeEndObject();
} catch (Exception e) {
throw new RuntimeException(String.format("Error writing the feature '%s'", feature.toString()), e);
}
}
/**
* Writes a single geometry value in GeoJSON format using the given JSON generator (writer).
*
* @param geom the geometry to write in GeoJSON
* @see http://wiki.geojson.org/GeoJSON_draft_version_6
*/
public static void writeGeometryValue(JsonGenerator jsonGenerator, IGeometry geom) throws IOException {
jsonGenerator.writeStartObject();
// Write field type
String strType = null;
switch (geom.getType()) {
case POINT: strType = "Point"; break;
case LINESTRING: strType = "LineString"; break;
case ENVELOPE: // Treat as a polygon
case POLYGON: strType = "Polygon"; break;
case MULTILINESTRING: strType = "MultilineString"; break;
case MULTIPOLYGON: strType = "MultiPolygon"; break;
case GEOMETRYCOLLECTION: strType = "GeometryCollection"; break;
}
jsonGenerator.writeStringField("type", strType);
// Write field value
Point point;
switch (geom.getType()) {
case POINT:
// http://wiki.geojson.org/GeoJSON_draft_version_6#Point
point = (Point) geom;
jsonGenerator.writeFieldName("coordinates");
writeCoordinates(jsonGenerator, point.coords);
break;
case LINESTRING:
// http://wiki.geojson.org/GeoJSON_draft_version_6#LineString
LineString2D linestring = (LineString2D) geom;
jsonGenerator.writeFieldName("coordinates");
jsonGenerator.writeStartArray();
point = new Point(2);
for (int $i = 0; $i < linestring.getNumPoints(); $i++) {
linestring.getPointN($i, point);
writeCoordinates(jsonGenerator, point.coords);
}
jsonGenerator.writeEndArray();
break;
case ENVELOPE:
// GeoJSON does not support envelopes as a separate geometry. So, we write it as a polygon
Envelope envelope = (Envelope) geom;
jsonGenerator.writeFieldName("coordinates");
jsonGenerator.writeStartArray(); // Start of polygon
jsonGenerator.writeStartArray(); // Start of the single linear ring inside the polygon
// first point
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(envelope.minCoord[0]);
jsonGenerator.writeNumber(envelope.minCoord[1]);
jsonGenerator.writeEndArray();
// second point
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(envelope.maxCoord[0]);
jsonGenerator.writeNumber(envelope.minCoord[1]);
jsonGenerator.writeEndArray();
// third point
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(envelope.maxCoord[0]);
jsonGenerator.writeNumber(envelope.maxCoord[1]);
jsonGenerator.writeEndArray();
// fourth point
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(envelope.minCoord[0]);
jsonGenerator.writeNumber(envelope.maxCoord[1]);
jsonGenerator.writeEndArray();
// fifth point (= first point)
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(envelope.minCoord[0]);
jsonGenerator.writeNumber(envelope.minCoord[1]);
jsonGenerator.writeEndArray();
jsonGenerator.writeEndArray(); // End of the linear ring
jsonGenerator.writeEndArray(); // End of the polygon
break;
case POLYGON:
// http://wiki.geojson.org/GeoJSON_draft_version_6#Polygon
Polygon2D polygon = (Polygon2D) geom;
jsonGenerator.writeFieldName("coordinates");
// Start the array of rings
jsonGenerator.writeStartArray();
point = new Point(2);
for (int $iRing = 0; $iRing < polygon.numRings; $iRing++) {
// String the array of points in this ring
jsonGenerator.writeStartArray();
int $iPoint = polygon.firstPointInRing[$iRing];
int lastPointInRing = ($iRing == polygon.numRings - 1)? polygon.numPoints : polygon.firstPointInRing[$iRing+1];
while ($iPoint < lastPointInRing) {
polygon.getPointN($iPoint, point);
writeCoordinates(jsonGenerator, point.coords);
$iPoint++;
}
// Repeat the first point as mandated by the GeoJSON format
polygon.getPointN(polygon.firstPointInRing[$iRing], point);
writeCoordinates(jsonGenerator, point.coords);
// Close the array of points in this ring
jsonGenerator.writeEndArray();
}
// Close the array of rings
jsonGenerator.writeEndArray();
break;
case MULTILINESTRING:
// http://wiki.geojson.org/GeoJSON_draft_version_6#MultiLineString
MultiLineString2D multiLineString = (MultiLineString2D) geom;
point = new Point(2);
jsonGenerator.writeFieldName("coordinates");
jsonGenerator.writeStartArray();
for (int $iRing = 0; $iRing < multiLineString.numLineStrings; $iRing++) {
jsonGenerator.writeStartArray();
int $iPoint = multiLineString.firstPointInLineString[$iRing];
int lastPointInRing = ($iRing == multiLineString.numLineStrings - 1)?
multiLineString.numPoints : multiLineString.firstPointInLineString[$iRing+1];
while ($iPoint < lastPointInRing) {
multiLineString.getPointN($iPoint, point);
writeCoordinates(jsonGenerator, point.coords);
$iPoint++;
}
jsonGenerator.writeEndArray();
}
jsonGenerator.writeEndArray();
break;
case MULTIPOLYGON:
// http://wiki.geojson.org/GeoJSON_draft_version_6#MultiPolygon
MultiPolygon2D multiPolygon = (MultiPolygon2D) geom;
point = new Point(2);
jsonGenerator.writeFieldName("coordinates");
jsonGenerator.writeStartArray(); // Start of the multipolygon
int $iPoly = 0, $iRing = 0;
int lastPointInCurrentPolygon = 0, lastPointInCurrentRing = 0;
for (int $iPoint = 0; $iPoint < multiPolygon.numPoints; $iPoint++) {
if ($iPoint == multiPolygon.firstPointInPolygon[$iPoly]) {
jsonGenerator.writeStartArray(); // Start of the polygon
lastPointInCurrentPolygon = $iPoly == multiPolygon.numPolygons - 1?
multiPolygon.numPoints - 1 : multiPolygon.firstPointInPolygon[$iPoly+1] - 1;
}
if ($iPoint == multiPolygon.firstPointInRing[$iRing]) {
jsonGenerator.writeStartArray(); // Start of the ring
lastPointInCurrentRing = $iRing == multiPolygon.numRings - 1?
multiPolygon.numPoints - 1 : multiPolygon.firstPointInRing[$iRing+1] - 1;
}
// Write the point
multiPolygon.getPointN($iPoint, point);
writeCoordinates(jsonGenerator, point.coords);
// Check if last point in ring
if ($iPoint == lastPointInCurrentRing) {
// Repeat the first point to conform with the GeoJSON format
multiPolygon.getPointN(multiPolygon.firstPointInRing[$iRing], point);
writeCoordinates(jsonGenerator, point.coords);
// Close the array of points in the current ring
jsonGenerator.writeEndArray(); // End of the current ring
$iRing++;
}
// Check if last point in polygon
if ($iPoint == lastPointInCurrentPolygon) {
jsonGenerator.writeEndArray(); // End of the current polygon
$iPoly++;
}
}
jsonGenerator.writeEndArray(); // End of the multipolygon
break;
case GEOMETRYCOLLECTION:
// http://wiki.geojson.org/GeoJSON_draft_version_6#GeometryCollection
GeometryCollection geometryCollection = (GeometryCollection) geom;
jsonGenerator.writeFieldName("geometries");
jsonGenerator.writeStartArray(); // Start of the geometry collection
for (int $iGeom = 0; $iGeom < geometryCollection.numGeometries(); $iGeom++) {
writeGeometryValue(jsonGenerator, geometryCollection.getGeometry($iGeom));
}
jsonGenerator.writeEndArray(); // End of the geometry collection
break;
default:
throw new RuntimeException(String.format("Geometry type '%s' is not yet supported in GeoJSON", geom.getType()));
}
jsonGenerator.writeEndObject();
}
private static void writeCoordinates(JsonGenerator jsonGenerator, double[] coords) throws IOException {
jsonGenerator.writeStartArray();
for (double coord : coords)
jsonGenerator.writeNumber(coord);
jsonGenerator.writeEndArray();
}
@Override
public void close(TaskAttemptContext taskAttemptContext) throws IOException {
// Close the array of features
jsonGenerator.writeEndArray();
// Close the main object
jsonGenerator.writeEndObject();
jsonGenerator.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy