package com.revolsys.swing.map.layer.arcgisrest;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import com.revolsys.collection.map.MapEx;
import com.revolsys.datatype.DataType;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.io.PathName;
import com.revolsys.io.map.MapObjectFactoryRegistry;
import com.revolsys.logging.Logs;
import com.revolsys.record.io.format.esri.rest.ArcGisRestCatalog;
import com.revolsys.record.io.format.esri.rest.map.MapService;
import com.revolsys.record.io.format.esri.rest.map.TileInfo;
import com.revolsys.spring.resource.UrlResource;
import com.revolsys.swing.Icons;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.component.BasePanel;
import com.revolsys.swing.component.ValueField;
import com.revolsys.swing.field.TextField;
import com.revolsys.swing.layout.GroupLayouts;
import com.revolsys.swing.map.Viewport2D;
import com.revolsys.swing.map.layer.AbstractTiledImageLayer;
import com.revolsys.swing.map.layer.BaseMapLayer;
import com.revolsys.swing.map.layer.BaseMapLayerGroup;
import com.revolsys.swing.map.layer.MapTile;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.swing.menu.Menus;
import com.revolsys.util.CaseConverter;
import com.revolsys.util.Exceptions;
import com.revolsys.util.PasswordUtil;
import com.revolsys.util.Property;
import com.revolsys.util.WrappedException;
import com.revolsys.webservice.WebService;
import com.revolsys.webservice.WebServiceConnectionManager;
import com.revolsys.webservice.WebServiceResource;
public class ArcGisRestServerTileCacheLayer extends AbstractTiledImageLayer {
private static void actionAddLayer(final BaseMapLayerGroup parent) {
final ValueField dialog = new ValueField();
dialog.setTitle("Add ArcGIS Tile Cache");
SwingUtil.addLabel(dialog, "URL");
final TextField urlField = new TextField("url", 50);
dialog.add(urlField);
GroupLayouts.makeColumns(dialog, 2, true, true);
dialog.setSaveAction(() -> {
final String url = urlField.getText();
if (Property.hasValue(url)) {
final ArcGisRestServerTileCacheLayer layer = new ArcGisRestServerTileCacheLayer();
layer.setUrl(url);
layer.setVisible(true);
parent.addLayer(layer);
}
});
dialog.showDialog();
}
public static void mapObjectFactoryInit() {
MapObjectFactoryRegistry.newFactory("arcGisRestServerTileLayer",
"Arc GIS REST Server Tile Cache Layer", ArcGisRestServerTileCacheLayer::new);
MapObjectFactoryRegistry.newFactory("arcgisServerRest", "Arc GIS REST Server Tile Cache Layer",
ArcGisRestServerTileCacheLayer::new);
final MenuFactory baseMapsMenu = MenuFactory.getMenu(BaseMapLayerGroup.class);
Menus.addMenuItem(baseMapsMenu, "group", "Add ArcGIS Tile Cache",
Icons.getIconWithBadge("map", "add"), ArcGisRestServerTileCacheLayer::actionAddLayer, false);
final MenuFactory tileInfoMenu = MenuFactory.getMenu(TileInfo.class);
final Function<TileInfo, BaseMapLayer> baseMapLayerFactory = ArcGisRestServerTileCacheLayer::new;
BaseMapLayer.addNewLayerMenu(tileInfoMenu, baseMapLayerFactory);
}
private String username;
private String password;
private GeometryFactory geometryFactory;
private final Object initSync = new Object();
private MapService mapService;
private String url;
private PathName servicePath;
private String connectionName;
public ArcGisRestServerTileCacheLayer() {
super("arcGisRestServerTileLayer");
}
public ArcGisRestServerTileCacheLayer(final Map<String, ? extends Object> properties) {
this();
setProperties(properties);
}
public ArcGisRestServerTileCacheLayer(final TileInfo tileInfo) {
this();
final MapService mapService = tileInfo.getMapService();
final WebService<?> webService = mapService.getWebService();
if (webService instanceof ArcGisRestCatalog) {
this.connectionName = webService.getName();
this.servicePath = mapService.getPathName();
setName(mapService.getParent().getName());
} else {
final UrlResource serviceUrl = mapService.getServiceUrl();
setUrl(serviceUrl);
}
}
@Override
public boolean equals(final Object other) {
if (other instanceof ArcGisRestServerTileCacheLayer) {
final ArcGisRestServerTileCacheLayer layer = (ArcGisRestServerTileCacheLayer)other;
if (DataType.equal(layer.connectionName, this.connectionName)) {
if (DataType.equal(layer.servicePath, this.servicePath)) {
if (DataType.equal(layer.url, this.url)) {
return true;
}
}
}
}
return false;
}
public String getConnectionName() {
return this.connectionName;
}
public MapService getMapService() {
return this.mapService;
}
@Override
public List<MapTile> getOverlappingMapTiles(final Viewport2D viewport) {
final List<MapTile> tiles = new ArrayList<>();
final MapService mapService = getMapService();
if (mapService != null) {
if (!isHasError()) {
try {
final double metresPerPixel = viewport.getUnitsPerPixel();
final int zoomLevel = mapService.getZoomLevel(metresPerPixel);
final double resolution = getResolution(viewport);
if (resolution > 0) {
final BoundingBox viewBoundingBox = viewport.getBoundingBox();
final BoundingBox maxBoundingBox = getBoundingBox();
final BoundingBox boundingBox = viewBoundingBox.convert(this.geometryFactory)
.intersection(maxBoundingBox);
final double minX = boundingBox.getMinX();
final double minY = boundingBox.getMinY();
final double maxX = boundingBox.getMaxX();
final double maxY = boundingBox.getMaxY();
// Tiles start at the North-West corner of the map
final int minTileX = mapService.getTileX(zoomLevel, minX);
final int minTileY = mapService.getTileY(zoomLevel, maxY);
final int maxTileX = mapService.getTileX(zoomLevel, maxX);
final int maxTileY = mapService.getTileY(zoomLevel, minY);
for (int tileY = minTileY; tileY <= maxTileY; tileY++) {
for (int tileX = minTileX; tileX <= maxTileX; tileX++) {
final ArcGisRestServerTileCacheMapTile tile = new ArcGisRestServerTileCacheMapTile(
this, mapService, zoomLevel, resolution, tileX, tileY);
tiles.add(tile);
}
}
}
} catch (final Throwable e) {
setError(e);
}
}
}
return tiles;
}
@Override
public double getResolution(final Viewport2D viewport) {
final MapService mapService = getMapService();
if (mapService == null) {
return 0;
} else {
final double metresPerPixel = viewport.getUnitsPerPixel();
final int zoomLevel = mapService.getZoomLevel(metresPerPixel);
return mapService.getResolution(zoomLevel);
}
}
public PathName getServicePath() {
return this.servicePath;
}
public String getUrl() {
return this.url;
}
@Override
protected boolean initializeDo() {
synchronized (this.initSync) {
if (this.mapService == null) {
try {
if (Property.hasValue(this.connectionName)) {
final WebService<?> webService = WebServiceConnectionManager
.getWebService(this.connectionName);
if (webService instanceof ArcGisRestCatalog) {
final ArcGisRestCatalog catalog = (ArcGisRestCatalog)webService;
final WebServiceResource service = catalog.getWebServiceResource(this.servicePath);
if (service instanceof MapService) {
this.mapService = (MapService)service;
} else {
Logs.error(this,
getPath() + ": Web service " + this.connectionName + " is not a ArcGIS service");
return false;
}
} else {
Logs.error(this, getPath() + ": Web service " + this.connectionName + " "
+ this.servicePath + " is not a ArcGIS Map service");
return false;
}
} else {
// TODO username/password
this.mapService = MapService.getMapService(this.url);
}
if (this.mapService == null) {
Logs.error(this, "Unable to connect to ArcGIS rest server");
return false;
} else {
final TileInfo tileInfo = this.mapService.getTileInfo();
if (tileInfo == null) {
Logs.info(this, this.url + " does not contain a tileInfo definition.");
return false;
} else {
this.geometryFactory = tileInfo.getGeometryFactory();
final BoundingBox boundingBox = this.mapService.getFullExtent();
setBoundingBox(boundingBox);
return true;
}
}
} catch (final WrappedException e) {
final Throwable cause = Exceptions.unwrap(e);
if (cause instanceof UnknownHostException) {
// Logs.error(this, getPath() + "Unknown host: " +
// cause.getMessage());
return setNotExists("Unknown host: " + cause.getMessage());
} else {
throw e;
}
}
} else {
return true;
}
}
}
@Override
protected ValueField newPropertiesTabGeneralPanelSource(final BasePanel parent) {
final ValueField panel = super.newPropertiesTabGeneralPanelSource(parent);
final String url = getUrl();
SwingUtil.addLabelledReadOnlyTextField(panel, "URL", url);
GroupLayouts.makeColumns(panel, 2, true);
return panel;
}
@Override
public void refreshDo() {
initializeForce();
super.refreshDo();
}
public void setConnectionName(final String connectionName) {
this.connectionName = connectionName;
}
public void setPassword(final String password) {
this.password = PasswordUtil.decrypt(password);
}
public void setServicePath(final PathName servicePath) {
this.servicePath = servicePath;
}
public void setUrl(final String url) {
final Object oldValue = this.url;
this.url = url;
if (getName() == null) {
final String name = CaseConverter.toCapitalizedWords(url.replaceAll(".+/rest/services/", "")//
.replaceAll("/MapServer", " ")//
.replaceAll("/", " "));
setName(name);
}
firePropertyChange("url", oldValue, 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();
if (Property.hasValue(this.connectionName)) {
addToMap(map, "connectionName", this.connectionName);
addToMap(map, "servicePath", this.servicePath);
} else {
addToMap(map, "url", this.url);
addToMap(map, "username", this.username);
addToMap(map, "password", PasswordUtil.encrypt(this.password));
}
return map;
}
}