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

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

package edu.ucr.cs.bdlab.io;

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.MultiPoint;
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 org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Writes {@link IFeature} values in KML format as explained in
 * http://www.opengis.net/kml/2.2 and
 * https://developers.google.com/kml.
 * The output is a file with one object that contains a label "Document" that has all the
 * features under it.
 */
@FeatureWriter.Metadata(extension = ".kml", shortName = "kml")
public class KMLFeatureWriter extends FeatureWriter {
  /**
   * The output stream
   */
  protected OutputStream out;

  /**
   * The KML output stream writer
   */
  protected XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
  protected XMLStreamWriter sw;

  /**
   * Initializes the writer to the given file path.
   *
   * @param kmlPath
   * @param conf
   * @throws IOException
   */
  @Override
  public void initialize(Path kmlPath, Configuration conf) throws IOException {
    FileSystem fs = kmlPath.getFileSystem(conf);
    out = fs.create(kmlPath);
    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 {
    try {
      this.out = out;
      sw = xmlOutputFactory.createXMLStreamWriter(out, "UTF-8");
    } catch (XMLStreamException e1) {
      e1.printStackTrace();
    }
    try {
      writeHeader(sw);
    } catch (XMLStreamException e) {
      e.printStackTrace();
    }
  }

  /**
   * Writes the header of the KML file before any features are written
   *
   * @param sw
   * @throws IOException
   * @throws XMLStreamException
   */
  protected void writeHeader(XMLStreamWriter sw) throws IOException, XMLStreamException {
    sw.writeStartDocument("UTF-8", "1.0");
    sw.writeStartElement("kml");
    sw.writeAttribute("xmlns", "http://www.opengis.net/kml/2.2");
    sw.writeStartElement("Document");
  }

  @Override
  public void write(Object dummy, IFeature f) throws IOException, InterruptedException {
    writeFeature(sw, f);
  }

  public static void writeFeature(XMLStreamWriter sw, IFeature feature) throws IOException {
    try {
      sw.writeStartElement("Placemark");
      sw.writeStartElement("ExtendedData");
      if (feature.getNumAttributes() > 0) {
        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
            sw.writeStartElement("Data");
            sw.writeAttribute("name", name);
            sw.writeStartElement("value");
            sw.writeCharacters(String.valueOf(value));
            sw.writeEndElement();
            sw.writeEndElement();
          }
        }
      }
      sw.writeEndElement();
      // Write the geometry
      IGeometry geom = feature.getGeometry();
      if (geom != null && !geom.isEmpty()) {
        writeGeometryValue(sw, geom);
        sw.writeEndElement();
      }
    } catch (XMLStreamException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * Writes a single geometry value in KML format using the given XMLStreamWriter (writer).
   *
   * @param geom the geometry to write in KML
   * @see http://www.opengis.net/doc/IS/kml/2.3
   */
  public static void writeGeometryValue(XMLStreamWriter sw, IGeometry geom) throws IOException, XMLStreamException {
    // 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;
    }
    // Write field value
    Point point;
    switch (geom.getType()) {
      case POINT:
        //http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#446
        sw.writeStartElement(strType);
        point = (Point) geom;
        sw.writeStartElement("coordinates");
        writeCoordinates(sw, point.coords);
        sw.writeEndElement();
        sw.writeEndElement();
        break;
      case LINESTRING:
        //http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#488
        sw.writeStartElement(strType);
        LineString2D linestring = (LineString2D) geom;
        point = new Point(2);
        sw.writeStartElement("coordinates");
        for (int $i = 0; $i < linestring.getNumPoints(); $i++) {
          linestring.getPointN($i, point);
          writeCoordinates(sw, point.coords);
        }
        sw.writeEndElement();
        sw.writeEndElement();
        break;
      case ENVELOPE:
        // KML does not support envelopes as a separate geometry. So, we write it as a polygon
        sw.writeStartElement(strType);
        Envelope envelope = (Envelope) geom;
        sw.writeStartElement("outerBoundaryIs");
        sw.writeStartElement("LinearRing");
        sw.writeStartElement("coordinates");

        // first point
        sw.writeCharacters(Double.toString(envelope.minCoord[0]) + ",");
        sw.writeCharacters(Double.toString(envelope.minCoord[1]) + " ");

        // second point
        sw.writeCharacters(Double.toString(envelope.maxCoord[0]) + ",");
        sw.writeCharacters(Double.toString(envelope.minCoord[1]) + " ");

        // third point
        sw.writeCharacters(Double.toString(envelope.maxCoord[0]) + ",");
        sw.writeCharacters(Double.toString(envelope.maxCoord[1]) + " ");

        // fourth point
        sw.writeCharacters(Double.toString(envelope.minCoord[0]) + ",");
        sw.writeCharacters(Double.toString(envelope.maxCoord[1]) + " ");

        // fifth point (= first point)
        sw.writeCharacters(Double.toString(envelope.minCoord[0]) + ",");
        sw.writeCharacters(Double.toString(envelope.minCoord[1]) + " ");

        sw.writeEndElement();
        sw.writeEndElement();// End of the linear ring
        sw.writeEndElement();
        sw.writeEndElement();// End of the polygon
        break;
      case POLYGON:
        //http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#505
        sw.writeStartElement(strType);
        Polygon2D polygon = (Polygon2D) geom;
        point = new Point(2);
        for (int $iRing = 0; $iRing < polygon.numRings; $iRing++) {
          // write the rings
          if ($iRing == 0) sw.writeStartElement("outerBoundaryIs");
          else sw.writeStartElement("innerBoundaryIs");
          sw.writeStartElement("LinearRing");
          sw.writeStartElement("coordinates");
          // write the points in this ring
          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(sw, point.coords);
            $iPoint++;
          }
          // Repeat the first point as mandated by the KML format
          polygon.getPointN(polygon.firstPointInRing[$iRing], point);
          writeCoordinates(sw, point.coords);
          sw.writeEndElement();
          sw.writeEndElement();
          sw.writeEndElement();// end of the ring
        }
        sw.writeEndElement();//end of the geometry
        break;
      case MULTIPOINT:
        //http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#438
        sw.writeStartElement("MultiGeometry");
        MultiPoint multiPoint = (MultiPoint) geom;
        point = new Point(2);
        for (int $iPoint = 0; $iPoint < multiPoint.getNumPoints(); $iPoint++) {
          sw.writeStartElement("Point");
          sw.writeStartElement("coordinates");
          multiPoint.getPointN($iPoint, point);
          writeCoordinates(sw, point.coords);
          sw.writeEndElement();
          sw.writeEndElement();
        }
        sw.writeEndElement();
        break;
      case MULTILINESTRING:
        ////http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#438
        sw.writeStartElement("MultiGeometry");
        MultiLineString2D multiLineString = (MultiLineString2D) geom;
        point = new Point(2);
        for (int $iRing = 0; $iRing < multiLineString.numLineStrings; $iRing++) {
          sw.writeStartElement("LineString");
          sw.writeStartElement("coordinates");
          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(sw, point.coords);
            $iPoint++;
          }
          sw.writeEndElement();
          sw.writeEndElement();
        }
        sw.writeEndElement();
        break;
      case MULTIPOLYGON:
        //http://docs.opengeospatial.org/is/12-007r2/12-007r2.html#438
        sw.writeStartElement("MultiGeometry");// Start of the multipolygon
        MultiPolygon2D multiPolygon = (MultiPolygon2D) geom;
        point = new Point(2);
        int $iPoly = 0, $iRing = 0;
        int lastPointInCurrentPolygon = 0, lastPointInCurrentRing = 0;
        for (int $iPoint = 0; $iPoint < multiPolygon.numPoints; $iPoint++) {
          if ($iPoint == multiPolygon.firstPointInPolygon[$iPoly]) {
            sw.writeStartElement("Polygon");// Start of the polygon
            sw.writeStartElement("outerBoundaryIs");
            lastPointInCurrentPolygon = $iPoly == multiPolygon.numPolygons - 1 ?
                multiPolygon.numPoints - 1 : multiPolygon.firstPointInPolygon[$iPoly + 1] - 1;
          }
          if ($iPoint == multiPolygon.firstPointInRing[$iRing]) {
            if ($iPoint != multiPolygon.firstPointInPolygon[$iPoly])
              sw.writeStartElement("innerBoundaryIs");
            sw.writeStartElement("LinearRing");//start of the ring
            sw.writeStartElement("coordinates");
            lastPointInCurrentRing = $iRing == multiPolygon.numRings - 1 ?
                multiPolygon.numPoints - 1 : multiPolygon.firstPointInRing[$iRing + 1] - 1;
          }

          // Write the point
          multiPolygon.getPointN($iPoint, point);
          writeCoordinates(sw, point.coords);
          // Check if last point in ring
          if ($iPoint == lastPointInCurrentRing) {
            // Repeat the first point to conform with the kml format
            multiPolygon.getPointN(multiPolygon.firstPointInRing[$iRing], point);
            writeCoordinates(sw, point.coords);

            // Close the current ring
            sw.writeEndElement();
            sw.writeEndElement();
            sw.writeEndElement();
            $iRing++;
          }
          // Check if last point in polygon
          if ($iPoint == lastPointInCurrentPolygon) {
            sw.writeEndElement();// End of the current polygon
            $iPoly++;
          }
        }
        sw.writeEndElement(); // End of the multipolygon
        break;
      case GEOMETRYCOLLECTION:
        GeometryCollection geometryCollection = (GeometryCollection) geom;
        sw.writeStartElement("MultiGeometry");// Start of the geometry collection
        for (int $iGeom = 0; $iGeom < geometryCollection.numGeometries(); $iGeom++) {
          writeGeometryValue(sw, geometryCollection.getGeometry($iGeom));
        }
        sw.writeEndElement();  //End of the geometry collection
        break;
      default:
        throw new RuntimeException(String.format("Geometry type '%s' is not yet supported in KML", geom.getType()));
    }
  }

  private static void writeCoordinates(XMLStreamWriter sw, double[] coords) throws XMLStreamException {
    sw.writeCharacters(Double.toString(coords[0]) + ",");
    sw.writeCharacters(Double.toString(coords[1]) + " ");
  }

  @Override
  public void close(TaskAttemptContext arg0) throws IOException, InterruptedException {
    try {
      //close the main object
      sw.writeEndDocument();
      //close the XMLStreamWriter and OutputStream
      sw.close();
      out.close();
    } catch (XMLStreamException e2) {
      e2.printStackTrace();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy