package com.revolsys.record.io.format.geojson;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.revolsys.collection.iterator.AbstractIterator;
import com.revolsys.geometry.io.GeometryReader;
import com.revolsys.geometry.model.ClockDirection;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.impl.LineStringDouble;
import com.revolsys.io.FileUtil;
import com.revolsys.io.IoConstants;
import com.revolsys.record.io.format.cogojson.CogoJson;
import com.revolsys.record.io.format.json.JsonParser;
import com.revolsys.record.io.format.json.JsonParser.EventType;
import com.revolsys.spring.resource.Resource;
public class GeoJsonGeometryReader extends AbstractIterator<Geometry> implements GeometryReader {
private GeometryFactory geometryFactory;
private JsonParser in;
public GeoJsonGeometryReader(final Resource resource) {
this.in = new JsonParser(resource);
}
@Override
protected void closeDo() {
FileUtil.closeSilent(this.in);
this.geometryFactory = null;
this.in = null;
}
@Override
protected void finalize() throws Throwable {
close();
}
@Override
public GeometryFactory getGeometryFactory() {
return this.geometryFactory;
}
@Override
protected Geometry getNext() throws NoSuchElementException {
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToAttribute();
if (GeoJson.TYPE.equals(fieldName)) {
this.in.next();
final String geometryType = this.in.getCurrentValue();
if (GeoJson.GEOMETRY_TYPE_NAMES.contains(geometryType)) {
return readGeometry();
}
} else if (GeoJson.CRS.equals(fieldName)) {
this.in.next();
this.geometryFactory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endDocument);
throw new NoSuchElementException();
}
@Override
public ClockDirection getPolygonRingDirection() {
return ClockDirection.COUNTER_CLOCKWISE;
}
@Override
protected void initDo() {
this.geometryFactory = getProperty(IoConstants.GEOMETRY_FACTORY);
if (this.geometryFactory == null) {
this.geometryFactory = GeometryFactory.floating3(4326);
}
if (this.in.hasNext()) {
this.in.next();
}
}
private LineString readCoordinatesList(final boolean cogo, final boolean ring) {
final List<Double> coordinates = new ArrayList<>();
final int axisCount = readCoordinatesList(coordinates);
if (cogo) {
final int vertexCount = coordinates.size() / axisCount;
if (vertexCount > 0) {
final double firstX = coordinates.get(0);
final double firstY = coordinates.get(1);
double previousX = firstX;
double previousY = firstY;
for (int i = 1; i < vertexCount; i++) {
final double distance = coordinates.get(i * axisCount);
final double angleDegrees = coordinates.get(i * axisCount + 1);
final double angle = Math.toRadians((450 - angleDegrees) % 360);
final double x = previousX + distance * Math.cos(angle);
final double y = previousY + distance * Math.sin(angle);
coordinates.set(i * axisCount, x);
coordinates.set(i * axisCount + 1, y);
previousX = x;
previousY = y;
}
if (ring) {
coordinates.set((vertexCount - 1) * axisCount, firstX);
coordinates.set((vertexCount - 1) * axisCount + 1, firstY);
}
}
}
return new LineStringDouble(axisCount, coordinates);
}
private int readCoordinatesList(final List<Double> coordinates) {
int axisCount = 0;
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.next();
if (event != EventType.endArray) {
do {
axisCount = Math.max(axisCount, readCoordinatesListCoordinates(coordinates));
event = this.in.next();
} while (event == EventType.comma);
}
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
return axisCount;
}
/**
* Read one points coordinates and add them to the list of coordinate values.
*
* @param values The list to add the points coordinates to.
* @return The dimension of the coordinate read.
*/
private int readCoordinatesListCoordinates(final List<Double> values) {
int numAxis = 0;
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.getEvent();
do {
final JsonParser parser = this.in;
final Object value = parser.getValue();
if (value instanceof EventType) {
event = (EventType)value;
} else if (value instanceof Number) {
values.add(((Number)value).doubleValue());
numAxis++;
event = this.in.next();
} else {
throw new IllegalArgumentException("Expecting number, not: " + value);
}
} while (event == EventType.comma);
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return numAxis;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
}
private List<LineString> readCoordinatesListList(final boolean cogo, final boolean ring) {
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.next();
final List<LineString> coordinatesLists = new ArrayList<>();
if (event != EventType.endArray) {
do {
coordinatesLists.add(readCoordinatesList(cogo, ring));
event = this.in.next();
} while (event == EventType.comma);
}
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return coordinatesLists;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
}
private List<List<LineString>> readCoordinatesListListList(final boolean cogo) {
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.next();
final List<List<LineString>> coordinatesLists = new ArrayList<>();
if (event != EventType.endArray) {
do {
coordinatesLists.add(readCoordinatesListList(cogo, true));
event = this.in.next();
} while (event == EventType.comma);
}
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return coordinatesLists;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
}
private GeometryFactory readCoordinateSystem() {
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.PROPERTIES.equals(fieldName)) {
final JsonParser parser1 = this.in;
final Map<String, Object> properties = parser1.getMap();
final String name = (String)properties.get("name");
if (name != null) {
if (name.startsWith(GeoJson.URN_OGC_DEF_CRS_EPSG)) {
final int srid = Integer
.parseInt(name.substring(GeoJson.URN_OGC_DEF_CRS_EPSG.length()));
factory = GeometryFactory.floating3(srid);
} else if (name.startsWith(GeoJson.EPSG)) {
final int srid = Integer.parseInt(name.substring(GeoJson.EPSG.length()));
factory = GeometryFactory.floating3(srid);
}
}
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
return factory;
}
private Geometry readGeometry() {
final String geometryType = this.in.getCurrentValue();
if (geometryType.equals(GeoJson.POINT)) {
return readPoint();
} else if (geometryType.equals(GeoJson.LINE_STRING)) {
return readLineString(false);
} else if (geometryType.equals(GeoJson.POLYGON)) {
return readPolygon(false);
} else if (geometryType.equals(GeoJson.MULTI_POINT)) {
return readMultiPoint();
} else if (geometryType.equals(GeoJson.MULTI_LINE_STRING)) {
return readMultiLineString(false);
} else if (geometryType.equals(GeoJson.MULTI_POLYGON)) {
return readMultiPolygon(false);
} else if (geometryType.equals(GeoJson.GEOMETRY_COLLECTION)) {
return readGeometryCollection();
} else if (geometryType.equals(CogoJson.COGO_LINE_STRING)) {
return readLineString(true);
} else if (geometryType.equals(CogoJson.COGO_POLYGON)) {
return readPolygon(true);
} else if (geometryType.equals(CogoJson.COGO_MULTI_LINE_STRING)) {
return readMultiLineString(true);
} else if (geometryType.equals(CogoJson.COGO_MULTI_POLYGON)) {
return readMultiPolygon(true);
} else {
return null;
}
}
private Geometry readGeometryCollection() {
List<Geometry> geometries = new ArrayList<>();
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.GEOMETRIES.equals(fieldName)) {
geometries = readGeometryList();
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
return factory.geometry(geometries);
}
private List<Geometry> readGeometryList() {
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.next();
final List<Geometry> geometries = new ArrayList<>();
if (event != EventType.endArray) {
do {
final Geometry geometry = getNext();
geometries.add(geometry);
event = this.in.next();
} while (event == EventType.comma);
}
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return geometries;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
}
private LineString readLineString(final boolean cogo) {
LineString points = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
points = readCoordinatesList(cogo, false);
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
if (points == null) {
return factory.lineString();
} else {
final int axisCount = points.getAxisCount();
final GeometryFactory geometryFactory = factory.convertAxisCount(axisCount);
return geometryFactory.lineString(points);
}
}
private Geometry readMultiLineString(final boolean cogo) {
List<LineString> lineStrings = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
lineStrings = readCoordinatesListList(cogo, false);
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
int axisCount = 2;
for (final LineString points : lineStrings) {
axisCount = Math.max(axisCount, points.getAxisCount());
}
factory = factory.convertAxisCount(axisCount);
return factory.lineal(lineStrings);
}
private Geometry readMultiPoint() {
List<LineString> pointsList = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
pointsList = readPointCoordinatesListList();
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
int axisCount = 2;
for (final LineString points : pointsList) {
axisCount = Math.max(axisCount, points.getAxisCount());
}
factory = factory.convertAxisCount(axisCount);
return factory.punctual(pointsList);
}
private Geometry readMultiPolygon(final boolean cogo) {
final List<Polygon> polygons = new ArrayList<>();
List<List<LineString>> polygonRings = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
polygonRings = readCoordinatesListListList(cogo);
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
int axisCount = 2;
if (polygonRings != null) {
for (final List<LineString> rings : polygonRings) {
for (final LineString points : rings) {
axisCount = Math.max(axisCount, points.getAxisCount());
}
factory = factory.convertAxisCount(axisCount);
final Polygon polygon = factory.polygon(rings);
polygons.add(polygon);
}
}
return factory.polygonal(polygons);
}
private Point readPoint() {
LineString coordinates = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
coordinates = readPointCoordinatesList();
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
if (coordinates == null) {
return factory.point();
} else {
final int axisCount = coordinates.getAxisCount();
final GeometryFactory geometryFactory = factory.convertAxisCount(axisCount);
return geometryFactory.point(coordinates);
}
}
private LineString readPointCoordinatesList() {
final JsonParser parser = this.in;
final double[] values = parser.getDoubleArray();
if (values == null) {
return null;
} else {
return new LineStringDouble(values.length, values);
}
}
private List<LineString> readPointCoordinatesListList() {
if (this.in.getEvent() == EventType.startArray
|| this.in.hasNext() && this.in.next() == EventType.startArray) {
EventType event = this.in.next();
final List<LineString> coordinatesLists = new ArrayList<>();
if (event != EventType.endArray) {
do {
coordinatesLists.add(readPointCoordinatesList());
event = this.in.next();
} while (event == EventType.comma);
}
if (event != EventType.endArray) {
throw new IllegalStateException("Exepecting end array, not: " + event);
}
return coordinatesLists;
} else {
throw new IllegalStateException("Exepecting start array, not: " + this.in.getEvent());
}
}
private Polygon readPolygon(final boolean cogo) {
List<LineString> rings = null;
GeometryFactory factory = this.geometryFactory;
do {
final JsonParser parser = this.in;
final String fieldName = parser.skipToNextAttribute();
if (GeoJson.COORDINATES.equals(fieldName)) {
rings = readCoordinatesListList(cogo, true);
} else if (GeoJson.CRS.equals(fieldName)) {
factory = readCoordinateSystem();
}
} while (this.in.getEvent() != EventType.endObject
&& this.in.getEvent() != EventType.endDocument);
int axisCount = 2;
for (final LineString points : rings) {
axisCount = Math.max(axisCount, points.getAxisCount());
}
factory = factory.convertAxisCount(axisCount);
return factory.polygon(rings);
}
}