package com.revolsys.swing.map.layer.arcgisrest;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import com.revolsys.collection.map.MapEx;
import com.revolsys.collection.map.Maps;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.io.PathName;
import com.revolsys.io.map.MapObjectFactoryRegistry;
import com.revolsys.logging.Logs;
import com.revolsys.record.Record;
import com.revolsys.record.io.RecordReader;
import com.revolsys.record.io.format.esri.rest.ArcGisRestCatalog;
import com.revolsys.record.io.format.esri.rest.map.FeatureLayer;
import com.revolsys.record.query.Query;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.spring.resource.UrlResource;
import com.revolsys.swing.map.layer.AbstractLayer;
import com.revolsys.swing.map.layer.LayerGroup;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.map.layer.record.renderer.AbstractMultipleRenderer;
import com.revolsys.swing.map.layer.record.renderer.AbstractRecordLayerRenderer;
import com.revolsys.swing.map.layer.record.renderer.FilterMultipleRenderer;
import com.revolsys.swing.map.layer.record.renderer.GeometryStyleRenderer;
import com.revolsys.swing.map.layer.record.renderer.MarkerStyleRenderer;
import com.revolsys.swing.map.layer.record.renderer.MultipleRenderer;
import com.revolsys.swing.map.layer.record.renderer.TextStyleRenderer;
import com.revolsys.swing.map.layer.record.style.GeometryStyle;
import com.revolsys.swing.map.layer.record.style.MarkerStyle;
import com.revolsys.swing.map.layer.record.style.TextStyle;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.swing.menu.Menus;
import com.revolsys.swing.tree.node.WebServiceConnectionTrees;
import com.revolsys.util.OS;
import com.revolsys.util.PasswordUtil;
import com.revolsys.util.Property;
public class ArcGisRestServerRecordLayer extends AbstractRecordLayer {
public static final String J_TYPE = "arcGisRestServerRecordLayer";
private static final Map<String, List<Double>> LINE_STYLE_PATTERNS = Maps
.<String, List<Double>> buildHash() //
.add("esriSLSDash", GeometryStyle.DASH_5) //
.add("esriSLSDashDot", GeometryStyle.DASH_DOT) //
.add("esriSLSDashDotDot", GeometryStyle.DASH_DOT_DOT) //
.add("esriSLSDot", GeometryStyle.DOT) //
.add("esriSLSNull", null) //
.add("esriSLSSolid", Collections.emptyList()) //
.getMap();
private static void actionAddLayer(final FeatureLayer layerDescription) {
final LayerGroup layerGroup = WebServiceConnectionTrees.getLayerGroup(layerDescription);
if (layerGroup != null) {
final ArcGisRestServerRecordLayer layer = new ArcGisRestServerRecordLayer(layerDescription);
layerGroup.addLayer(layer);
if (OS.getPreferenceBoolean("com.revolsys.gis", AbstractLayer.PREFERENCE_PATH,
AbstractLayer.PREFERENCE_NEW_LAYERS_SHOW_TABLE_VIEW, false)) {
layer.showTableView();
}
}
}
public static Color getColor(final MapEx properties) {
final String fieldName = "color";
return getColor(properties, fieldName);
}
public static Color getColor(final MapEx properties, final String fieldName) {
final List<Number> colorValues = properties.getValue(fieldName);
if (colorValues != null && colorValues.size() == 4) {
final int red = colorValues.get(0).intValue();
final int green = colorValues.get(1).intValue();
final int blue = colorValues.get(2).intValue();
final int alpha = colorValues.get(3).intValue();
return new Color(red, green, blue, alpha);
}
return null;
}
public static void mapObjectFactoryInit() {
MapObjectFactoryRegistry.newFactory(J_TYPE, "Arc GIS REST Server Record Layer",
ArcGisRestServerRecordLayer::new);
final MenuFactory recordLayerDescriptionMenu = MenuFactory.getMenu(FeatureLayer.class);
Menus.addMenuItem(recordLayerDescriptionMenu, "default", "Add Layer", "map_add",
ArcGisRestServerRecordLayer::actionAddLayer, false);
}
private FeatureLayer layerDescription;
private String url;
private PathName layerPath;
private String username;
private String password;
public ArcGisRestServerRecordLayer() {
super(J_TYPE);
setReadOnly(true);
}
public ArcGisRestServerRecordLayer(final FeatureLayer layerDescription) {
this();
setLayerDescription(layerDescription);
setProperties(Collections.emptyMap());
}
public ArcGisRestServerRecordLayer(final Map<String, ? extends Object> properties) {
this();
setProperties(properties);
}
private void addTextRenderer(final AbstractMultipleRenderer renderers,
final MapEx labelProperties) {
final TextStyle textStyle = new TextStyle();
final String alignment = labelProperties.getString("labelPlacement");
if (alignment.endsWith("Left")) {
textStyle.setTextHorizontalAlignment("right");
} else if (alignment.endsWith("Right")) {
textStyle.setTextHorizontalAlignment("left");
} else if (alignment.endsWith("Before")) {
textStyle.setTextHorizontalAlignment("right");
textStyle.setTextPlacementType("vertex(0)");
} else if (alignment.endsWith("Start")) {
textStyle.setTextHorizontalAlignment("left");
textStyle.setTextPlacementType("vertex(0)");
} else if (alignment.endsWith("After")) {
textStyle.setTextHorizontalAlignment("left");
textStyle.setTextPlacementType("vertex(n)");
} else if (alignment.endsWith("End")) {
textStyle.setTextHorizontalAlignment("right");
textStyle.setTextPlacementType("vertex(n)");
} else if (alignment.endsWith("Along")) {
textStyle.setTextHorizontalAlignment("center");
textStyle.setTextPlacementType("auto");
} else {
textStyle.setTextHorizontalAlignment("center");
}
if (alignment.contains("Above")) {
textStyle.setTextVerticalAlignment("bottom");
} else if (alignment.endsWith("Below")) {
textStyle.setTextVerticalAlignment("top");
} else {
textStyle.setTextVerticalAlignment("center");
}
String textName = labelProperties.getString("labelExpression");
textName = textName.replace(" CONCAT ", "");
textName = textName.replaceAll("\"([^\"]+)\"", "$1");
textStyle.setTextName(textName);
final MapEx symbol = labelProperties.getValue("symbol");
if ("esriTS".equals(symbol.getString("type"))) {
final Color textFill = getColor(symbol);
textStyle.setTextFill(textFill);
final Color backgroundColor = getColor(symbol, "backgroundColor");
textStyle.setTextBoxColor(backgroundColor);
// "useCodedValues": false,
// "borderLineColor": null,
// "verticalAlignment": "bottom",
// "horizontalAlignment": "left",
// "rightToLeft": false,
// "kerning": true,
final double angle = symbol.getDouble("angle", 0);
textStyle.setTextOrientation(angle);
final Measure<Length> textDx = Measure.valueOf(symbol.getDouble("xoffset", 0), NonSI.PIXEL);
textStyle.setTextDx(textDx);
final Measure<Length> textDy = Measure.valueOf(symbol.getDouble("yoffset", 0), NonSI.PIXEL);
textStyle.setTextDx(textDy);
final MapEx font = symbol.getValue("font");
if (font != null) {
final String faceName = font.getString("family", "Arial");
textStyle.setTextFaceName(faceName);
final Measure<Length> size = Measure.valueOf(font.getDouble("size", 10), NonSI.PIXEL);
textStyle.setTextSize(size);
}
// "font": {
// "style": "normal",
// "weight": "bold",
// "decoration": "none"
}
final TextStyleRenderer textRenderer = new TextStyleRenderer(this, textStyle);
textRenderer.setName(textName.replace("[", "").replace("]", ""));
long minimumScale = labelProperties.getLong("minScale", Long.MAX_VALUE);
if (minimumScale == 0) {
minimumScale = Long.MAX_VALUE;
}
textRenderer.setMinimumScale(minimumScale);
final long maximumScale = labelProperties.getLong("maxScale", 0);
textRenderer.setMaximumScale(maximumScale);
final String where = labelProperties.getString("where");
textRenderer.setQueryFilter(where);
renderers.addRenderer(textRenderer);
}
@Override
public ArcGisRestServerRecordLayer clone() {
final ArcGisRestServerRecordLayer clone = (ArcGisRestServerRecordLayer)super.clone();
return clone;
}
@Override
protected void forEachRecordInternal(final Query query, final Consumer<? super LayerRecord> consumer) {
try (
RecordReader reader = this.layerDescription.newRecordReader(this::newLayerRecord, query)) {
for (final Record record : reader) {
consumer.accept((LayerRecord)record);
}
}
}
public FeatureLayer getLayerDescription() {
return this.layerDescription;
}
public PathName getLayerPath() {
return this.layerPath;
}
@Override
public int getRecordCount(final Query query) {
if (this.layerDescription == null) {
return 0;
} else {
return this.layerDescription.getRecordCount(query);
}
}
@Override
public List<LayerRecord> getRecords(BoundingBox boundingBox) {
if (hasGeometryField()) {
boundingBox = convertBoundingBox(boundingBox);
if (Property.hasValue(boundingBox)) {
final List<LayerRecord> records = this.layerDescription.getRecords(this::newLayerRecord,
boundingBox);
return records;
}
}
return Collections.emptyList();
}
public String getUrl() {
return this.url;
}
@Override
protected boolean initializeDo() {
FeatureLayer layerDescription = getLayerDescription();
if (layerDescription == null) {
final String url = getUrl();
final PathName layerPath = getLayerPath();
if (url == null) {
Logs.error(this, "An ArcGIS Rest server requires a url: " + getPath());
return false;
}
if (layerPath == null) {
Logs.error(this, "An ArcGIS Rest server requires a layerPath: " + getPath());
return false;
}
ArcGisRestCatalog server;
try {
server = ArcGisRestCatalog.newArcGisRestCatalog(url);
} catch (final Throwable e) {
Logs.error(this, "Unable to connect to server: " + url + " for " + getPath(), e);
return false;
}
try {
layerDescription = server.getWebServiceResource(layerPath, FeatureLayer.class);
} catch (final IllegalArgumentException e) {
Logs.error(this, "ArcGIS Rest service is not a layer " + getPath(), e);
return false;
}
if (layerDescription == null) {
Logs.error(this, "No ArcGIS Rest layer with name: " + layerPath + " for " + getPath());
return false;
} else {
setLayerDescription(layerDescription);
}
}
if (layerDescription != null) {
final RecordDefinition recordDefinition = layerDescription.getRecordDefinition();
if (recordDefinition != null) {
setRecordDefinition(recordDefinition);
setBoundingBox(layerDescription.getBoundingBox());
initRenderer();
return super.initializeDo();
}
}
return false;
}
private void initRenderer() {
final List<AbstractRecordLayerRenderer> renderers = new ArrayList<>();
final MapEx drawingInfo = this.layerDescription.getProperty("drawingInfo");
if (drawingInfo != null) {
final MapEx rendererProperties = drawingInfo.getValue("renderer");
if (rendererProperties != null) {
final String rendererType = rendererProperties.getString("type");
if ("simple".equals(rendererType)) {
final AbstractRecordLayerRenderer renderer = newSymbolRenderer(rendererProperties,
"symbol");
if (renderer != null) {
renderers.add(renderer);
}
} else if ("uniqueValue".equals(rendererType)) {
final FilterMultipleRenderer filterRenderer = newUniqueValueRenderer(rendererProperties);
renderers.add(filterRenderer);
} else {
Logs.error(this, "Unsupported renderer=" + rendererType + "\n" + rendererProperties);
}
}
final List<MapEx> labellingInfo = drawingInfo.getValue("labelingInfo");
if (labellingInfo != null) {
final MultipleRenderer labelRenderer = new MultipleRenderer(this);
labelRenderer.setName("labels");
for (final MapEx labelProperties : labellingInfo) {
addTextRenderer(labelRenderer, labelProperties);
}
if (!labelRenderer.isEmpty()) {
renderers.add(labelRenderer);
}
}
}
if (renderers.isEmpty()) {
} else if (renderers.size() == 1) {
setRenderer(renderers.get(0));
} else {
setRenderer(new MultipleRenderer(this, renderers));
}
}
private AbstractRecordLayerRenderer newSimpleFillRenderer(final MapEx symbol) {
final MapEx outline = symbol.getValue("outline");
final GeometryStyle style;
if (outline == null) {
style = new GeometryStyle();
style.setLineWidth(Measure.valueOf(0, NonSI.PIXEL));
} else {
style = newSimpleLineStyle(outline);
}
final Color fillColor = getColor(symbol);
style.setPolygonFill(fillColor);
return new GeometryStyleRenderer(this, style);
}
private AbstractRecordLayerRenderer newSimpleLineRenderer(final MapEx symbol) {
final GeometryStyle style = newSimpleLineStyle(symbol);
return new GeometryStyleRenderer(this, style);
}
private GeometryStyle newSimpleLineStyle(final MapEx symbol) {
final double lineWidth = symbol.getDouble("width", 1.0);
final Color lineColor = getColor(symbol);
final GeometryStyle style = GeometryStyle.line(lineColor, lineWidth);
final String dashStyle = symbol.getString("style");
if (LINE_STYLE_PATTERNS.containsKey(dashStyle)) {
final List<Double> dashArray = LINE_STYLE_PATTERNS.get(dashStyle);
if (dashArray == null) {
style.setLineWidth(Measure.valueOf(0, NonSI.PIXEL));
} else if (!dashArray.isEmpty()) {
style.setLineDashArray(dashArray);
}
}
return style;
}
private AbstractRecordLayerRenderer newSimpleMarkerRenderer(final MapEx symbol) {
String markerName = symbol.getString("style", "esriSMSCirlce");
markerName = markerName.replace("esriSMS", "").toLowerCase();
final double markerSize = symbol.getDouble("size", 10);
final Color markerFill = getColor(symbol);
Color markerColor = new Color(0, 0, 0, 0);
final MapEx outline = symbol.getValue("outline");
double lineWidth = 0;
if (outline != null) {
markerColor = getColor(outline);
lineWidth = outline.getDouble("width", lineWidth);
}
final MarkerStyle markerStyle = MarkerStyle.marker(markerName, markerSize, markerColor,
lineWidth, markerFill);
return new MarkerStyleRenderer(this, markerStyle);
}
private AbstractRecordLayerRenderer newSymbolRenderer(final MapEx rendererProperties,
final String fieldName) {
final MapEx symbolProperties = rendererProperties.getValue(fieldName);
if (symbolProperties == null) {
return null;
} else {
final String symbolType = symbolProperties.getString("type");
if ("esriSMS".equals(symbolType)) {
return newSimpleMarkerRenderer(symbolProperties);
} else if ("esriSLS".equals(symbolType)) {
return newSimpleLineRenderer(symbolProperties);
} else if ("esriSFS".equals(symbolType)) {
return newSimpleFillRenderer(symbolProperties);
} else {
// TODO PMS
// TODO PFS
Logs.error(this, "Unsupported symbol=" + symbolType + "\n" + symbolType);
return new GeometryStyleRenderer(this);
}
}
}
@SuppressWarnings("unchecked")
private FilterMultipleRenderer newUniqueValueRenderer(final MapEx rendererProperties) {
final FilterMultipleRenderer filterRenderer = new FilterMultipleRenderer(this);
final String fieldName = rendererProperties.getString("field1");
filterRenderer.setName(fieldName);
for (final MapEx valueProperties : (List<MapEx>)rendererProperties
.getValue("uniqueValueInfos")) {
final AbstractRecordLayerRenderer valueRenderer = newSymbolRenderer(valueProperties,
"symbol");
if (valueRenderer != null) {
final String valueLabel = valueProperties.getString("label");
if (valueLabel != null) {
valueRenderer.setName(valueLabel);
}
final String value = valueProperties.getString("value");
if (value != null) {
final FieldDefinition fieldDefinition = getFieldDefinition(fieldName);
if (fieldDefinition.getDataType().isRequiresQuotes()) {
valueRenderer.setQueryFilter(fieldName + "='" + value + '\'');
} else {
valueRenderer.setQueryFilter(fieldName + " = " + value);
}
}
filterRenderer.addRenderer(valueRenderer);
}
}
final AbstractRecordLayerRenderer defaultRenderer = newSymbolRenderer(rendererProperties,
"defaultSymbol");
if (defaultRenderer != null) {
final String defaultLabel = rendererProperties.getString("defaultLabel", "Default");
defaultRenderer.setName(defaultLabel);
filterRenderer.addRenderer(defaultRenderer);
}
return filterRenderer;
}
public void setLayerDescription(final FeatureLayer layerDescription) {
this.layerDescription = layerDescription;
if (layerDescription != null) {
final String name = layerDescription.getName();
setName(name);
final UrlResource url = layerDescription.getRootServiceUrl();
setUrl(url);
final PathName pathName = layerDescription.getPathName();
setLayerPath(pathName);
final long minScale = layerDescription.getMinScale();
setMinimumScale(minScale);
final long maxScale = layerDescription.getMaxScale();
setMaximumScale(maxScale);
}
}
public void setLayerPath(final PathName layerPath) {
this.layerPath = layerPath;
}
public void setPassword(final String password) {
this.password = PasswordUtil.decrypt(password);
}
public void setUrl(final String url) {
this.url = url;
}
public void setUrl(final UrlResource url) {
if (url != null) {
setUrl(url.getUriString());
this.username = url.getUsername();
this.password = url.getPassword();
}
}
public void setUsername(final String username) {
this.username = username;
}
@Override
public MapEx toMap() {
final MapEx map = super.toMap();
addToMap(map, "url", this.url);
addToMap(map, "username", this.username);
addToMap(map, "password", PasswordUtil.encrypt(this.password));
addToMap(map, "layerPath", this.layerPath);
return map;
}
}