Create a special third-party case for ESRI servers

This currently supports groups (the id in the sources.json *must* be the
group id) and specific FeatureServers.

This should only be used when (a) permission is given for OpenStreetMap
to use the data and (b) the server owner has given their OK. While the
feature calls should be as efficient as possible, many server owners
will be paying for data transfer.

Signed-off-by: Taylor Smock <taylor.smock@kaart.com>
pull/1/head
Taylor Smock 2020-05-20 14:13:35 -06:00
rodzic dee0b13fc7
commit 5d883d8fd4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 625F6A74A3E4311A
3 zmienionych plików z 128 dodań i 6 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.BoundingBoxDownloader;
import org.openstreetmap.josm.io.GeoJSONReader;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.io.OsmApiException;
import org.openstreetmap.josm.io.OsmTransferException;
@ -90,7 +91,15 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
@Override
protected DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
DataSet ds = OsmReaderCustom.parseDataSet(source, progressMonitor, true);
DataSet ds;
if (MapWithAIInfo.MapWithAIType.ESRI_FEATURE_SERVER.equals(this.info.getSourceType())) {
ds = GeoJSONReader.parseDataSet(source, progressMonitor);
if (info.getReplacementTags() != null) {
GetDataRunnable.replaceKeys(ds, info.getReplacementTags());
}
} else {
ds = OsmReaderCustom.parseDataSet(source, progressMonitor, true);
}
if (url != null && info.getUrl() != null && !info.getUrl().trim().isEmpty()) {
GetDataRunnable.addMapWithAISourceTag(ds, getSourceTag(info));
}

Wyświetl plik

@ -44,7 +44,7 @@ public class MapWithAIInfo extends TileSourceInfo implements Comparable<MapWithA
* Type of MapWithAI entry
*/
public enum MapWithAIType {
FACEBOOK("facebook"), THIRD_PARTY("thirdParty");
FACEBOOK("facebook"), THIRD_PARTY("thirdParty"), ESRI("esri"), ESRI_FEATURE_SERVER("esriFeatureServer");
private final String typeString;
@ -734,9 +734,15 @@ public class MapWithAIInfo extends TileSourceInfo implements Comparable<MapWithA
StringBuilder sb = new StringBuilder();
if (url != null && !url.trim().isEmpty()) {
sb.append(url);
List<String> parameters = getParametersString();
if (!parameters.isEmpty()) {
sb.append('&').append(String.join("&", parameters));
if (MapWithAIType.ESRI_FEATURE_SERVER.equals(type)) {
if (!url.endsWith("/")) {
sb.append("/");
}
sb.append("query?geometryType=esriGeometryEnvelope&geometry={bbox}&inSR=4326&f=geojson&outfields=*");
}
List<String> parametersString = getParametersString();
if (!parametersString.isEmpty()) {
sb.append('&').append(String.join("&", parametersString));
}
}
return sb.toString();

Wyświetl plik

@ -3,6 +3,7 @@ package org.openstreetmap.josm.plugins.mapwithai.data.mapwithai;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -16,9 +17,20 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import org.openstreetmap.josm.data.StructUtils;
import org.openstreetmap.josm.data.imagery.ImageryInfo;
import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.CachedFile;
@ -193,7 +205,11 @@ public class MapWithAILayerInfo {
allDefaultLayers.clear();
defaultLayers.addAll(newLayers);
for (MapWithAIInfo layer : newLayers) {
allDefaultLayers.add(layer);
if (MapWithAIInfo.MapWithAIType.ESRI.equals(layer.getSourceType())) {
allDefaultLayers.addAll(addEsriLayer(layer));
} else {
allDefaultLayers.add(layer);
}
}
defaultLayerIds.clear();
Collections.sort(defaultLayers);
@ -210,6 +226,97 @@ public class MapWithAILayerInfo {
}
}
private static Collection<MapWithAIInfo> addEsriLayer(MapWithAIInfo layer) {
Pattern startReplace = Pattern.compile("\\{start\\}");
String search = "/search?sortField=added&sortOrder=desc&num=12&start={start}&f=json";
String url = layer.getUrl();
String group = layer.getId();
if (!url.endsWith("/")) {
url = url.concat("/");
}
Collection<MapWithAIInfo> information = new HashSet<>();
String next = "1";
String searchUrl = startReplace.matcher(search).replaceAll(next);
while (!next.equals("-1")) {
try (CachedFile layers = new CachedFile(url + "content/groups/" + group + searchUrl);
BufferedReader i = layers.getContentReader();
JsonReader reader = Json.createReader(i)) {
JsonStructure parser = reader.read();
if (parser.getValueType().equals(JsonValue.ValueType.OBJECT)) {
JsonObject obj = parser.asJsonObject();
next = obj.getString("nextStart", "-1");
searchUrl = startReplace.matcher(search).replaceAll(next);
JsonArray features = obj.getJsonArray("results");
for (JsonObject feature : features.getValuesAs(JsonObject.class)) {
MapWithAIInfo newInfo = new MapWithAIInfo();
newInfo.setId(feature.getString("id"));
if (feature.getString("type", "").equals("Feature Service")) {
newInfo.setUrl(featureService(newInfo, feature.getString("url")));
} else {
newInfo.setUrl(feature.getString("url"));
}
newInfo.setName(feature.getString("title", feature.getString("name")));
String[] extent = feature.getJsonArray("extent").getValuesAs(JsonArray.class).stream()
.flatMap(array -> array.getValuesAs(JsonNumber.class).stream())
.map(JsonNumber::doubleValue).map(Object::toString).toArray(String[]::new);
ImageryBounds imageryBounds = new ImageryBounds(
String.join(",", extent[1], extent[0], extent[3], extent[2]), ",");
newInfo.setBounds(imageryBounds);
newInfo.setSourceType(MapWithAIInfo.MapWithAIType.ESRI_FEATURE_SERVER);
newInfo.setTermsOfUseText(feature.getString("licenseInfo", null));
if (feature.containsKey("thumbnail")) {
newInfo.setAttributionImageURL(url + "content/items/" + newInfo.getId() + "/info/"
+ feature.getString("thumbnail"));
}
// TODO groupCategories
// TODO snippet/description
information.add(newInfo);
}
}
} catch (ClassCastException | IOException e) {
Logging.error(e);
next = "-1";
}
}
return information;
}
private static String featureService(MapWithAIInfo mapwithaiInfo, String url) {
String toGet = url.endsWith("pjson") ? url : url.concat("?f=pjson");
try (CachedFile featureServer = new CachedFile(toGet);
BufferedReader br = featureServer.getContentReader();
JsonReader reader = Json.createReader(br)) {
JsonObject info = reader.readObject();
JsonArray layers = info.getJsonArray("layers");
// TODO use all the layers?
JsonObject layer = layers.get(0).asJsonObject();
String partialUrl = (url.endsWith("/") ? url : url + "/") + layer.getInt("id");
mapwithaiInfo.setReplacementTags(getReplacementTags(partialUrl));
return partialUrl;
} catch (IOException e) {
Logging.error(e);
return null;
}
}
private static Map<String, String> getReplacementTags(String layerUrl) {
String toGet = layerUrl.endsWith("pjson") ? layerUrl : layerUrl.concat("?f=pjson");
try (CachedFile featureServer = new CachedFile(toGet);
BufferedReader br = featureServer.getContentReader();
JsonReader reader = Json.createReader(br)) {
JsonObject info = reader.readObject();
return info.getJsonArray("fields").getValuesAs(JsonObject.class).stream()
.collect(Collectors.toMap(o -> o.getString("name"), o -> o.getString("alias", null)));
} catch (IOException e) {
Logging.error(e);
}
return Collections.emptyMap();
}
/**
* Build the mapping of unique ids to {@link ImageryInfo}s.
*