All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.ucr.cs.bdlab.io.GeoJSONFeatureWriter Maven / Gradle / Ivy

There is a newer version: 0.10.1-RC2
Show newest version
/*
 * 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