kopia lustrzana https://github.com/JOSM/MapWithAI
277 wiersze
11 KiB
Java
277 wiersze
11 KiB
Java
// License: GPL. For details, see LICENSE file.
|
|
package org.openstreetmap.josm.plugins.mapwithai.backend;
|
|
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.TreeMap;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
import org.openstreetmap.josm.data.Bounds;
|
|
import org.openstreetmap.josm.data.coor.LatLon;
|
|
import org.openstreetmap.josm.io.CachedFile;
|
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
|
|
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
|
|
import org.openstreetmap.josm.plugins.mapwithai.spi.preferences.MapWithAIConfig;
|
|
import org.openstreetmap.josm.tools.Logging;
|
|
import org.openstreetmap.josm.tools.Territories;
|
|
import org.openstreetmap.josm.tools.Utils;
|
|
|
|
import jakarta.json.Json;
|
|
import jakarta.json.JsonException;
|
|
import jakarta.json.JsonObject;
|
|
import jakarta.json.JsonValue;
|
|
|
|
/**
|
|
* Check for data availability in an area
|
|
*/
|
|
public class DataAvailability {
|
|
/** A map of tag -> message of possible data types */
|
|
static final Map<String, String> POSSIBLE_DATA_POINTS = new TreeMap<>();
|
|
|
|
private static final String PROVIDES = "provides";
|
|
private static final int SEVEN_DAYS_IN_SECONDS = 604_800;
|
|
|
|
/**
|
|
* Map<Source,
|
|
* Map<(url|parameters|countries|license|osm_compatible|permission_url),
|
|
* Object>>
|
|
*/
|
|
protected static final Map<String, Map<String, Object>> COUNTRY_MAP = new HashMap<>();
|
|
/**
|
|
* This holds classes that can give availability of data for a specific service
|
|
*/
|
|
private static final List<Class<? extends DataAvailability>> DATA_SOURCES = new ArrayList<>();
|
|
|
|
private static DataAvailability instance;
|
|
|
|
/**
|
|
* A map of countries to a map of available types
|
|
* ({@code Map<Country, Map<Type, IsAvailable>>}
|
|
*/
|
|
static final Map<String, Map<String, Boolean>> COUNTRIES = new HashMap<>();
|
|
|
|
protected DataAvailability() {
|
|
if (DataAvailability.class.equals(this.getClass())) {
|
|
initialize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the class
|
|
*/
|
|
private static void initialize() {
|
|
try (var jsonFile = new CachedFile(MapWithAIConfig.getUrls().getMapWithAISourcesJson());
|
|
var jsonParser = Json.createParser(jsonFile.getContentReader())) {
|
|
jsonFile.setMaxAge(SEVEN_DAYS_IN_SECONDS);
|
|
jsonParser.next();
|
|
final var jsonObject = jsonParser.getObject();
|
|
for (var entry : jsonObject.entrySet()) {
|
|
Logging.debug("{0}: {1}", entry.getKey(), entry.getValue());
|
|
if (JsonValue.ValueType.OBJECT == entry.getValue().getValueType()
|
|
&& entry.getValue().asJsonObject().containsKey("countries")) {
|
|
final var countries = entry.getValue().asJsonObject().get("countries");
|
|
parseCountries(COUNTRIES, countries, entry.getValue());
|
|
}
|
|
}
|
|
} catch (JsonException | IOException e) {
|
|
Logging.debug(e);
|
|
}
|
|
|
|
DATA_SOURCES.forEach(clazz -> {
|
|
try {
|
|
clazz.getConstructor().newInstance();
|
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
|
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
|
Logging.debug(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parse a JSON Value for country information
|
|
*
|
|
* @param countriesMap The countries map (will be modified)
|
|
* @param countries The country object (JsonObject)
|
|
* @param information The information for the source
|
|
*/
|
|
private static void parseCountries(Map<String, Map<String, Boolean>> countriesMap, JsonValue countries,
|
|
JsonValue information) {
|
|
if (JsonValue.ValueType.OBJECT == countries.getValueType()) {
|
|
parseCountriesObject(countriesMap, countries.asJsonObject(), information);
|
|
} else {
|
|
Logging.error("MapWithAI: Check format of countries map from MapWithAI");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a JsonObject for countries
|
|
*
|
|
* @param countriesMap The countries map (will be modified)
|
|
* @param countryObject The country object (JsonObject)
|
|
* @param information The information for the source
|
|
*/
|
|
private static void parseCountriesObject(Map<String, Map<String, Boolean>> countriesMap, JsonObject countryObject,
|
|
JsonValue information) {
|
|
for (Map.Entry<String, JsonValue> entry : countryObject.entrySet()) {
|
|
Map<String, Boolean> providesMap = countriesMap.getOrDefault(entry.getKey(), new TreeMap<>());
|
|
countriesMap.putIfAbsent(entry.getKey(), providesMap);
|
|
if (JsonValue.ValueType.ARRAY == entry.getValue().getValueType()) {
|
|
for (String provide : entry.getValue().asJsonArray().stream()
|
|
.filter(c -> JsonValue.ValueType.STRING == c.getValueType()).map(JsonValue::toString)
|
|
.map(DataAvailability::stripQuotes).toList()) {
|
|
providesMap.put(provide, true);
|
|
}
|
|
}
|
|
if (providesMap.isEmpty() && JsonValue.ValueType.OBJECT == information.getValueType()
|
|
&& information.asJsonObject().containsKey(PROVIDES)
|
|
&& JsonValue.ValueType.ARRAY == information.asJsonObject().get(PROVIDES).getValueType()) {
|
|
for (String provide : information.asJsonObject().getJsonArray(PROVIDES).stream()
|
|
.filter(val -> JsonValue.ValueType.STRING == val.getValueType()).map(JsonValue::toString)
|
|
.map(DataAvailability::stripQuotes).toList()) {
|
|
providesMap.put(provide, Boolean.TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Strip double quotes (") from a string
|
|
*
|
|
* @param string A string that may have quotes at the beginning, the end, or
|
|
* both
|
|
* @return A string that doesn't have quotes at the beginning or end
|
|
*/
|
|
public static String stripQuotes(String string) {
|
|
return string.replaceAll("((^\")|(\"$))", "");
|
|
}
|
|
|
|
/**
|
|
* Get the global instance that should be used to check for data availability
|
|
*
|
|
* @return the unique instance
|
|
*/
|
|
public static DataAvailability getInstance() {
|
|
if (instance == null || COUNTRIES.isEmpty() || MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) {
|
|
instance = new DataAvailability();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* Check if a bounds may have data
|
|
*
|
|
* @param bounds An area that may have data
|
|
* @return True if one of the corners of the {@code bounds} is in a country with
|
|
* available data.
|
|
*/
|
|
public boolean hasData(Bounds bounds) {
|
|
final List<LatLon> corners = new ArrayList<>();
|
|
corners.add(bounds.getMin());
|
|
corners.add(new LatLon(bounds.getMinLat(), bounds.getMaxLon()));
|
|
corners.add(bounds.getMax());
|
|
corners.add(new LatLon(bounds.getMaxLat(), bounds.getMinLon()));
|
|
corners.add(bounds.getCenter());
|
|
return corners.stream().anyMatch(this::hasData);
|
|
}
|
|
|
|
/**
|
|
* Check if a latlon point may have data
|
|
*
|
|
* @param latLon A point that may have data from MapWithAI
|
|
* @return true if it is in an ares with data from MapWithAI
|
|
*/
|
|
public boolean hasData(LatLon latLon) {
|
|
for (final Map.Entry<String, Map<String, Boolean>> entry : COUNTRIES.entrySet()) {
|
|
Logging.debug(entry.getKey());
|
|
if (Territories.isIso3166Code(entry.getKey(), latLon)) {
|
|
return entry.getValue().entrySet().stream().anyMatch(Map.Entry::getValue);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get data types that may be visible around a point
|
|
*
|
|
* @param latLon The point of interest
|
|
* @return A map that may have available data types (or be empty)
|
|
*/
|
|
public static Map<String, Boolean> getDataTypes(LatLon latLon) {
|
|
return COUNTRIES.entrySet().stream().filter(entry -> Territories.isIso3166Code(entry.getKey(), latLon))
|
|
.map(Map.Entry::getValue).findFirst().orElse(Collections.emptyMap());
|
|
}
|
|
|
|
/**
|
|
* Get the URL that this class is responsible for
|
|
*
|
|
* @return The url (e.g., example.com/addresses/{bbox}), or null if generic
|
|
*/
|
|
public String getUrl() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the Terms Of Use Url
|
|
*
|
|
* @return The url or ""
|
|
*/
|
|
public String getTermsOfUseUrl() {
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Get the Privacy Policy Url
|
|
*
|
|
* @return The url or the TOS url (sometimes a privacy policy is in the TOS).
|
|
* The TOS url may be an empty string.
|
|
*/
|
|
public String getPrivacyPolicyUrl() {
|
|
return getTermsOfUseUrl();
|
|
}
|
|
|
|
/**
|
|
* Get the terms of use for all specially handled URL's
|
|
*
|
|
* @return List of terms of use urls
|
|
*/
|
|
public static List<String> getTermsOfUse() {
|
|
return Stream.concat(MapWithAILayerInfo.getInstance().getLayers().stream().map(MapWithAIInfo::getTermsOfUseURL),
|
|
DATA_SOURCES.stream().map(clazz -> {
|
|
try {
|
|
return clazz.getConstructor().newInstance().getTermsOfUseUrl();
|
|
} catch (ReflectiveOperationException e) {
|
|
Logging.debug(e);
|
|
}
|
|
return "";
|
|
})).filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty())
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Get the privacy policy for all specially handled URL's
|
|
*
|
|
* @return List of privacy policy urls
|
|
*/
|
|
public static List<String> getPrivacyPolicy() {
|
|
return Stream
|
|
.concat(MapWithAILayerInfo.getInstance().getLayers().stream().map(MapWithAIInfo::getPrivacyPolicyURL),
|
|
DATA_SOURCES.stream().map(clazz -> {
|
|
try {
|
|
return clazz.getConstructor().newInstance().getPrivacyPolicyUrl();
|
|
} catch (ReflectiveOperationException e) {
|
|
Logging.debug(e);
|
|
}
|
|
return "";
|
|
}))
|
|
.filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty()).distinct()
|
|
.collect(Collectors.toList());
|
|
}
|
|
}
|