001package ca.bc.webarts.tools;
002
003import java.io.StringWriter;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007
008import javax.xml.stream.XMLOutputFactory;
009import javax.xml.stream.XMLStreamException;
010import javax.xml.stream.XMLStreamWriter;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015import com.google.gson.JsonArray;
016import com.google.gson.JsonElement;
017import com.google.gson.JsonObject;
018
019/**
020 * A minimal converter of GeoJSON to KML (as used in openLCA).
021 *
022 * @see https://developers.google.com/kml/documentation/kmlreference
023 * @see http://geojson.org/geojson-spec.html
024 */
025public class GeoJson2Kml {
026
027  private XMLStreamWriter kml;
028
029  private int indent = 0;
030
031  private GeoJson2Kml(XMLStreamWriter kml) {
032    this.kml = kml;
033  }
034
035  public static String convert(JsonObject geoJson) {
036    if (geoJson == null)
037      return null;
038    XMLOutputFactory fac = XMLOutputFactory.newInstance();
039    try (StringWriter writer = new StringWriter()) {
040      XMLStreamWriter kml = fac.createXMLStreamWriter(writer);
041      new GeoJson2Kml(kml).doIt(geoJson);
042      kml.flush();
043      writer.flush();
044      kml.close();
045      return writer.toString();
046    } catch (Exception e) {
047      Logger log = LoggerFactory.getLogger(GeoJson2Kml.class);
048      log.error("failed to convert GeoJSON", e);
049      return null;
050    }
051  }
052
053  private void doIt(JsonObject geoJson) throws Exception {
054    kml.writeStartDocument("utf-8", "1.0");
055    kml.setDefaultNamespace("http://earth.google.com/kml/2.0");
056    startElem("kml");
057    kml.writeNamespace("", "http://earth.google.com/kml/2.0");
058    startElems("Folder", "Placemark");
059    writeGeometry(geoJson);
060    endElems(3);
061    kml.writeEndDocument();
062  }
063
064  private void writeGeometry(JsonObject geoJson) throws Exception {
065    JsonElement typeElem = geoJson.get("type");
066    if (typeElem == null || !typeElem.isJsonPrimitive())
067      return;
068    String type = typeElem.getAsString();
069    switch (type) {
070    case "Point":
071      writePoint(geoJson);
072      break;
073    case "LineString":
074      writeLineString(geoJson);
075      break;
076    case "Polygon":
077      writePolygon(geoJson);
078      break;
079    case "GeometryCollection":
080      writeGeometryCollection(geoJson);
081      break;
082    }
083
084  }
085
086  private void writePoint(JsonObject geoJson) throws Exception {
087    String coordinate = getCoordinate(geoJson.get("coordinates"));
088    if (coordinate == null)
089      return;
090    startElems("Point", "coordinates");
091    writeCoordinate(coordinate);
092    endElem();
093    endElem();
094  }
095
096  private void writeLineString(JsonObject geoJson) throws Exception {
097    JsonElement elem = geoJson.get("coordinates");
098    List<String> coordinates = getCoordinates(elem);
099    if (coordinates.isEmpty())
100      return;
101    startElems("LineString", "coordinates");
102    for (String coordinate : coordinates)
103      writeCoordinate(coordinate);
104    endElems(2);
105  }
106
107  private void writePolygon(JsonObject geoJson) throws Exception {
108    JsonElement elem = geoJson.get("coordinates");
109    if (elem == null || !elem.isJsonArray())
110      return;
111    JsonArray array = elem.getAsJsonArray();
112    if (array.size() == 0)
113      return;
114    List<String> outerRing = getCoordinates(array.get(0));
115    if (outerRing.isEmpty())
116      return;
117    startElem("Polygon");
118    startElems("outerBoundaryIs", "LinearRing", "coordinates");
119    for (String coordinate : outerRing)
120      writeCoordinate(coordinate);
121    endElems(3);
122    if (array.size() > 1) {
123      List<String> innerRing = getCoordinates(array.get(1));
124      startElems("innerBoundaryIs", "LinearRing", "coordinates");
125      for (String coordinate : innerRing)
126        writeCoordinate(coordinate);
127      endElems(3);
128    }
129    endElem();
130  }
131
132  private void writeGeometryCollection(JsonObject geoJson) throws Exception {
133    JsonElement elem = geoJson.get("geometries");
134    if (elem == null || !elem.isJsonArray())
135      return;
136    startElem("MultiGeometry");
137    for (JsonElement geom : elem.getAsJsonArray()) {
138      if (!geom.isJsonObject())
139        continue;
140      writeGeometry(geom.getAsJsonObject());
141    }
142    endElem();
143  }
144
145  private void writeCoordinate(String coordinate) throws XMLStreamException {
146    kml.writeCharacters("\n");
147    for (int i = 0; i < indent; i++)
148      kml.writeCharacters("  ");
149    kml.writeCharacters(coordinate);
150  }
151
152  private List<String> getCoordinates(JsonElement elem) {
153    if (elem == null || !elem.isJsonArray())
154      return Collections.emptyList();
155    JsonArray array = elem.getAsJsonArray();
156    List<String> coordinates = new ArrayList<>();
157    for (JsonElement ce : array) {
158      String coordinate = getCoordinate(ce);
159      if (coordinate == null)
160        return Collections.emptyList();
161      coordinates.add(coordinate);
162    }
163    return coordinates;
164  }
165
166  private String getCoordinate(JsonElement elem) {
167    if (elem == null || !elem.isJsonArray())
168      return null;
169    JsonArray point = elem.getAsJsonArray();
170    StringBuilder s = new StringBuilder();
171    boolean first = true;
172    for (JsonElement num : point) {
173      if (!num.isJsonPrimitive())
174        return null;
175      if (!first)
176        s.append(',');
177      else
178        first = false;
179      double d = num.getAsDouble();
180      s.append(d);
181    }
182    return s.toString();
183  }
184
185  private void startElems(String... names) throws Exception {
186    for (String name : names)
187      startElem(name);
188  }
189
190  private void startElem(String name) throws Exception {
191    kml.writeCharacters("\n");
192    for (int i = 0; i < indent; i++)
193      kml.writeCharacters("  ");
194    kml.writeStartElement(name);
195    indent++;
196  }
197
198  private void endElems(int count) throws Exception {
199    for (int i = 0; i < count; i++)
200      endElem();
201  }
202
203  private void endElem() throws Exception {
204    kml.writeCharacters("\n");
205    indent--;
206    for (int i = 0; i < indent; i++)
207      kml.writeCharacters("  ");
208    kml.writeEndElement();
209  }
210
211}