package com.revolsys.record.io.format.esri.rest.map; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.function.BiFunction; import com.revolsys.collection.map.MapEx; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; 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.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.io.BaseCloseable; import com.revolsys.io.FileUtil; import com.revolsys.logging.Logs; import com.revolsys.net.urlcache.FileResponseCache; import com.revolsys.record.Record; import com.revolsys.record.RecordFactory; import com.revolsys.record.RecordState; import com.revolsys.record.io.format.csv.AbstractRecordReader; import com.revolsys.record.io.format.json.JsonParser; import com.revolsys.record.io.format.json.JsonParser.EventType; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.spring.resource.Resource; import com.revolsys.util.Exceptions; import com.revolsys.util.Property; public class ArcGisRestServerFeatureReader extends AbstractRecordReader { private static Map<DataType, BiFunction<GeometryFactory, MapEx, Geometry>> GEOMETRY_CONVERTER_BY_TYPE = new HashMap<>(); static { GEOMETRY_CONVERTER_BY_TYPE.put(DataTypes.POINT, ArcGisRestServerFeatureReader::parsePoint); GEOMETRY_CONVERTER_BY_TYPE.put(DataTypes.MULTI_POINT, ArcGisRestServerFeatureReader::parseMultiPoint); GEOMETRY_CONVERTER_BY_TYPE.put(DataTypes.MULTI_LINE_STRING, ArcGisRestServerFeatureReader::parseMultiLineString); GEOMETRY_CONVERTER_BY_TYPE.put(DataTypes.MULTI_POLYGON, ArcGisRestServerFeatureReader::parseMultiPolygon); } public static Geometry parseMultiLineString(final GeometryFactory geometryFactory, final MapEx properties) { final List<LineString> lines = new ArrayList<>(); final List<List<List<Number>>> paths = properties.getValue("paths", Collections.emptyList()); for (final List<List<Number>> points : paths) { final LineString lineString = geometryFactory.lineString(points); lines.add(lineString); } return geometryFactory.geometry(lines); } public static Geometry parseMultiPoint(final GeometryFactory geometryFactory, final MapEx properties) { final List<Point> lines = new ArrayList<>(); final List<List<Number>> paths = properties.getValue("paths", Collections.emptyList()); for (final List<Number> pointCoordinates : paths) { final Point point = geometryFactory.point(pointCoordinates); lines.add(point); } return geometryFactory.geometry(lines); } public static Geometry parseMultiPolygon(final GeometryFactory geometryFactory, final MapEx properties) { final List<Polygon> polygons = new ArrayList<>(); final List<LinearRing> rings = new ArrayList<>(); final List<List<List<Number>>> paths = properties.getValue("rings", Collections.emptyList()); for (final List<List<Number>> points : paths) { final LinearRing ring = geometryFactory.linearRing(points); if (ring.isClockwise()) { if (!rings.isEmpty()) { final Polygon polygon = geometryFactory.polygon(rings); polygons.add(polygon); } rings.clear(); } rings.add(ring); } if (!rings.isEmpty()) { final Polygon polygon = geometryFactory.polygon(rings); polygons.add(polygon); } return geometryFactory.geometry(polygons); } public static Geometry parsePoint(final GeometryFactory geometryFactory, final MapEx properties) { final double x = Maps.getDouble(properties, "x"); final double y = Maps.getDouble(properties, "y"); final double z = Maps.getDouble(properties, "z", Double.NaN); final double m = Maps.getDouble(properties, "m", Double.NaN); if (Double.isNaN(m)) { if (Double.isNaN(z)) { return geometryFactory.point(x, y); } else { return geometryFactory.point(x, y, z); } } else { return geometryFactory.point(x, y, z, m); } } private JsonParser parser; private boolean closed; private RecordDefinition recordDefinition; private RecordFactory<?> recordFacory; private BiFunction<GeometryFactory, MapEx, Geometry> geometryConverter; private GeometryFactory geometryFactory; private int recordCount = 0; private int pageRecordCount = 0; private Map<String, Object> queryParameters; private final int queryOffset; private final int queryLimit; private int pageSize; private final boolean supportsPaging; private final FeatureLayer layer; private Resource resource; private final String where; private final boolean pageByObjectId; private int totalRecordCount; private int currentRecordId = 0; private final String idFieldName; public ArcGisRestServerFeatureReader(final FeatureLayer layer, final Map<String, Object> queryParameters, final int offset, final int limit, final RecordFactory<?> recordFactory, final boolean pageByObjectId) { super(recordFactory); this.layer = layer; this.queryParameters = queryParameters; this.where = (String)queryParameters.get("where"); this.queryOffset = offset; this.queryLimit = limit; this.pageSize = layer.getMaxRecordCount(); if (this.pageSize > 1000) { this.pageSize = 1000; } if (this.queryLimit < this.pageSize) { this.pageSize = this.queryLimit; } this.recordDefinition = layer.getRecordDefinition(); this.recordFacory = recordFactory; if (this.recordDefinition.hasGeometryField()) { final DataType geometryType = this.recordDefinition.getGeometryField().getDataType(); this.geometryConverter = GEOMETRY_CONVERTER_BY_TYPE.get(geometryType); this.geometryFactory = this.recordDefinition.getGeometryFactory(); if (this.geometryConverter == null) { Logs.error(this, "Unsupported geometry type " + geometryType); throw new IllegalArgumentException("Unsupported geometry type " + geometryType); } } if (!pageByObjectId && layer.getCurrentVersion() >= 10.3 && layer.isSupportsPagination()) { this.supportsPaging = true; this.pageByObjectId = false; } else { final Map<String, Object> countParameters = new LinkedHashMap<>(queryParameters); this.totalRecordCount = layer.getRecordCount(countParameters, queryParameters); this.supportsPaging = false; this.pageByObjectId = true; } this.idFieldName = getIdFieldName(); } @Override protected void closeDo() { FileUtil.closeSilent(this.parser); this.parser = null; this.geometryConverter = null; this.geometryFactory = null; this.queryParameters = null; this.recordDefinition = null; this.recordFacory = null; } @Override protected void finalize() throws Throwable { close(); } @SuppressWarnings("resource") @Override protected Record getNext() throws NoSuchElementException { int previousRecordOffset = this.currentRecordId; final int maxRetries = 3; for (int retry = 0; retry < maxRetries; retry++) { if (this.closed) { throw new NoSuchElementException(); } else { JsonParser parser = this.parser; if (this.recordCount < this.queryLimit) { if (parser == null) { parser = newParser(); } if (!parser.skipToNextObjectInArray()) { if (this.pageByObjectId) { parser = newParser(); if (!parser.skipToNextObjectInArray()) { throw new NoSuchElementException(); } } else if (this.supportsPaging) { if (this.pageRecordCount == this.pageSize) { parser = newParser(); if (!parser.skipToNextObjectInArray()) { throw new NoSuchElementException(); } } } else { throw new NoSuchElementException(); } } } else { throw new NoSuchElementException(); } if (this.closed) { throw new NoSuchElementException(); } else { try { if (parser.isEvent(EventType.endArray, EventType.endDocument)) { throw new NoSuchElementException(); } final MapEx recordMap = this.parser.getMap(); final Record record = this.recordFacory.newRecord(this.recordDefinition); record.setState(RecordState.INITIALIZING); final MapEx fieldValues = recordMap.getValue("attributes"); final int recordId = fieldValues.getInteger(this.idFieldName, -1); this.currentRecordId = recordId; if (this.pageByObjectId) { if (this.currentRecordId == -1) { throw new NoSuchElementException(); } } record.setValues(fieldValues); if (this.geometryConverter != null) { final MapEx geometryProperties = recordMap.getValue("geometry"); if (Property.hasValue(geometryProperties)) { final Geometry geometry = this.geometryConverter.apply(this.geometryFactory, geometryProperties); record.setGeometryValue(geometry); } } if (parser.hasNext()) { final EventType nextEvent = parser.next(); if (nextEvent == EventType.endArray || nextEvent == EventType.endDocument) { this.parser = null; } } else { this.parser = null; } record.setState(RecordState.PERSISTED); this.pageRecordCount++; this.recordCount++; return record; } catch (final NoSuchElementException e) { throw e; } catch (final Throwable e) { if (retry + 1 == maxRetries) { throw new RuntimeException("Unable to read: " + getPathName(), e); } if (this.pageByObjectId) { if (this.currentRecordId == previousRecordOffset) { if (retry > 1) { Logs.error(this, "Unable to read record: " + getPathName() + " " + this.idFieldName + "=" + (this.currentRecordId + 1)); this.currentRecordId++; previousRecordOffset = this.currentRecordId; } } else { Logs.error(this, "Unable to read record: " + getPathName() + " " + this.idFieldName + "=" + (this.currentRecordId + 1)); this.currentRecordId++; } } else { close(); Exceptions.throwUncheckedException(e); } } } } } throw new RuntimeException("Unable to read: " + getPathName()); } @Override public ClockDirection getPolygonRingDirection() { return ClockDirection.CLOCKWISE; } @Override public RecordDefinition getRecordDefinition() { return this.recordDefinition; } protected JsonParser newParser() { if (this.closed) { throw new NoSuchElementException(); } else if (this.pageByObjectId && this.totalRecordCount == 0) { throw new NoSuchElementException(); } else { this.pageRecordCount = 0; if (this.pageByObjectId) { String where; if (this.where == null || this.where.equals(this.idFieldName + " > 0")) { where = this.idFieldName + " > " + this.currentRecordId; } else { where = "(" + this.where + ") AND " + this.idFieldName + " > " + this.currentRecordId; } this.queryParameters.put("where", where); this.queryParameters.put("orderByFields", this.idFieldName); } else if (this.supportsPaging) { this.queryParameters.put("resultOffset", this.queryOffset + this.recordCount); if (this.pageSize > 0) { this.queryParameters.put("resultRecordCount", this.pageSize); } } this.resource = this.layer.getResource("query", this.queryParameters); try ( BaseCloseable noCache = FileResponseCache.disable()) { this.parser = new JsonParser(this.resource); } if (!this.parser.skipToAttribute("features")) { throw new NoSuchElementException(); } return this.parser; } } }