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