package com.revolsys.record.io.format.esri.rest.map; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.revolsys.collection.map.MapEx; import com.revolsys.datatype.DataType; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.ClockDirection; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.io.PathName; import com.revolsys.jdbc.JdbcUtils; import com.revolsys.logging.Logs; import com.revolsys.record.ArrayRecord; import com.revolsys.record.Record; import com.revolsys.record.RecordFactory; import com.revolsys.record.code.SimpleCodeTable; import com.revolsys.record.io.RecordReader; import com.revolsys.record.io.format.esri.gdb.xml.model.enums.FieldType; import com.revolsys.record.io.format.esri.gdb.xml.model.enums.GeometryType; import com.revolsys.record.io.format.esri.rest.ArcGisResponse; import com.revolsys.record.io.format.esri.rest.ArcGisRestCatalog; import com.revolsys.record.io.format.esri.rest.CatalogElement; import com.revolsys.record.io.format.json.Json; import com.revolsys.record.query.Condition; import com.revolsys.record.query.Query; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordDefinitionImpl; import com.revolsys.spring.resource.Resource; import com.revolsys.spring.resource.UrlResource; import com.revolsys.util.Property; import com.revolsys.webservice.WebServiceFeatureLayer; public class FeatureLayer extends LayerDescription implements WebServiceFeatureLayer { public static FeatureLayer getRecordLayerDescription(final String layerUrl) { return new FeatureLayer(layerUrl); } public static FeatureLayer getRecordLayerDescription(final String serverUrl, final PathName pathName) { final ArcGisRestCatalog catalog = new ArcGisRestCatalog(serverUrl); return catalog.getWebServiceResource(pathName, FeatureLayer.class); } public static FeatureLayer getRecordLayerDescription(final String serverUrl, final String path) { final PathName pathName = PathName.newPathName(path); return getRecordLayerDescription(serverUrl, pathName); } private RecordDefinition recordDefinition; private BoundingBox boundingBox; private boolean supportsPagination; protected FeatureLayer(final ArcGisRestAbstractLayerService service, final CatalogElement parent) { super(service, parent); } public FeatureLayer(final ArcGisRestAbstractLayerService service, final CatalogElement parent, final MapEx properties) { super(service, parent); initialize(properties); } public FeatureLayer(final String layerUrl) { setServiceUrl(new UrlResource(layerUrl)); } private void addDefaultRecordQueryParameters(final Map<String, Object> parameters) { parameters.put("returnZ", "true"); parameters.put("outFields", "*"); } private void addField(final RecordDefinitionImpl recordDefinition, final String geometryType, final MapEx field) { final String fieldName = field.getString("name"); final String fieldTitle = field.getString("string"); final String fieldType = field.getString("type"); final FieldType esriFieldType = FieldType.valueOf(fieldType); final DataType dataType; if (esriFieldType == FieldType.esriFieldTypeGeometry) { final DataType geometryDataType = getGeometryDataType(geometryType); if (geometryDataType == null) { throw new IllegalArgumentException("No geometryType specified for " + getServiceUrl()); } dataType = geometryDataType; } else { dataType = esriFieldType.getDataType(); } if (dataType == null) { throw new IllegalArgumentException( "Unsupported field=" + fieldName + " type=" + dataType + " for " + getServiceUrl()); } final int length = field.getInteger("length", 0); final FieldDefinition fieldDefinition = recordDefinition.addField(fieldName, dataType, length, false); fieldDefinition.setTitle(fieldTitle); setCodeTable(fieldDefinition, field); if (esriFieldType == FieldType.esriFieldTypeOID) { recordDefinition.setIdFieldName(fieldName); fieldDefinition.setRequired(true); } } @Override public BoundingBox getBoundingBox() { refreshIfNeeded(); return this.boundingBox; } private DataType getGeometryDataType(final String geometryType) { DataType geometryDataType = null; if (Property.hasValue(geometryType)) { final GeometryType esriGeometryType = GeometryType.valueOf(geometryType); geometryDataType = esriGeometryType.getDataType(); if (geometryDataType == null) { throw new IllegalArgumentException( "Unsupported geometryType=" + geometryType + " for " + getServiceUrl()); } } return geometryDataType; } @Override public String getIconName() { return WebServiceFeatureLayer.super.getIconName(); } @Override public PathName getPathName() { return super.getPathName(); } @Override public int getRecordCount(final BoundingBox boundingBox) { final Map<String, Object> parameters = newQueryParameters(boundingBox); if (parameters == null) { return 0; } else { return getRecordCount(parameters, boundingBox); } } protected int getRecordCount(final Map<String, Object> parameters, final Object errorText) { parameters.put("returnCountOnly", "true"); final Resource resource = getResource("query", parameters); try { final MapEx response = Json.toMap(resource); return response.getInteger("count", 0); } catch (final Throwable e) { Logs.debug(this, "Unable to get count for: " + errorText + "\n" + resource.getUriString()); } return 0; } @Override public int getRecordCount(final Query query) { final Map<String, Object> parameters = newQueryParameters(query); if (query != null) { // OFFSET & LIMIT final int offset = query.getOffset(); parameters.put("resultOffset", offset); final int limit = query.getLimit(); if (limit != Integer.MAX_VALUE) { parameters.put("resultRecordCount", limit); } } return getRecordCount(parameters, query); } @Override public RecordDefinition getRecordDefinition() { refreshIfNeeded(); return this.recordDefinition; } @Override protected void initialize(final MapEx properties) { super.initialize(properties); this.boundingBox = ArcGisResponse.newBoundingBox(properties, "extent"); final PathName pathName = getPathName(); final List<MapEx> fields = properties.getValue("fields"); if (fields != null) { final RecordDefinitionImpl newRecordDefinition = new RecordDefinitionImpl(pathName); newRecordDefinition.setPolygonRingDirection(ClockDirection.CLOCKWISE); final String description = properties.getString("description"); newRecordDefinition.setDescription(description); final String geometryType = properties.getString("geometryType"); for (final MapEx field : fields) { addField(newRecordDefinition, geometryType, field); } if (Property.hasValue(geometryType)) { if (!newRecordDefinition.hasGeometryField()) { final DataType geometryDataType = getGeometryDataType(geometryType); if (geometryDataType == null) { throw new IllegalArgumentException("No geometryType specified for " + getServiceUrl()); } else { newRecordDefinition.addField("GEOMETRY", geometryDataType); } } } if (this.boundingBox != null) { final GeometryFactory geometryFactory = this.boundingBox.getGeometryFactory(); newRecordDefinition.setGeometryFactory(geometryFactory); } final FieldDefinition objectIdField = newRecordDefinition.getField("OBJECTID"); if (newRecordDefinition.getIdField() == null && objectIdField != null) { final int fieldIndex = objectIdField.getIndex(); newRecordDefinition.setIdFieldIndex(fieldIndex); objectIdField.setRequired(true); } this.recordDefinition = newRecordDefinition; } } public boolean isSupportsPagination() { return this.supportsPagination; } public Map<String, Object> newQueryParameters(BoundingBox boundingBox) { refreshIfNeeded(); boundingBox = convertBoundingBox(boundingBox); if (Property.hasValue(boundingBox)) { final Map<String, Object> parameters = new LinkedHashMap<>(); parameters.put("f", "json"); parameters.put("geometryType", "esriGeometryEnvelope"); final double minX = boundingBox.getMinX(); final double minY = boundingBox.getMinY(); final double maxX = boundingBox.getMaxX(); final double maxY = boundingBox.getMaxY(); final String boundingBoxText = minX + "," + minY + "," + maxX + "," + maxY; parameters.put("geometry", boundingBoxText); addDefaultRecordQueryParameters(parameters); return parameters; } else { return null; } } public Map<String, Object> newQueryParameters(final Query query) { final Map<String, Object> parameters = new LinkedHashMap<>(); parameters.put("f", "json"); parameters.put("returnGeometry", "true"); parameters.put("where", this.recordDefinition.getIdFieldName() + " > 0"); if (query != null) { // WHERE final Condition whereCondition = query.getWhereCondition(); if (whereCondition != Condition.ALL) { final String where = whereCondition.toString(); parameters.put("where", where); } // ORDER BY final Map<String, Boolean> orderBy = query.getOrderBy(); if (Property.hasValue(orderBy)) { final String orderByFields = JdbcUtils.appendOrderByFields(new StringBuilder(), orderBy) .toString(); parameters.put("orderByFields", orderByFields); } } return parameters; } public <V extends Record> RecordReader newRecordReader(final Query query, final boolean pageByObjectId) { return newRecordReader(ArrayRecord.FACTORY, query, pageByObjectId); } @Override public <V extends Record> RecordReader newRecordReader(final RecordFactory<V> recordFactory, final BoundingBox boundingBox) { final Map<String, Object> parameters = newQueryParameters(boundingBox); final ArcGisRestServerFeatureReader reader = new ArcGisRestServerFeatureReader(this, parameters, 0, Integer.MAX_VALUE, recordFactory, !isSupportsPagination()); return reader; } @Override public <V extends Record> RecordReader newRecordReader(final RecordFactory<V> recordFactory, final Query query) { return newRecordReader(recordFactory, query, false); } public <V extends Record> RecordReader newRecordReader(final RecordFactory<V> recordFactory, final Query query, final boolean pageByObjectId) { refreshIfNeeded(); final Map<String, Object> parameters = newQueryParameters(query); addDefaultRecordQueryParameters(parameters); int offset = 0; int limit = Integer.MAX_VALUE; if (query != null) { offset = query.getOffset(); limit = query.getLimit(); } return new ArcGisRestServerFeatureReader(this, parameters, offset, limit, recordFactory, pageByObjectId); } public void setAdvancedQueryCapabilities(final MapEx advancedQueryCapabilities) { setProperties(advancedQueryCapabilities); } @SuppressWarnings("unchecked") private void setCodeTable(final FieldDefinition fieldDefinition, final MapEx field) { final MapEx domain = (MapEx)field.get("domain"); if (domain != null) { final String domainType = domain.getString("type"); final String domainName = domain.getString("name"); final List<MapEx> codedValues = (List<MapEx>)domain.get("codedValues"); if ("codedValue".equals(domainType) && Property.hasValuesAll(domainName, codedValues)) { final SimpleCodeTable codeTable = new SimpleCodeTable(domainName); for (final MapEx codedValue : codedValues) { final String code = codedValue.getString("code"); final String description = codedValue.getString("name"); codeTable.addValue(code, description); } fieldDefinition.setCodeTable(codeTable); } } } public void setSupportsPagination(final boolean supportsPagination) { this.supportsPagination = supportsPagination; } }