import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ArrayNode; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Point; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SpatialReference; import com.esri.core.map.Feature; import com.esri.core.map.Graphic; import com.esri.core.symbol.Symbol; /** * A parser that reads data in <a href="http://geojson.org">GeoJSON</a> format, and returns * a collection of {@link Feature} or {@link Geometry}. * * <p> * To parse a * <a href="http://geojson.org/geojson-spec.html#feature-collection-objects">FeatureCollection</a> * use {@link #parseFeatures(String)}; to parse a * <a href="http://geojson.org/geojson-spec.html#feature-collection-objects">GeometryCollection</a> * use {@link #parseGeometries(String)}. * * <p> * Limitations: * <ul> * <li> No support for the GeoJSON coordinate reference system. All input geometries are * assumed to be in CRS84. * </ul> * @since 10.2.4 */ public final class GeoJsonParser { // symbology to be used for all the features private Symbol symbol = null; // dependency on the Jackson parser library to parse JSON private final ObjectMapper mapper = new ObjectMapper(); // geometries in GeoJSON are assumed to be in CRS84 (Esri Wkid = 4326) private final SpatialReference inSR = SpatialReference.create(4326); // output CRS can be configured to be different private SpatialReference outSR = null; // field names defined in the GeoJson spec private final static String FIELD_COORDINATES = "coordinates"; private final static String FIELD_FEATURE = "Feature"; private final static String FIELD_FEATURES = "features"; private final static String FIELD_FEATURE_COLLECTION = "FeatureCollection"; private final static String FIELD_GEOMETRY = "geometry"; private final static String FIELD_GEOMETRIES = "geometries"; private final static String FIELD_GEOMETRY_COLLECTION = "GeometryCollection"; private final static String FIELD_PROPERTIES = "properties"; private final static String FIELD_TYPE = "type"; private enum GeometryType { POINT("Point"), MULTI_POINT("MultiPoint"), LINE_STRING("LineString"), MULTI_LINE_STRING("MultiLineString"), POLYGON("Polygon"), MULTI_POLYGON("MultiPolygon"); private final String val; GeometryType(String val) { this.val = val; } public static GeometryType fromString(String val) { for (GeometryType type : GeometryType.values()) { if (type.val.equals(val)) { return type; } } return null; } } // ------------------------------------------------------------------------ // Public methods // ------------------------------------------------------------------------ public GeoJsonParser setSymbol(Symbol symbol) { this.symbol = symbol; return this; } public GeoJsonParser setOutSpatialReference(SpatialReference outSR) { this.outSR = outSR; return this; } public List<Feature> parseFeatures(File file) { try { JsonParser parser = new JsonFactory().createJsonParser(file); return parseFeatures(parser); } catch (Exception ex) { throw new RuntimeException(ex); } } public List<Feature> parseFeatures(String str) { try { JsonParser parser = new JsonFactory().createJsonParser(str); return parseFeatures(parser); } catch (Exception ex) { throw new RuntimeException(ex); } } public List<Geometry> parseGeometries(File file) { try { JsonParser parser = new JsonFactory().createJsonParser(file); return parseGeometries(parser); } catch (Exception ex) { throw new RuntimeException(ex); } } public List<Geometry> parseGeometries(String str) { try { JsonParser parser = new JsonFactory().createJsonParser(str); return parseGeometries(parser); } catch (Exception ex) { throw new RuntimeException(ex); } } // ------------------------------------------------------------------------ // Private methods // ------------------------------------------------------------------------ private List<Feature> parseFeatures(JsonParser parser) { try { JsonNode node = mapper.readTree(parser); String type = node.path(FIELD_TYPE).getTextValue(); if (type.equals(FIELD_FEATURE_COLLECTION)) { ArrayNode jsonFeatures = (ArrayNode) node.path(FIELD_FEATURES); return parseFeatures(jsonFeatures); } else if (type.equals(FIELD_GEOMETRY_COLLECTION)) { ArrayNode jsonFeatures = (ArrayNode) node.path(FIELD_GEOMETRIES); List<Geometry> geometries = parseGeometries(jsonFeatures); List<Feature> features = new LinkedList<Feature>(); for (Geometry g : geometries) { features.add(new Graphic(g, symbol)); } return features; } } catch (Exception ex) { throw new RuntimeException(ex); } return Collections.emptyList(); } private List<Feature> parseFeatures(ArrayNode jsonFeatures) { List<Feature> features = new LinkedList<Feature>(); for (JsonNode jsonFeature : jsonFeatures) { String type = jsonFeature.path(FIELD_TYPE).getTextValue(); if (!FIELD_FEATURE.equals(type)) { continue; } Geometry g = parseGeometry(jsonFeature.path(FIELD_GEOMETRY)); if (outSR != null && outSR.getID() != 4326) { g = GeometryEngine.project(g, inSR, outSR); } Map<String, Object> attributes = parseProperties(jsonFeature.path(FIELD_PROPERTIES)); Feature f = new Graphic(g, symbol, attributes); features.add(f); } return features; } private List<Geometry> parseGeometries(JsonParser parser) { try { JsonNode node = mapper.readTree(parser); String type = node.path(FIELD_TYPE).getTextValue(); if (type.equals(FIELD_GEOMETRY_COLLECTION)) { ArrayNode jsonFeatures = (ArrayNode) node.path(FIELD_GEOMETRIES); return parseGeometries(jsonFeatures); } } catch (Exception ex) { throw new RuntimeException(ex); } return Collections.emptyList(); } private List<Geometry> parseGeometries(ArrayNode jsonGeometries) { List<Geometry> geometries = new LinkedList<Geometry>(); for (JsonNode jsonGeometry : jsonGeometries) { Geometry g = parseGeometry(jsonGeometry); if (outSR != null && outSR.getID() != 4326) { g = GeometryEngine.project(g, inSR, outSR); } geometries.add(g); } return geometries; } private Map<String, Object> parseProperties(JsonNode node) { Map<String, Object> properties = new HashMap<String, Object>(); Iterator<Map.Entry<String, JsonNode>> propertyInterator = node.getFields(); while (propertyInterator.hasNext()) { Map.Entry<String, JsonNode> property = propertyInterator.next(); JsonNode jsonValue = property.getValue(); if (jsonValue.isInt()) { properties.put(property.getKey(), property.getValue().asInt()); } else if (jsonValue.isDouble()) { properties.put(property.getKey(), property.getValue().asDouble()); } else if (jsonValue.isTextual()) { properties.put(property.getKey(), property.getValue().asText()); } } return properties; } /** * { "type": "Point", "coordinates": [100.0, 0.0] } * @param parser * @param str * @return a geometry. * @throws IOException * @throws JsonParseException */ private Geometry parseGeometry(JsonNode node) { GeometryType type = GeometryType.fromString(node.path(FIELD_TYPE).getTextValue()); return parseCoordinates(type, node.path(FIELD_COORDINATES)); } private Geometry parseCoordinates(GeometryType type, JsonNode node) { Geometry g = null; switch (type) { default: case POINT: g = parsePointCoordinates(node); break; case MULTI_POINT: g = parseMultiPointCoordinates(node); break; case LINE_STRING: g = parseLineStringCoordinates(node); break; case MULTI_LINE_STRING: g = parseMultiLineStringCoordinates(node); break; case POLYGON: g = parsePolygonCoordinates(node); break; case MULTI_POLYGON: g = parseMultiPolygonCoordinates(node); break; } return g; } /** * Parses a point * Example: * [101.0, 0.0]. * @param parser * @return a point. * @throws Exception */ private Point parsePointCoordinates(JsonNode node) { Point p = new Point(); p.setXY(node.get(0).asDouble(), node.get(1).asDouble()); if (node.size() == 3) { p.setZ(node.get(2).asDouble()); } return p; } /** * Parses a multipoint * Example: * [ [100.0, 0.0], [101.0, 1.0] ]. * @param parser * @return a multipoint. * @throws Exception */ private MultiPoint parseMultiPointCoordinates(JsonNode node) { MultiPoint p = new MultiPoint(); ArrayNode jsonPoints = (ArrayNode) node; for (JsonNode jsonPoint : jsonPoints) { Point point = parsePointCoordinates(jsonPoint); p.add(point); } return p; } /** * Parses a line string * Example: * [ [100.0, 0.0], [101.0, 1.0] ]. * @param parser * @return a polyline. * @throws Exception */ private Polyline parseLineStringCoordinates(JsonNode node) { Polyline g = new Polyline(); boolean first = true; ArrayNode points = (ArrayNode) node; for (JsonNode point : points) { Point p = parsePointCoordinates(point); if (first) { g.startPath(p); first = false; } else { g.lineTo(p); } } return g; } /** * Parses a multi line string * Example: * [ * [ [100.0, 0.0], [101.0, 1.0] ], * [ [102.0, 2.0], [103.0, 3.0] ] * ] * @param parser * @return a polyline * @throws Exception */ private Polyline parseMultiLineStringCoordinates(JsonNode node) { Polyline g = new Polyline(); ArrayNode jsonLines = (ArrayNode) node; for (JsonNode jsonLine : jsonLines) { Polyline line = parseLineStringCoordinates(jsonLine); g.add(line, false); } return g; } /** * Example: * [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] * @param parser * @return a polygon * @throws JsonParseException * @throws IOException */ private Polygon parseSimplePolygonCoordinates(JsonNode node) { Polygon g = new Polygon(); boolean first = true; ArrayNode points = (ArrayNode) node; for (JsonNode point : points) { Point p = parsePointCoordinates(point); if (first) { g.startPath(p); first = false; } else { g.lineTo(p); } } g.closeAllPaths(); return g; } /** * Parses a polygon string * Example: * without holes: * [ * [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] * ] * * with holes: * [ * [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ], * [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] * ] * @param parser * @return a polygon * @throws Exception */ private Polygon parsePolygonCoordinates(JsonNode node) { Polygon g = new Polygon(); ArrayNode jsonPolygons = (ArrayNode) node; for (JsonNode jsonPolygon : jsonPolygons) { Polygon simplePolygon = parseSimplePolygonCoordinates(jsonPolygon); g.add(simplePolygon, false); } return g; } /** * Parses a multi polygon string * Example: * [ * [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], * [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], * [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] * ] * @param parser * @return a polygon * @throws Exception */ private Polygon parseMultiPolygonCoordinates(JsonNode node) { Polygon g = new Polygon(); ArrayNode jsonPolygons = (ArrayNode) node; for (JsonNode jsonPolygon : jsonPolygons) { Polygon simplePolygon = parsePolygonCoordinates(jsonPolygon); g.add(simplePolygon, false); } return g; } }