Fix an NPE caused by Territories in JOSM

Also add spotless for code formatting

Signed-off-by: Taylor Smock <taylor.smock@kaart.com>
pull/1/head
Taylor Smock 2020-04-21 11:48:50 -06:00
rodzic 96294ec888
commit ddcf69001b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 625F6A74A3E4311A
17 zmienionych plików z 87 dodań i 192 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ plugins {
id "com.github.spotbugs" version "3.0.0"
id "org.openstreetmap.josm" version "0.6.5"
id "net.ltgt.errorprone" version "1.1.1"
id "com.diffplug.gradle.spotless" version "3.28.1"
//id 'de.aaschmid.cpd' version '2.0'
}
@ -125,6 +126,17 @@ jacocoTestCoverageVerification {
}
}
spotless {
java {
licenseHeader "// License: GPL. For details, see LICENSE file."
removeUnusedImports()
endWithNewline()
indentWithSpaces(4)
eclipse().configFile "config/josm_formatting.xml"
trimTrailingWhitespace()
}
}
josm {
manifest {
oldVersionDownloadLink 15820, "v1.3.7", new URL("https://gokaart.gitlab.io/JOSM_MapWithAI/dist/v1.3.7/mapwithai.jar")

Wyświetl plik

@ -28,7 +28,7 @@ import org.openstreetmap.josm.tools.Utils;
public class DataAvailability {
/** This points to a list of default sources that can be used with MapWithAI */
public static final String DEFAULT_SERVER_URL = "https://gokaart.gitlab.io/JOSM_MapWithAI/json/sources.json";
public static String DEFAULT_SERVER_URL = "https://gokaart.gitlab.io/JOSM_MapWithAI/json/sources.json";
/** A map of tag -&gt; message of possible data types */
static final Map<String, String> POSSIBLE_DATA_POINTS = new TreeMap<>();
@ -44,9 +44,7 @@ public class DataAvailability {
* This holds classes that can give availability of data for a specific service
*/
private static final List<Class<? extends DataAvailability>> DATA_SOURCES = new ArrayList<>();
static {
DATA_SOURCES.add(MapWithAIAvailability.class);
}
/**
* A map of countries to a map of available types
* ({@code Map<Country, Map<Type, IsAvailable>>}
@ -264,4 +262,13 @@ public class DataAvailability {
return "";
}).filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty()).collect(Collectors.toList());
}
/**
* Set the URL to use to get MapWithAI information
*
* @param url The URL which serves MapWithAI servers
*/
public static void setReleaseUrl(String url) {
DEFAULT_SERVER_URL = url;
}
}

Wyświetl plik

@ -1,146 +0,0 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Stream;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonValue;
import javax.json.stream.JsonParser;
import org.openstreetmap.josm.io.CachedFile;
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Territories;
public final class MapWithAIAvailability extends DataAvailability {
private static String rapidReleases = "https://raw.githubusercontent.com/facebookmicrosites/Open-Mapping-At-Facebook/master/data/rapid_releases.geojson";
/** Original country, replacement countries */
private static final Map<String, Collection<String>> COUNTRY_NAME_FIX = new HashMap<>();
static {
COUNTRY_NAME_FIX.put("Egypt", Collections.singleton("Egypt, Arab Rep."));
COUNTRY_NAME_FIX.put("Dem. Rep. Congo", Collections.singleton("Congo, Dem. Rep."));
COUNTRY_NAME_FIX.put("Democratic Republic of the Congo", Collections.singleton("Congo, Dem. Rep."));
COUNTRY_NAME_FIX.put("eSwatini", Collections.singleton("Swaziland"));
COUNTRY_NAME_FIX.put("Gambia", Collections.singleton("Gambia, The"));
COUNTRY_NAME_FIX.put("The Bahamas", Collections.singleton("Bahamas, The"));
COUNTRY_NAME_FIX.put("Ivory Coast", Collections.singleton("Côte d'Ivoire"));
COUNTRY_NAME_FIX.put("Somaliland", Collections.singleton("Somalia")); // Technically a self-declared independent
// area of Somalia
COUNTRY_NAME_FIX.put("Carribean Countries",
Arrays.asList("Antigua and Barbuda", "Anguilla", "Barbados", "British Virgin Islands", "Cayman Islands",
"Dominica", "Dominican Republic", "Grenada", "Guadeloupe", "Haiti", "Jamaica", "Martinique",
"Montserrat", "Puerto Rico", "Saba", "Saint-Barthélemy", "Saint-Martin", "Sint Eustatius",
"Sint Maarten", "St. Kitts and Nevis", "St. Lucia", "St. Vincent and the Grenadines",
"Turks and Caicos Islands"));
COUNTRY_NAME_FIX.put("Falkland Islands (Islas Maldivas)", Collections.singleton("Falkland Islands"));
COUNTRY_NAME_FIX.put("Laos", Collections.singleton("Lao PDR"));
COUNTRY_NAME_FIX.put("East Timor", Collections.singleton("Timor-Leste"));
COUNTRY_NAME_FIX.put("Congo", Collections.singleton("Congo, Rep."));
COUNTRY_NAME_FIX.put("North Macedonia", Collections.singleton("Macedonia, FYR"));
COUNTRY_NAME_FIX.put("Venezuela", Collections.singleton("Venezuela, RB"));
COUNTRY_NAME_FIX.put("South Korea", Collections.singleton("Korea, Rep."));
COUNTRY_NAME_FIX.put("Kyrgyzstan", Collections.singleton("Kyrgyz Republic"));
COUNTRY_NAME_FIX.put("Northern Cyprus", Collections.singleton("Cyprus"));
COUNTRY_NAME_FIX.put("Yemen", Collections.singleton("Yemen, Rep."));
POSSIBLE_DATA_POINTS.put("highway", "RapiD roads available");
POSSIBLE_DATA_POINTS.put("building", "MS buildings available");
}
public MapWithAIAvailability() {
super();
try (CachedFile cachedRapidReleases = new CachedFile(rapidReleases);
JsonParser parser = Json.createParser(cachedRapidReleases.getContentReader())) {
cachedRapidReleases.setMaxAge(604_800);
parser.next();
final Stream<Entry<String, JsonValue>> entries = parser.getObjectStream();
final Optional<Entry<String, JsonValue>> objects = entries.filter(entry -> "objects".equals(entry.getKey()))
.findFirst();
if (objects.isPresent()) {
final JsonObject value = objects.get().getValue().asJsonObject();
if (value != null) {
final JsonObject centroid = value.getJsonObject("rapid_releases_points");
if (centroid != null) {
final JsonArray countries = centroid.getJsonArray("geometries");
if (countries != null) {
COUNTRIES.clear();
COUNTRIES.putAll(parseForCountries(countries));
}
}
}
}
} catch (IOException e) {
Logging.debug(e);
}
}
private static Map<String, Map<String, Boolean>> parseForCountries(JsonArray countries) {
final Map<String, Map<String, Boolean>> returnCountries = new TreeMap<>();
Territories.initialize();
for (int i = 0; i < countries.size(); i++) {
final JsonObject country = countries.getJsonObject(i).getJsonObject("properties");
for (String countryName : cornerCaseNames(country.getString("Country"))) {
final Optional<String> realCountryISO = Territories.getOriginalDataSet().allPrimitives()
.parallelStream()
.filter(o -> o.hasKey("name:en") && o.get("name:en").equalsIgnoreCase(countryName))
.map(o -> o.hasKey("ISO3166-1:alpha2") ? o.get("ISO3166-1:alpha2") : o.get("ISO3166-2"))
.min(Comparator.comparing(String::length));
if (realCountryISO.isPresent()) {
String key = realCountryISO.get();
// We need to handle cases like Alaska more elegantly
final Map<String, Boolean> data = returnCountries.getOrDefault(key, new TreeMap<>());
for (final Entry<String, String> entry : POSSIBLE_DATA_POINTS.entrySet()) {
final boolean hasData = "yes".equals(country.getString(entry.getValue()));
if (hasData || !data.containsKey(entry.getKey())) {
data.put(entry.getKey(), hasData);
}
}
returnCountries.put(key, data);
} else {
Logging.error(tr("{0}: We couldn''t find {1}", MapWithAIPlugin.NAME, countryName));
}
}
}
return returnCountries;
}
private static Collection<String> cornerCaseNames(String name) {
return COUNTRY_NAME_FIX.containsKey(name) ? COUNTRY_NAME_FIX.get(name) : Collections.singleton(name);
}
/**
* @param url The URL where the MapWithAI data releases are.
*/
public static void setReleaseUrl(String url) {
rapidReleases = url;
}
@Override
public String getUrl() {
return MapWithAIPreferenceHelper.DEFAULT_MAPWITHAI_API;
}
@Override
public String getTermsOfUseUrl() {
return "https://mapwith.ai/doc/license/MapWithAILicense.pdf";
}
@Override
public String getPrivacyPolicyUrl() {
return "https://mapwith.ai/doc/license/MapWithAIPrivacyPolicy.pdf#page=3";
}
}

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.gui.help.HelpUtil.ht;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.openstreetmap.josm.tools.I18n.tr;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.commands;
import static org.openstreetmap.josm.tools.I18n.tr;

Wyświetl plik

@ -82,4 +82,4 @@ public class ConnectingNodeInformationTest extends Test {
}
}
}
}

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.data.validation.tests;
import static org.openstreetmap.josm.tools.I18n.marktr;

Wyświetl plik

@ -124,8 +124,7 @@ public class MapWithAIParametersPanel extends JPanel {
private static List<Object[]> getHeadersAsVector(Map<String, Pair<String, Boolean>> headers) {
return headers.entrySet().stream().sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
.map(e -> new Object[] { e.getKey(), e.getValue().a, e.getValue().b })
.collect(Collectors.toList());
.map(e -> new Object[] { e.getKey(), e.getValue().a, e.getValue().b }).collect(Collectors.toList());
}
/**

Wyświetl plik

@ -1,6 +1,11 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@ -17,21 +22,17 @@ import javax.json.JsonReader;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.imagery.ImageryInfo;
import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
import org.openstreetmap.josm.data.imagery.Shape;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.io.CachedFile;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
import org.openstreetmap.josm.tools.DefaultGeoProperty;
import org.openstreetmap.josm.tools.GeoPropertyIndex;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Territories;
import org.openstreetmap.josm.tools.Utils;
@ -104,32 +105,29 @@ public class MapWithAISourceReader implements Closeable {
List<ImageryBounds> bounds = new ArrayList<>();
if (JsonValue.ValueType.OBJECT.equals(countries.getValueType())) {
Set<String> codes = Territories.getKnownIso3166Codes();
DataSet ds = Territories.getOriginalDataSet();
for (Map.Entry<String, JsonValue> country : countries.asJsonObject().entrySet()) {
if (codes.contains(country.getKey())) {
Collection<OsmPrimitive> countryData = ds
.getPrimitives(i -> i.getKeys().containsValue(country.getKey()));
OsmPrimitive prim = countryData.iterator().next();
ImageryBounds tmp = new ImageryBounds(bboxToBoundsString(prim.getBBox(), ","), ",");
countryData
.stream().map(OsmPrimitive::getBBox).map(b -> new Bounds(b.getBottomRightLat(),
b.getTopLeftLon(), b.getTopLeftLat(), b.getBottomRightLon()))
.forEach(tmp::extend);
countryData.stream().filter(Way.class::isInstance).map(Way.class::cast)
.map(MapWithAISourceReader::wayToShape).forEach(tmp::addShape);
// This doesn't subtract inner ways. TODO?
countryData.stream().filter(Relation.class::isInstance).map(Relation.class::cast)
.flatMap(r -> r.getMembers().stream().filter(m -> "outer".equals(m.getRole()))
.map(RelationMember::getMember).filter(Way.class::isInstance)
.map(Way.class::cast))
.map(MapWithAISourceReader::wayToShape).forEach(tmp::addShape);
bounds.add(tmp);
GeoPropertyIndex<Boolean> geoPropertyIndex = Territories.getGeoPropertyIndex(country.getKey());
if (geoPropertyIndex.getGeoProperty() instanceof DefaultGeoProperty) {
DefaultGeoProperty prop = (DefaultGeoProperty) geoPropertyIndex.getGeoProperty();
Rectangle2D areaBounds = prop.getArea().getBounds2D();
ImageryBounds tmp = new ImageryBounds(bboxToBoundsString(new BBox(areaBounds.getMinX(),
areaBounds.getMinY(), areaBounds.getMaxX(), areaBounds.getMaxY()), ","), ",");
areaToShapes(prop.getArea()).forEach(tmp::addShape);
bounds.add(tmp);
}
}
}
}
MapWithAIInfo info = new MapWithAIInfo(name, url, type, eula, id);
info.setDefaultEntry(values.getBoolean("default", false));
info.setParameters(values.getJsonArray("parameters"));
if (values.containsKey("terms_of_use_url")) {
info.setTermsOfUseText(values.getString("terms_of_use_url"));
}
if (values.containsKey("privacy_policy_url")) {
info.setPrivacyPolicyURL(values.getString("privacy_policy_url"));
}
if (!bounds.isEmpty()) {
ImageryBounds bound = bounds.get(0);
bounds.remove(0);
@ -142,18 +140,40 @@ public class MapWithAISourceReader implements Closeable {
return new MapWithAIInfo(name);
}
private static Collection<Shape> areaToShapes(java.awt.Shape shape) {
PathIterator iterator = shape.getPathIterator(new AffineTransform());
Shape defaultShape = new Shape();
Collection<Shape> shapes = new ArrayList<>();
float[] moveTo = null;
while (!iterator.isDone()) {
float[] coords = new float[6];
int type = iterator.currentSegment(coords);
if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO) {
if (type == PathIterator.SEG_MOVETO) {
moveTo = coords;
}
defaultShape.addPoint(Float.toString(coords[1]), Float.toString(coords[0]));
} else if (type == PathIterator.SEG_CLOSE && moveTo != null && moveTo.length >= 2) {
defaultShape.addPoint(Float.toString(moveTo[1]), Float.toString(moveTo[0]));
shapes.add(defaultShape);
defaultShape = new Shape();
} else {
Logging.error(tr("No implementation for converting a segment of type {0} to coordinates", type));
}
iterator.next();
}
if (!defaultShape.getPoints().isEmpty()) {
shapes.add(defaultShape);
}
return shapes;
}
private static String bboxToBoundsString(BBox bbox, String separator) {
return String.join(separator, LatLon.cDdFormatter.format(bbox.getBottomRightLat()),
LatLon.cDdFormatter.format(bbox.getTopLeftLon()), LatLon.cDdFormatter.format(bbox.getTopLeftLat()),
LatLon.cDdFormatter.format(bbox.getBottomRightLon()));
}
private static Shape wayToShape(Way way) {
return new Shape(way.getNodes().stream().map(Node::getCoor)
.map(l -> Double.toString(l.lat()) + "," + Double.toString(l.lon())).collect(Collectors.joining(",")),
",");
}
/**
* Sets whether opening HTTP connections should fail fast, i.e., whether a
* {@link HttpClient#setConnectTimeout(int) low connect timeout} should be used.

Wyświetl plik

@ -29,7 +29,6 @@ import org.openstreetmap.josm.data.osm.WaySegment;
import org.openstreetmap.josm.data.projection.ProjectionRegistry;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
import org.openstreetmap.josm.plugins.mapwithai.gui.preferences.MapWithAILayerInfoTest;
import org.openstreetmap.josm.testutils.JOSMTestRules;
import org.openstreetmap.josm.tools.Geometry;

Wyświetl plik

@ -35,7 +35,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class MapWithAIActionTest {
@Rule
@SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
public JOSMTestRules test = new JOSMTestRules().main().projection().timeout(100000);
public JOSMTestRules test = new JOSMTestRules().main().projection().territories().timeout(100000);
private MapWithAIAction action;
@ -45,9 +45,7 @@ public class MapWithAIActionTest {
public void setUp() {
action = new MapWithAIAction();
wireMock.start(); // This is required to avoid failing a test in MapWithAIAvailabilityTest
MapWithAIAvailability.setReleaseUrl(
wireMock.baseUrl() + "/facebookmicrosites/Open-Mapping-At-Facebook/master/data/rapid_releases.geojson");
Territories.initialize();
DataAvailability.setReleaseUrl(wireMock.baseUrl() + "/JOSM_MapWithAI/json/sources.json");
LatLon temp = new LatLon(40, -100);
await().atMost(Durations.TEN_SECONDS).until(() -> Territories.isIso3166Code("US", temp));
}

Wyświetl plik

@ -1,6 +1,4 @@
/**
*
*/
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import static org.junit.jupiter.api.Assertions.assertEquals;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.commands.conflation;
import static org.openstreetmap.josm.tools.I18n.tr;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.data.validation.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.data.validation.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;

Wyświetl plik

@ -1,3 +1,4 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.testutils;
import static org.openstreetmap.josm.tools.I18n.tr;