Porównaj commity

...

24 Commity
v815 ... master

Autor SHA1 Wiadomość Data
Dirk Stöcker f3659fc3ed fix JOSM repository access 2025-04-02 18:10:06 +02:00
Dirk Stöcker cdb1889864 drop deprecated isBlank usage 2025-04-02 17:44:19 +02:00
Taylor Smock 3bca38d512
Fix tests
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-11-11 08:27:09 -07:00
Taylor Smock 9b7d424f7b
Handle sources with . in name by replacing them with _
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-31 15:13:42 -06:00
Taylor Smock 551e26ef8e
Performance improvements
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-31 13:55:36 -06:00
Taylor Smock 87274baa62
Add overture data sources
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-31 12:07:05 -06:00
Taylor Smock 629e34f413
Fix #23971: Give a nicer error message when a server is down
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-21 07:28:26 -06:00
Taylor Smock 817b633660
Significantly improve performance for StreetAddressTest
This is mostly done by reducing the amount of garbage collection that
needs to occur.

Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-15 13:26:16 -06:00
Taylor Smock 5d9df1fba4
Update wiremock tests
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-10-15 13:13:39 -06:00
Taylor Smock cecb9f40be
Fix UI freeze during download of Esri layers
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-04-15 15:06:52 -06:00
Taylor Smock 7f725f7e4c
Update gradle build dependencies
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-03-12 08:00:47 -06:00
Taylor Smock f7c1d3d8b0
Fix #23529: JSON downloads may wait on EDT while EDT is waiting on downloads to finish
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-03-12 06:46:53 -06:00
Taylor Smock fbd3f10541
Use v2 JOSMPluginAction scripts
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-01-17 06:41:52 -07:00
Taylor Smock faa031e49c
Fix issue where the sources would not be updated
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-01-17 06:24:08 -07:00
Taylor Smock ca0b8e479f
Fix an issue where a custom source would have "&" for the first query parameter instead of "?"
Signed-off-by: Taylor Smock <tsmock@meta.com>
2024-01-17 06:24:08 -07:00
Taylor Smock ad6ec16acd
Don't download sources multiple times when user asks for refresh multiple times in different locations
Signed-off-by: Taylor Smock <smocktaylor@gmail.com>
2024-01-05 06:37:47 -07:00
Taylor Smock d56513358d
Fix #23390: Progress Monitor needs to send child monitors to avoid being in the finished state
Signed-off-by: Taylor Smock <smocktaylor@gmail.com>
2024-01-05 06:16:54 -07:00
Taylor Smock eff911c300
Extract TileXYZ and add some basic tests
Signed-off-by: Taylor Smock <smocktaylor@gmail.com>
2024-01-03 11:18:39 -07:00
Hermann Schwarting d13d61f13c PMTiles: invert y axis of tile coordinates
The y axis of XYZ tiles is oriented from north to south. When
enumerating tiles for a given bounding box, the y coordinates have to be
enumerated from north/top to south/bottom.
2024-01-03 17:45:24 +01:00
Taylor Smock 68b9ff91c1
Set minimum Java version
Signed-off-by: Taylor Smock <smocktaylor@gmail.com>
2024-01-02 14:44:15 -07:00
Taylor Smock 225abbb685
Avoid using worker thread for getting sources -- this can block data downloads
Signed-off-by: Taylor Smock <tsmock@meta.com>
2023-11-29 11:30:16 -07:00
Taylor Smock e8c6e96217
Remove unnecessary test rules
Signed-off-by: Taylor Smock <tsmock@meta.com>
2023-11-29 11:30:15 -07:00
Taylor Smock e796f49d8e
See #23220: Use jakarta.annotation instead of javax.annotation (JSR305)
Some lint issues were also fixed.

Signed-off-by: Taylor Smock <tsmock@meta.com>
2023-10-25 10:49:59 -06:00
Taylor Smock 4af7ac225f
Use max zoom from pmtiles and guess zoom for new MVT layers
Signed-off-by: Taylor Smock <tsmock@meta.com>
2023-10-04 08:03:26 -06:00
118 zmienionych plików z 1890 dodań i 793 usunięć

Wyświetl plik

@ -10,20 +10,18 @@ on:
branches:
- master
- $default-branch
schedule:
- cron: "1 5 * * 6"
workflow_dispatch:
jobs:
call-workflow:
strategy:
matrix:
josm-revision: ["", "r18723"]
uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v1
josm-revision: ["", "r19067"]
uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v3
with:
java-version: 17
josm-revision: ${{ matrix.josm-revision }}
plugin-jar-name: 'mapwithai'
perform-revision-tagging: ${{ github.repository == 'JOSM/MapWithAI' && github.ref_type == 'branch' && github.ref_name == 'master' && github.event_name != 'schedule' && github.event_name != 'pull_request' && matrix.josm-revision == 'r18723' }}
perform-revision-tagging: ${{ matrix.josm-revision == 'r19067' && github.repository == 'JOSM/MapWithAI' && github.ref_type == 'branch' && github.ref_name == 'master' && github.event_name != 'schedule' && github.event_name != 'pull_request' }}
secrets: inherit

Wyświetl plik

@ -10,4 +10,4 @@ permissions:
jobs:
call-workflow:
uses: JOSM/JOSMPluginAction/.github/workflows/reports.yaml@v1
uses: JOSM/JOSMPluginAction/.github/workflows/reports.yaml@v3

Wyświetl plik

@ -1,9 +1,9 @@
import groovy.xml.XmlParser
plugins {
id "com.diffplug.spotless" version "6.20.0"
id "com.github.ben-manes.versions" version "0.47.0"
id "com.github.spotbugs" version "5.0.14"
id "com.diffplug.spotless" version "6.25.0"
id "com.github.ben-manes.versions" version "0.51.0"
id "com.github.spotbugs" version "6.0.8"
// id "de.aaschmid.cpd" version "3.3"
id "eclipse"
id "jacoco"
@ -12,7 +12,7 @@ plugins {
id "maven-publish"
id "net.ltgt.errorprone" version "3.1.0"
id "org.openstreetmap.josm" version "0.8.2"
id "org.sonarqube" version "4.2.1.3168"
id "org.sonarqube" version "4.4.1.3373"
id "pmd"
}
@ -29,15 +29,15 @@ repositories {
def versions = [
awaitility: "4.2.0",
equalsverifier: "3.15",
errorprone: "2.20.0",
findsecbugs: "1.12.0",
equalsverifier: "3.15.8",
errorprone: "2.26.0",
findsecbugs: "1.13.0",
jacoco: "0.8.10",
jmockit: "1.49.a",
josm: properties.get("plugin.compile.version"),
junit: "5.9.1",
junit: "5.10.2",
pmd: "6.20.0",
spotbugs: "4.7.3",
spotbugs: "4.8.3",
wiremock: "2.35.0",
]

Wyświetl plik

@ -7,6 +7,7 @@
<!-- edit the properties of this plugin in the file `gradle.properties` -->
<property file="${basedir}/gradle.properties"/>
<property name="java.lang.version" value="17"/>
<property name="plugin.minimum.java.version" value="17"/>
<!-- ** include targets that all plugins have in common ** -->
<import file="../build-common.xml"/>

Wyświetl plik

@ -1,9 +1,9 @@
# The minimum JOSM version this plugin is compatible with (can be any numeric version
plugin.main.version = 18723
plugin.main.version = 19067
# The JOSM version this plugin is currently compiled against
# Please make sure this version is available at https://josm.openstreetmap.de/download
# The special values "latest" and "tested" are also possible here, but not recommended.
plugin.compile.version = 18724
plugin.compile.version = 19067
plugin.canloadatruntime = true
plugin.author = Taylor Smock
plugin.class = org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,7 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

12
gradlew vendored
Wyświetl plik

@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,11 +130,14 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

193
pom.xml 100644
Wyświetl plik

@ -0,0 +1,193 @@
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openstreetmap.josm.plugins</groupId>
<artifactId>MapWithAI</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<jmockit.version>1.49.a</jmockit.version>
<pmd.version>7.5.0</pmd.version>
<jacoco.version>0.8.12</jacoco.version>
<checkstyle.version>10.18.1</checkstyle.version>
<spotbugs.version>4.8.6</spotbugs.version>
</properties>
<repositories>
<repository>
<id>JOSM-releases</id>
<url>https://josm.openstreetmap.de/repository/releases/</url>
</repository>
<repository>
<id>JOSM-snapshots</id>
<url>https://josm.openstreetmap.de/repository/snapshots/</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.openstreetmap.josm</groupId>
<artifactId>josm</artifactId>
<version>1.5-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openstreetmap.josm.plugins</groupId>
<artifactId>pmtiles</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openstreetmap.josm.plugins</groupId>
<artifactId>utilsplugin2</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openstreetmap.josm</groupId>
<artifactId>josm-unittest</artifactId>
<version>1.5-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency> <!-- needed until JOSM core removes Junit4 support -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>3.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49.a</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<testSourceDirectory>src/test/unit</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.43.0</version>
<configuration>
<java>
<eclipse>
<version>4.21</version>
<file>${project.basedir}/../00_core_tools/eclipse/formatter.xml</file>
</eclipse>
<removeUnusedImports/>
<licenseHeader>
<content>// License: GPL. For details, see LICENSE file.</content>
</licenseHeader>
<includes>
<include>src/main/java/**/*.java</include>
<include>src/test/unit/**/*.java</include>
<include>src/test/integration/**/*.java</include>
</includes>
</java>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<configuration>
<rules>
<requireMavenVersion>
<version>3.6.3</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
<!-- Configure the test plugin, specifically enable autodetection of global extensions -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<skipAfterFailureCount>1</skipAfterFailureCount>
<argLine>-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar</argLine>
<properties>
<configurationParameters>
file.encoding = UTF-8
java.locale.providers = SPI,CLDR
junit.jupiter.extensions.autodetection.enabled = true
junit.jupiter.execution.parallel.enabled = true
</configurationParameters>
</properties>
<systemPropertyVariables>
<josm.home>src/test/build/config/josm.home</josm.home>
<josm.test.data>src/test/resources</josm.test.data>
<java.awt.headless>true</java.awt.headless>
<glass.platform>Monocle</glass.platform>
<monocle.platform>Headless</monocle.platform>
<prism.order>sw</prism.order>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

Wyświetl plik

@ -3,8 +3,6 @@ package org.openstreetmap.josm.plugins.mapwithai;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.swing.JMenuItem;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
@ -12,6 +10,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JMenuItem;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.actions.PreferencesAction;
import org.openstreetmap.josm.data.validation.OsmValidator;
@ -29,6 +29,7 @@ import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;
import org.openstreetmap.josm.plugins.mapwithai.backend.DownloadListener;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIAction;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIMoveAction;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIObject;
@ -135,7 +136,8 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
MainApplication.worker.execute(() -> UpdateProd.doProd(info.mainversion));
// Preload the MapWithAILayerInfo for the JOSM download window
// This reduces the amount of time taken for first button click by 100ms.
MainApplication.worker.execute(MapWithAILayerInfo::getInstance);
// Don't use the worker thread to avoid blocking user downloads
MapWithAIDataUtils.getForkJoinPool().execute(MapWithAILayerInfo::getInstance);
destroyables.add(new MapWithAICopyProhibit());
}

Wyświetl plik

@ -84,10 +84,17 @@ public class AddMapWithAILayerAction extends JosmAction implements AdaptableActi
@Override
public void actionPerformed(ActionEvent e) {
if (!isEnabled()) {
return;
if (isEnabled()) {
MainApplication.worker.execute(() -> realRun(this.info));
}
}
/**
* Run the download tasks. This should be run off of the EDT, see #23529.
*
* @param info The external data to download
*/
private static void realRun(MapWithAIInfo info) {
MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
final DataSet ds;
final OsmData<?, ?, ?, ?> boundsSource;

Wyświetl plik

@ -13,13 +13,17 @@ import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -64,11 +68,13 @@ import org.openstreetmap.josm.plugins.mapwithai.tools.MapPaintUtils;
import org.openstreetmap.josm.plugins.pmtiles.data.imagery.PMTilesImageryInfo;
import org.openstreetmap.josm.plugins.pmtiles.gui.layers.PMTilesImageSource;
import org.openstreetmap.josm.plugins.pmtiles.lib.DirectoryCache;
import org.openstreetmap.josm.plugins.pmtiles.lib.Header;
import org.openstreetmap.josm.plugins.pmtiles.lib.PMTiles;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.JosmRuntimeException;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Utils;
import jakarta.json.Json;
import jakarta.json.JsonValue;
@ -80,91 +86,6 @@ import jakarta.json.stream.JsonParser;
* @author Taylor Smock
*/
public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
private record TileXYZ(int x, int y, int z) {
/**
* Checks to see if the given bounds are functionally equal to this tile
*
* @param left left
* @param bottom bottom
* @param right right
* @param top top
*/
boolean checkBounds(double left, double bottom, double right, double top) {
final var thisLeft = xToLongitude(this.x, this.z);
final var thisRight = xToLongitude(this.x + 1, this.z);
final var thisBottom = yToLatitude(this.y + 1, this.z);
final var thisTop = yToLatitude(this.y, this.z);
return equalsEpsilon(thisLeft, left, this.z) && equalsEpsilon(thisRight, right, this.z)
&& equalsEpsilon(thisBottom, bottom, this.z) && equalsEpsilon(thisTop, top, this.z);
}
private static boolean equalsEpsilon(double first, double second, int z) {
// 0.1% of tile size is considered to be "equal"
final var maxDiff = (360 / Math.pow(2, z)) / 1000;
final var diff = Math.abs(first - second);
return diff <= maxDiff;
}
private static double xToLongitude(int x, int z) {
return (x / Math.pow(2, z)) * 360 - 180;
}
private static double yToLatitude(int y, int z) {
var t = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return 180 / Math.PI * Math.atan((Math.exp(t) - Math.exp(-t)) / 2);
}
/**
* Convert a bbox to a tile
*
* @param bounds The bounds to convert
* @return The tile
*/
private static TileXYZ tileFromBBox(Bounds bounds) {
return tileFromBBox(bounds.getMinLon(), bounds.getMinLat(), bounds.getMaxLon(), bounds.getMaxLat());
}
/**
* Checks to see if the given bounds are functionally equal to this tile
*
* @param left left lon
* @param bottom bottom lat
* @param right right lon
* @param top top lat
*/
private static TileXYZ tileFromBBox(double left, double bottom, double right, double top) {
var zoom = 18;
while (zoom > 0) {
final var tile1 = tileFromLatLonZoom(left, bottom, zoom);
final var tile2 = tileFromLatLonZoom(right, top, zoom);
if (tile1.equals(tile2)) {
return tile1;
} else if (tile1.checkBounds(left, bottom, right, top)) {
return tile1;
} else if (tile2.checkBounds(left, bottom, right, top)) {
return tile2;
// Just in case the coordinates are _barely_ in other tiles and not the "common"
// tile
} else if (Math.abs(tile1.x() - tile2.x()) <= 2 && Math.abs(tile1.y() - tile2.y()) <= 2) {
final var tileT = new TileXYZ((tile1.x() + tile2.x()) / 2, (tile1.y() + tile2.y()) / 2, zoom);
if (tileT.checkBounds(left, bottom, right, top)) {
return tileT;
}
}
zoom--;
}
return new TileXYZ(0, 0, 0);
}
private static TileXYZ tileFromLatLonZoom(double lon, double lat, int zoom) {
var xCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom) * (180 + lon) / 360)));
var yCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom)
* (1 - (Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI))
/ 2)));
return new TileXYZ(xCoordinate, yCoordinate, zoom);
}
}
private final String url;
private final boolean crop;
private final int start;
@ -209,18 +130,28 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
if (url.contains("{x}") && url.contains("{y}") && url.contains("{z}")) {
final var tile = TileXYZ.tileFromBBox(lon1, lat1, lon2, lat2);
return getRequestForTile(tile);
}
var current = url.replace("{bbox}", Double.toString(lon1) + ',' + lat1 + ',' + lon2 + ',' + lat2)
.replace("{xmin}", Double.toString(lon1)).replace("{ymin}", Double.toString(lat1))
.replace("{xmax}", Double.toString(lon2)).replace("{ymax}", Double.toString(lat2));
boolean hasQuery = !Optional.ofNullable(URI.create(current).getRawQuery()).map(String::isEmpty).orElse(true);
if (crop) {
current += (hasQuery ? '&' : '?') + "crop_bbox="
+ DetectTaskingManagerUtils.getTaskingManagerBounds().toBBox().toStringCSV(",");
hasQuery = true;
}
if (this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER && !this.info.isConflated()) {
current += (hasQuery ? '&' : '?') + "resultOffset=" + this.start;
}
return current;
}
private String getRequestForTile(TileXYZ tile) {
return url.replace("{x}", Long.toString(tile.x())).replace("{y}", Long.toString(tile.y())).replace("{z}",
Long.toString(tile.z()));
}
return url.replace("{bbox}", Double.toString(lon1) + ',' + lat1 + ',' + lon2 + ',' + lat2)
.replace("{xmin}", Double.toString(lon1)).replace("{ymin}", Double.toString(lat1))
.replace("{xmax}", Double.toString(lon2)).replace("{ymax}", Double.toString(lat2))
+ (crop ? "&crop_bbox=" + DetectTaskingManagerUtils.getTaskingManagerBounds().toBBox().toStringCSV(",")
: "")
+ (this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER && !this.info.isConflated()
? "&resultOffset=" + this.start
: "");
}
@Override
public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
@ -355,25 +286,80 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
private DataSet readMvt(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
final DataSet ds;
final var tileXYZ = TileXYZ.tileFromBBox(this.downloadArea);
final TileSource tileSource;
final InputStream actualSource;
final List<TileXYZ> tiles;
final Header header;
final DirectoryCache cachedDirectories;
if (this.info.getSourceType() == MapWithAIType.PMTILES) {
final var hilbert = PMTiles.convertToHilbert(tileXYZ.z(), tileXYZ.x(), tileXYZ.y());
try {
final var header = PMTiles.readHeader(URI.create(this.url));
header = PMTiles.readHeader(URI.create(this.url));
final var root = PMTiles.readRootDirectory(header);
final var cachedDirectories = new DirectoryCache(root);
final var data = PMTiles.readData(header, hilbert, cachedDirectories);
tiles = TileXYZ.tilesFromBBox(header.maxZoom(), this.downloadArea).toList();
cachedDirectories = new DirectoryCache(root);
tileSource = new PMTilesImageSource(new PMTilesImageryInfo(header));
actualSource = new ByteArrayInputStream(data);
} catch (IOException e) {
throw new IllegalDataException(e);
}
} else {
actualSource = source;
header = null;
cachedDirectories = null;
// Assume the source is added by the user
final int zoom;
if (this.info.getMaxZoom() == 0) {
var z = 18;
for (; z > 0; z--) {
final var tileOptional = TileXYZ.tilesFromBBox(z, this.downloadArea).findFirst();
if (tileOptional.isPresent()) {
final var tile = tileOptional.get();
try {
final var responseHeader = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL).build()
.send(HttpRequest.newBuilder().uri(URI.create(this.getRequestForTile(tile)))
.method("HEAD", HttpRequest.BodyPublishers.noBody()).build(),
HttpResponse.BodyHandlers.discarding());
if (responseHeader.statusCode() >= 200 && responseHeader.statusCode() < 300) {
break;
}
} catch (IOException e) {
Logging.trace(e);
} catch (InterruptedException e) {
Logging.trace(e);
Thread.currentThread().interrupt();
return null;
}
}
}
zoom = z;
} else {
zoom = this.info.getMaxZoom();
}
tiles = TileXYZ.tilesFromBBox(zoom, this.downloadArea).toList();
tileSource = new MapboxVectorTileSource(new ImageryInfo(this.url, this.url));
}
ds = new DataSet();
final var currentBounds = new Bounds(this.downloadArea);
progressMonitor.beginTask(tr("Downloading data"), 2 * tiles.size());
for (TileXYZ tileXYZ : tiles) {
try {
final var hilbert = PMTiles.convertToHilbert(tileXYZ.z(), tileXYZ.x(), tileXYZ.y());
final var data = this.info.getSourceType() == MapWithAIType.PMTILES
? new ByteArrayInputStream(PMTiles.readData(header, hilbert, cachedDirectories))
: getInputStream(getRequestForTile(tileXYZ), progressMonitor.createSubTaskMonitor(1, true));
final var dataSet = loadTile(tileSource, tileXYZ, data);
ds.mergeFrom(dataSet, progressMonitor.createSubTaskMonitor(1, true));
tileXYZ.expandBounds(currentBounds);
} catch (OsmTransferException | IOException e) {
progressMonitor.finishTask();
throw new IllegalDataException(e);
}
}
progressMonitor.finishTask();
ds.addDataSource(new DataSource(currentBounds, this.url));
return ds;
}
private static DataSet loadTile(TileSource tileSource, TileXYZ tileXYZ, InputStream actualSource)
throws IllegalDataException {
final var tile = new MVTTile(tileSource, tileXYZ.x(), tileXYZ.y(), tileXYZ.z());
try {
@ -381,10 +367,11 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
} catch (IOException e) {
throw new IllegalDataException(e);
}
ds = new DataSet();
ds.addDataSource(new DataSource(this.downloadArea, this.url));
final var ds = new DataSet();
final var primitiveMap = new HashMap<PrimitiveId, OsmPrimitive>(tile.getData().getAllPrimitives().size());
for (VectorPrimitive p : tile.getData().getAllPrimitives()) {
for (Class<? extends VectorPrimitive> clazz : Arrays.asList(VectorNode.class, VectorWay.class,
VectorRelation.class)) {
for (VectorPrimitive p : Utils.filteredCollection(tile.getData().getAllPrimitives(), clazz)) {
final OsmPrimitive osmPrimitive;
if (p instanceof VectorNode node) {
osmPrimitive = new Node(node.getCoor());
@ -409,6 +396,7 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
ds.addPrimitive(osmPrimitive);
primitiveMap.put(p, osmPrimitive);
}
}
return ds;
}

Wyświetl plik

@ -36,7 +36,6 @@ public class DataAvailability {
static final Map<String, String> POSSIBLE_DATA_POINTS = new TreeMap<>();
private static final String PROVIDES = "provides";
private static final String EMPTY_STRING = "";
private static final int SEVEN_DAYS_IN_SECONDS = 604_800;
/**
@ -150,7 +149,7 @@ public class DataAvailability {
* @return A string that doesn't have quotes at the beginning or end
*/
public static String stripQuotes(String string) {
return string.replaceAll("((^\")|(\"$))", EMPTY_STRING);
return string.replaceAll("((^\")|(\"$))", "");
}
/**
@ -224,7 +223,7 @@ public class DataAvailability {
* @return The url or ""
*/
public String getTermsOfUseUrl() {
return EMPTY_STRING;
return "";
}
/**
@ -247,11 +246,10 @@ public class DataAvailability {
DATA_SOURCES.stream().map(clazz -> {
try {
return clazz.getConstructor().newInstance().getTermsOfUseUrl();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
} catch (ReflectiveOperationException e) {
Logging.debug(e);
}
return EMPTY_STRING;
return "";
})).filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty())
.collect(Collectors.toList());
}
@ -267,11 +265,10 @@ public class DataAvailability {
DATA_SOURCES.stream().map(clazz -> {
try {
return clazz.getConstructor().newInstance().getPrivacyPolicyUrl();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
} catch (ReflectiveOperationException e) {
Logging.debug(e);
}
return EMPTY_STRING;
return "";
}))
.filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty()).distinct()
.collect(Collectors.toList());

Wyświetl plik

@ -68,7 +68,7 @@ public class DataConflationSender implements RunnableFuture<DataSet> {
@Override
public void run() {
String url = MapWithAIConflationCategory.conflationUrlFor(category);
if (!Utils.isBlank(url) && !NetworkManager.isOffline(url)) {
if (!Utils.isStripEmpty(url) && !NetworkManager.isOffline(url)) {
this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
try {
final var form = new TreeMap<String, String>();

Wyświetl plik

@ -219,9 +219,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> {
if (!monitor.isCanceled()) {
if (bounds.size() == MAX_NUMBER_OF_BBOXES_TO_PROCESS) {
final var temporaryDataSet = getDataReal(bounds.get(0), monitor);
synchronized (this.dataSet) {
dataSet.mergeFrom(temporaryDataSet);
}
this.dataSet.update(() -> dataSet.mergeFrom(temporaryDataSet));
} else {
final Collection<GetDataRunnable> tasks = bounds.stream()
.map(bound -> new GetDataRunnable(bound, dataSet, monitor.createSubTaskMonitor(0, true)))
@ -250,10 +248,10 @@ public class GetDataRunnable extends RecursiveTask<DataSet> {
* @param info The information used to download the data
*/
public static void cleanup(DataSet dataSet, Bounds bounds, MapWithAIInfo info) {
realCleanup(dataSet, bounds, info);
dataSet.update(() -> realCleanup(dataSet, bounds, info));
}
private static synchronized void realCleanup(DataSet dataSet, Bounds bounds, MapWithAIInfo info) {
private static void realCleanup(DataSet dataSet, Bounds bounds, MapWithAIInfo info) {
final Bounds boundsToUse;
if (bounds == null && !dataSet.getDataSourceBounds().isEmpty()) {
boundsToUse = new Bounds(dataSet.getDataSourceBounds().get(0));
@ -405,7 +403,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> {
.map(entry -> new Pair<>(Tag.ofString(entry.getKey()), Tag.ofString(entry.getValue())))
.collect(Collectors.toMap(pair -> pair.a, pair -> pair.b));
MapWithAIPreferenceHelper.getReplacementTags().entrySet().stream()
.filter(entry -> !entry.getKey().equals(EQUALS) && Utils.isBlank(entry.getValue()))
.filter(entry -> !entry.getKey().equals(EQUALS) && Utils.isStripEmpty(entry.getValue()))
.map(entry -> new Tag(entry.getKey(), null)).forEach(tag -> replaceTags.put(tag, tag));
replaceTags(dataSet, replaceTags);
}
@ -419,7 +417,7 @@ public class GetDataRunnable extends RecursiveTask<DataSet> {
public static void replaceTags(DataSet dataSet, Map<Tag, Tag> replaceTags) {
replaceTags.forEach((orig, replace) -> dataSet.allNonDeletedPrimitives().stream()
.filter(prim -> prim.hasTag(orig.getKey(), orig.getValue())
|| (prim.hasKey(orig.getKey()) && Utils.isBlank(orig.getValue())))
|| (prim.hasKey(orig.getKey()) && Utils.isStripEmpty(orig.getValue())))
.forEach(prim -> prim.put(replace)));
}

Wyświetl plik

@ -18,6 +18,7 @@ import java.util.Optional;
import java.util.TreeSet;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.openstreetmap.josm.data.Bounds;
@ -127,7 +128,7 @@ public final class MapWithAIDataUtils {
realBounds.size() * urls.size());
for (var bound : realBounds) {
for (var url : urls) {
if (url.getUrl() != null && !Utils.isBlank(url.getUrl())) {
if (url.getUrl() != null && !Utils.isStripEmpty(url.getUrl())) {
final var ds = download(monitor, bound, url, maximumDimensions);
downloadedDataSets.add(ds);
MapWithAIDataUtils.getForkJoinPool().execute(ds);
@ -197,9 +198,14 @@ public final class MapWithAIDataUtils {
original.mergeFrom(ds.join());
} catch (RuntimeException e) {
final String notificationMessage;
final var cause = e.getCause();
if (cause instanceof IllegalDataException illegalDataException) {
notificationMessage = ExceptionUtil.explainException(illegalDataException);
Throwable cause = e.getCause();
if (cause != null) {
while (cause.getCause() != null && RuntimeException.class.equals(cause.getClass())) {
cause = cause.getCause();
}
}
if (cause instanceof IllegalDataException) {
notificationMessage = ExceptionUtil.explainException((Exception) cause);
Logging.trace(e);
final var notification = new Notification();
GuiHelper.runInEDT(() -> notification.setContent(notificationMessage));
@ -213,37 +219,26 @@ public final class MapWithAIDataUtils {
}
}
private static boolean confirmBigDownload(List<Bounds> realBounds) {
final var confirmation = new ConfirmBigDownload(realBounds);
GuiHelper.runInEDTAndWait(confirmation);
return confirmation.confirmed();
}
private static class ConfirmBigDownload implements Runnable {
Boolean bool;
final List<?> realBounds;
public ConfirmBigDownload(List<?> realBounds) {
this.realBounds = realBounds;
}
@Override
public void run() {
bool = ConditionalOptionPaneUtil.showConfirmationDialog(MapWithAIPlugin.NAME.concat(".alwaysdownload"),
null,
/**
* Confirm a large download
*
* @param realBounds The list of bounds that will be downloaded
* @return {@code true} if the user still wants to download data
*/
private static synchronized boolean confirmBigDownload(List<Bounds> realBounds) {
final var confirmation = new AtomicBoolean(false);
// This is not a separate class since we don't want to show multiple
// confirmation dialogs
// which is why this method is synchronized.
GuiHelper.runInEDTAndWait(() -> {
final var confirmed = ConditionalOptionPaneUtil.showConfirmationDialog(
MapWithAIPlugin.NAME.concat(".alwaysdownload"), null,
tr("You are going to make {0} requests to the MapWithAI server. This may take some time. <br /> Continue?",
realBounds.size()),
null, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_OPTION);
}
/**
* Check if the user confirmed the download
*
* @return {@code true} if the user wants to continue
*/
public boolean confirmed() {
return bool;
}
confirmation.set(confirmed);
});
return confirmation.get();
}
/**

Wyświetl plik

@ -0,0 +1,121 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.backend;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.openstreetmap.josm.data.Bounds;
/**
* Create a tile
*
* @param x The x coordinate of the tile
* @param y The y coordinate of the tile
* @param z The zoom level
*/
record TileXYZ(int x, int y, int z) {
/**
* Checks to see if the given bounds are functionally equal to this tile
*
* @param left left
* @param bottom bottom
* @param right right
* @param top top
*/
boolean checkBounds(double left, double bottom, double right, double top) {
final var thisLeft = xToLongitude(this.x, this.z);
final var thisRight = xToLongitude(this.x + 1, this.z);
final var thisBottom = yToLatitude(this.y + 1, this.z);
final var thisTop = yToLatitude(this.y, this.z);
return equalsEpsilon(thisLeft, left, this.z) && equalsEpsilon(thisRight, right, this.z)
&& equalsEpsilon(thisBottom, bottom, this.z) && equalsEpsilon(thisTop, top, this.z);
}
private static boolean equalsEpsilon(double first, double second, int z) {
// 0.1% of tile size is considered to be "equal"
final var maxDiff = (360 / Math.pow(2, z)) / 1000;
final var diff = Math.abs(first - second);
return diff <= maxDiff;
}
private static double xToLongitude(int x, int z) {
return (x / Math.pow(2, z)) * 360 - 180;
}
private static double yToLatitude(int y, int z) {
var t = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return 180 / Math.PI * Math.atan((Math.exp(t) - Math.exp(-t)) / 2);
}
/**
* Convert bounds to tiles
*
* @param zoom The zoom level to use
* @param bounds The bounds to convert to tiles
* @return A stream of tiles for the bounds at the given zoom level
*/
static Stream<TileXYZ> tilesFromBBox(int zoom, Bounds bounds) {
final var left = bounds.getMinLon();
final var bottom = bounds.getMinLat();
final var right = bounds.getMaxLon();
final var top = bounds.getMaxLat();
final var tile1 = tileFromLatLonZoom(left, bottom, zoom);
final var tile2 = tileFromLatLonZoom(right, top, zoom);
return IntStream.rangeClosed(tile1.x, tile2.x)
.mapToObj(x -> IntStream.rangeClosed(tile2.y, tile1.y).mapToObj(y -> new TileXYZ(x, y, zoom)))
.flatMap(stream -> stream);
}
/**
* Checks to see if the given bounds are functionally equal to this tile
*
* @param left left lon
* @param bottom bottom lat
* @param right right lon
* @param top top lat
*/
static TileXYZ tileFromBBox(double left, double bottom, double right, double top) {
var zoom = 18;
while (zoom > 0) {
final var tile1 = tileFromLatLonZoom(left, bottom, zoom);
final var tile2 = tileFromLatLonZoom(right, top, zoom);
if (tile1.equals(tile2)) {
return tile1;
} else if (tile1.checkBounds(left, bottom, right, top)) {
return tile1;
} else if (tile2.checkBounds(left, bottom, right, top)) {
return tile2;
// Just in case the coordinates are _barely_ in other tiles and not the "common"
// tile
} else if (Math.abs(tile1.x() - tile2.x()) <= 2 && Math.abs(tile1.y() - tile2.y()) <= 2) {
final var tileT = new TileXYZ((tile1.x() + tile2.x()) / 2, (tile1.y() + tile2.y()) / 2, zoom);
if (tileT.checkBounds(left, bottom, right, top)) {
return tileT;
}
}
zoom--;
}
return new TileXYZ(0, 0, 0);
}
static TileXYZ tileFromLatLonZoom(double lon, double lat, int zoom) {
var xCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom) * (180 + lon) / 360)));
var yCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom)
* (1 - (Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI)) / 2)));
return new TileXYZ(xCoordinate, yCoordinate, zoom);
}
/**
* Extends a bounds object to contain this tile
*
* @param currentBounds The bounds to extend
*/
void expandBounds(Bounds currentBounds) {
final var thisLeft = xToLongitude(this.x, this.z);
final var thisRight = xToLongitude(this.x + 1, this.z);
final var thisBottom = yToLatitude(this.y + 1, this.z);
final var thisTop = yToLatitude(this.y, this.z);
currentBounds.extend(thisBottom, thisLeft);
currentBounds.extend(thisTop, thisRight);
}
}

Wyświetl plik

@ -3,12 +3,9 @@ package org.openstreetmap.josm.plugins.mapwithai.commands;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openstreetmap.josm.command.Command;
@ -17,6 +14,8 @@ import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
import jakarta.annotation.Nullable;
/**
* This is similar to the ReplaceGeometryUtils.buildUpgradeNodeCommand method
*
@ -46,19 +45,19 @@ public final class MergeBuildingNodeCommand {
private static Node getNewOrNoTagNode(OsmPrimitive referenceObject) {
List<Node> nodes;
if (referenceObject instanceof Way) {
nodes = ((Way) referenceObject).getNodes();
} else if (referenceObject instanceof Relation) {
nodes = ((Relation) referenceObject).getMemberPrimitives().stream().flatMap(o -> {
if (o instanceof Way) {
return ((Way) o).getNodes().stream();
} else if (o instanceof Node) {
return Stream.of((Node) o);
if (referenceObject instanceof Way way) {
nodes = way.getNodes();
} else if (referenceObject instanceof Relation relation) {
nodes = relation.getMemberPrimitives().stream().flatMap(o -> {
if (o instanceof Way way) {
return way.getNodes().stream();
} else if (o instanceof Node node) {
return Stream.of(node);
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList());
} else if (referenceObject instanceof Node) {
nodes = Collections.singletonList((Node) referenceObject);
}).filter(Objects::nonNull).toList();
} else if (referenceObject instanceof Node node) {
nodes = Collections.singletonList(node);
} else {
throw new IllegalArgumentException(tr("Unknown OsmPrimitive type"));
}

Wyświetl plik

@ -3,15 +3,11 @@ package org.openstreetmap.josm.plugins.mapwithai.commands;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@ -34,6 +30,9 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelpe
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Pair;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
/**
* Merge duplicate ways
*/
@ -83,7 +82,7 @@ public class MergeDuplicateWays extends Command {
* @param way2 The second way
*/
public MergeDuplicateWays(DataSet data, Way way1, Way way2) {
this(data, Stream.of(way1, way2).filter(Objects::nonNull).collect(Collectors.toList()));
this(data, Stream.of(way1, way2).filter(Objects::nonNull).toList());
}
/**
@ -94,7 +93,7 @@ public class MergeDuplicateWays extends Command {
*/
public MergeDuplicateWays(DataSet data, List<Way> ways) {
super(data);
this.ways = ways.stream().filter(MergeDuplicateWays::nonDeletedWay).collect(Collectors.toList());
this.ways = ways.stream().filter(MergeDuplicateWays::nonDeletedWay).toList();
this.commands = new ArrayList<>();
}
@ -110,11 +109,11 @@ public class MergeDuplicateWays extends Command {
} else if (ways.size() == 1) {
checkForDuplicateWays(ways.get(0), commands);
} else {
Iterator<Way> it = ways.iterator();
Way way1 = it.next();
final var it = ways.iterator();
var way1 = it.next();
while (it.hasNext()) {
Way way2 = it.next();
final Command tCommand = checkForDuplicateWays(way1, way2);
final var way2 = it.next();
final var tCommand = checkForDuplicateWays(way1, way2);
if (tCommand != null) {
commands.add(tCommand);
tCommand.executeCommand();
@ -122,8 +121,7 @@ public class MergeDuplicateWays extends Command {
way1 = way2;
}
}
final List<Command> realCommands = commands.stream().filter(Objects::nonNull).distinct()
.collect(Collectors.toList());
final List<Command> realCommands = commands.stream().filter(Objects::nonNull).distinct().toList();
commands.clear();
commands.addAll(realCommands);
if (!commands.isEmpty() && (commands.size() != 1)) {
@ -165,14 +163,13 @@ public class MergeDuplicateWays extends Command {
final List<Way> ways = (bound == null ? dataSet.getWays() : dataSet.searchWays(bound.toBBox())).stream()
.filter(prim -> !prim.isIncomplete() && !prim.isDeleted())
.collect(Collectors.toCollection(ArrayList::new));
for (int i = 0; i < ways.size(); i++) {
final Way way1 = ways.get(i);
final Collection<Way> nearbyWays = dataSet.searchWays(way1.getBBox()).stream()
.filter(MergeDuplicateWays::nonDeletedWay).collect(Collectors.toList());
nearbyWays.remove(way1);
for (var i = 0; i < ways.size(); i++) {
final var way1 = ways.get(i);
final var nearbyWays = dataSet.searchWays(way1.getBBox()).stream().filter(MergeDuplicateWays::nonDeletedWay)
.filter(w -> !Objects.equals(w, way1)).toList();
for (final Way way2 : nearbyWays) {
final Command command = checkForDuplicateWays(way1, way2);
final Collection<OsmPrimitive> deletedWays = new ArrayList<>();
final var command = checkForDuplicateWays(way1, way2);
final var deletedWays = new ArrayList<OsmPrimitive>();
if (command != null) {
commands.add(command);
command.executeCommand();
@ -194,11 +191,10 @@ public class MergeDuplicateWays extends Command {
*/
public static void checkForDuplicateWays(Way way, List<Command> commands) {
final Collection<Way> nearbyWays = way.getDataSet().searchWays(way.getBBox()).stream()
.filter(MergeDuplicateWays::nonDeletedWay).collect(Collectors.toList());
nearbyWays.remove(way);
.filter(MergeDuplicateWays::nonDeletedWay).filter(w -> !Objects.equals(w, way)).toList();
for (final Way way2 : nearbyWays) {
if (!way2.isDeleted()) {
final Command tCommand = checkForDuplicateWays(way, way2);
final var tCommand = checkForDuplicateWays(way, way2);
if (tCommand != null) {
commands.add(tCommand);
tCommand.executeCommand();
@ -230,10 +226,8 @@ public class MergeDuplicateWays extends Command {
Logging.error("{0}", way2);
}
if ((compressed.size() > 1) && duplicateEntrySet.stream().noneMatch(entry -> entry.getValue().size() > 1)) {
final List<Integer> initial = compressed.stream().map(entry -> entry.a.a).sorted()
.collect(Collectors.toList());
final List<Integer> after = compressed.stream().map(entry -> entry.b.a).sorted()
.collect(Collectors.toList());
final var initial = compressed.stream().map(entry -> entry.a.a).sorted().toList();
final var after = compressed.stream().map(entry -> entry.b.a).sorted().toList();
if (sorted(initial) && sorted(after)) {
returnCommand = mergeWays(way1, way2, compressed);
}
@ -278,12 +272,12 @@ public class MergeDuplicateWays extends Command {
}
}
Collections.reverse(before);
final Way newWay = new Way(way1);
final var newWay = new Way(way1);
List<Command> commands = new ArrayList<>();
before.forEach(node -> newWay.addNode(0, node));
after.forEach(newWay::addNode);
if (newWay.getNodesCount() > 0) {
final ChangeCommand changeCommand = new ChangeCommand(way1, newWay);
final var changeCommand = new ChangeCommand(way1, newWay);
commands.add(changeCommand);
/*
* This must be executed, otherwise the delete command will believe that way2
@ -315,8 +309,8 @@ public class MergeDuplicateWays extends Command {
* @return The node that the param {@code node} duplicates
*/
public static Node nodeInCompressed(Node node, Set<Pair<Pair<Integer, Node>, Pair<Integer, Node>>> compressed) {
Node returnNode = node;
for (final Pair<Pair<Integer, Node>, Pair<Integer, Node>> pair : compressed) {
var returnNode = node;
for (final var pair : compressed) {
if (node.equals(pair.a.b)) {
returnNode = pair.b.b;
} else if (node.equals(pair.b.b)) {
@ -326,26 +320,26 @@ public class MergeDuplicateWays extends Command {
break;
}
}
final Node tReturnNode = returnNode;
final var tReturnNode = returnNode;
node.getKeys().forEach(tReturnNode::put);
return returnNode;
}
/**
* Check if the node pairs increment in the same direction (only checks first
* two pairs), ensure that they are sorted with {@link sorted}
* two pairs), ensure that they are sorted with {@link #sorted}
*
* @param compressed The set of duplicate node/placement pairs
* @return true if the node pairs increment in the same direction
*/
public static boolean checkDirection(Set<Pair<Pair<Integer, Node>, Pair<Integer, Node>>> compressed) {
final Iterator<Pair<Pair<Integer, Node>, Pair<Integer, Node>>> iterator = compressed.iterator();
boolean returnValue = false;
final var iterator = compressed.iterator();
var returnValue = false;
if (compressed.size() > 1) {
final Pair<Pair<Integer, Node>, Pair<Integer, Node>> first = iterator.next();
final Pair<Pair<Integer, Node>, Pair<Integer, Node>> second = iterator.next();
final boolean way1Forward = first.a.a < second.a.a;
final boolean way2Forward = first.b.a < second.b.a;
final var first = iterator.next();
final var second = iterator.next();
final var way1Forward = first.a.a < second.a.a;
final var way2Forward = first.b.a < second.b.a;
returnValue = way1Forward == way2Forward;
}
return returnValue;
@ -358,10 +352,10 @@ public class MergeDuplicateWays extends Command {
* @return true if there are no gaps and it increases
*/
public static boolean sorted(List<Integer> collection) {
boolean returnValue = true;
var returnValue = true;
if (collection.size() > 1) {
Integer last = collection.get(0);
for (int i = 1; i < collection.size(); i++) {
for (var i = 1; i < collection.size(); i++) {
final Integer next = collection.get(i);
if ((next - last) != 1) {
returnValue = false;
@ -381,17 +375,16 @@ public class MergeDuplicateWays extends Command {
* @return A map of node -&gt; node(s) duplicates
*/
public static Map<Pair<Integer, Node>, Map<Integer, Node>> getDuplicateNodes(Way way1, Way way2) {
final Map<Pair<Integer, Node>, Map<Integer, Node>> duplicateNodes = new LinkedHashMap<>();
for (int j = 0; j < way1.getNodesCount(); j++) {
final Node origNode = way1.getNode(j);
for (int k = 0; k < way2.getNodesCount(); k++) {
final Node possDupeNode = way2.getNode(k);
final var duplicateNodes = new LinkedHashMap<Pair<Integer, Node>, Map<Integer, Node>>();
for (var j = 0; j < way1.getNodesCount(); j++) {
final var origNode = way1.getNode(j);
for (var k = 0; k < way2.getNodesCount(); k++) {
final var possDupeNode = way2.getNode(k);
if (origNode.equals(possDupeNode) || (origNode
.greatCircleDistance(possDupeNode) < MapWithAIPreferenceHelper.getMaxNodeDistance())) {
final Pair<Integer, Node> origNodePair = new Pair<>(j, origNode);
final Map<Integer, Node> dupeNodeMap = duplicateNodes.getOrDefault(origNodePair, new HashMap<>());
final var origNodePair = new Pair<>(j, origNode);
final var dupeNodeMap = duplicateNodes.computeIfAbsent(origNodePair, ignored -> new HashMap<>());
dupeNodeMap.put(k, possDupeNode);
duplicateNodes.put(origNodePair, dupeNodeMap);
}
}
}

Wyświetl plik

@ -3,9 +3,7 @@ package org.openstreetmap.josm.plugins.mapwithai.data.mapwithai;
import static org.openstreetmap.josm.tools.I18n.marktr;
import javax.annotation.Nonnull;
import javax.swing.ImageIcon;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
@ -13,11 +11,15 @@ import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import javax.swing.ImageIcon;
import org.openstreetmap.josm.data.sources.ISourceCategory;
import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
import jakarta.annotation.Nonnull;
/**
* The category for a MapWithAI source (i.e., buildings/highways/addresses/etc.)
*
@ -26,12 +28,12 @@ import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
*/
public enum MapWithAICategory implements ISourceCategory<MapWithAICategory> {
BUILDING("data/closedway", "buildings", marktr("Buildings")),
HIGHWAY("presets/transport/way/way_road", "highways", marktr("Roads")),
ADDRESS("presets/misc/housenumber_small", "addresses", marktr("Addresses")),
POWER("presets/power/pole", "pole", marktr("Power")), PRESET("dialogs/search", "presets", marktr("Presets")),
FEATURED("presets/service/network-wireless", "featured", marktr("Featured")),
PREVIEW("presets/misc/fixme", "preview", marktr("Preview")), OTHER(null, "other", marktr("Other"));
BUILDING("data/closedway", "buildings", marktr("Buildings")), HIGHWAY("presets/transport/way/way_road", "highways",
marktr("Roads")), ADDRESS("presets/misc/housenumber_small", "addresses",
marktr("Addresses")), POWER("presets/power/pole", "pole", marktr("Power")), PRESET("dialogs/search",
"presets", marktr("Presets")), FEATURED("presets/service/network-wireless", "featured",
marktr("Featured")), PREVIEW("presets/misc/fixme", "preview",
marktr("Preview")), OTHER(null, "other", marktr("Other"));
private static final Map<ImageSizes, Map<MapWithAICategory, ImageIcon>> iconCache = Collections
.synchronizedMap(new EnumMap<>(ImageSizes.class));
@ -98,6 +100,7 @@ public enum MapWithAICategory implements ISourceCategory<MapWithAICategory> {
*/
public static class DescriptionComparator implements Comparator<MapWithAICategory>, Serializable {
@Serial
private static final long serialVersionUID = 9131636715279880580L;
@Override

Wyświetl plik

@ -3,9 +3,6 @@ package org.openstreetmap.josm.plugins.mapwithai.data.mapwithai;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.annotation.Nonnull;
import javax.swing.SwingUtilities;
import java.io.IOException;
import java.io.Serial;
import java.time.Instant;
@ -25,6 +22,8 @@ import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.SwingUtilities;
import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
import org.openstreetmap.josm.actions.ExpertToggleAction;
import org.openstreetmap.josm.data.Preferences;
@ -42,12 +41,15 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo.MapWithAIPreferenceEntry;
import org.openstreetmap.josm.plugins.mapwithai.io.mapwithai.ESRISourceReader;
import org.openstreetmap.josm.plugins.mapwithai.io.mapwithai.MapWithAISourceReader;
import org.openstreetmap.josm.plugins.mapwithai.io.mapwithai.OvertureSourceReader;
import org.openstreetmap.josm.plugins.mapwithai.spi.preferences.MapWithAIConfig;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.ListenerList;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Utils;
import jakarta.annotation.Nonnull;
/**
* Manages the list of imagery entries that are shown in the imagery menu.
*/
@ -97,8 +99,10 @@ public class MapWithAILayerInfo {
}
// Avoid a deadlock in the EDT.
if (!finished.get() && !SwingUtilities.isEventDispatchThread()) {
var count = 0;
synchronized (MapWithAILayerInfo.class) {
while (!finished.get()) {
while (!finished.get() && count < 120) {
count++;
try {
MapWithAILayerInfo.class.wait(1000);
} catch (InterruptedException e) {
@ -210,9 +214,13 @@ public class MapWithAILayerInfo {
if (this.finishListenerListenerList == null) {
this.finishListenerListenerList = ListenerList.create();
}
boolean running = this.finishListenerListenerList.hasListeners();
if (listener != null) {
this.finishListenerListenerList.addListener(listener);
}
if (running) {
return;
}
if (worker == null) {
final var pleaseWaitRunnable = new PleaseWaitRunnable(tr("Update default entries")) {
@Override
@ -280,8 +288,8 @@ public class MapWithAILayerInfo {
}
// This is literally to avoid allocations on startup
final Preferences preferences;
if (Config.getPref() instanceof Preferences) {
preferences = (Preferences) Config.getPref();
if (Config.getPref()instanceof Preferences pref) {
preferences = pref;
} else {
preferences = null;
}
@ -326,6 +334,7 @@ public class MapWithAILayerInfo {
// This is called here to "pre-cache" the layer information, to avoid blocking
// the EDT
this.updateEsriLayers(result);
this.updateOvertureLayers(result);
newLayers.addAll(result);
} catch (IOException ex) {
loadError = true;
@ -339,11 +348,12 @@ public class MapWithAILayerInfo {
* @param layers The layers to update
*/
private void updateEsriLayers(@Nonnull final Collection<MapWithAIInfo> layers) {
final var esriInfo = new ArrayList<MapWithAIInfo>(300);
for (var layer : layers) {
if (MapWithAIType.ESRI == layer.getSourceType()) {
for (var future : parseEsri(layer)) {
try {
allDefaultLayers.add(future.get());
esriInfo.add(future.get());
} catch (InterruptedException e) {
Logging.error(e);
Thread.currentThread().interrupt();
@ -351,10 +361,27 @@ public class MapWithAILayerInfo {
Logging.error(e);
}
}
} else {
allDefaultLayers.add(layer);
}
}
layers.addAll(esriInfo);
}
/**
* Update the overture layers
* @param layers The layers to iterate through and modify
* @throws IOException If something happens while parsing overture layers
*/
private void updateOvertureLayers(@Nonnull final Collection<MapWithAIInfo> layers) throws IOException {
final var overtureLayers = new ArrayList<MapWithAIInfo>(4);
for (var layer : layers) {
if (MapWithAIType.OVERTURE == layer.getSourceType()) {
try (var reader = new OvertureSourceReader(layer)) {
reader.parse().ifPresent(overtureLayers::addAll);
}
}
}
layers.removeIf(layer -> MapWithAIType.OVERTURE == layer.getSourceType());
layers.addAll(overtureLayers);
}
protected void finish() {
@ -362,7 +389,7 @@ public class MapWithAILayerInfo {
synchronized (allDefaultLayers) {
allDefaultLayers.clear();
defaultLayers.addAll(newLayers);
this.updateEsriLayers(newLayers);
allDefaultLayers.addAll(newLayers);
allDefaultLayers.sort(new MapWithAIInfo.MapWithAIInfoCategoryComparator());
allDefaultLayers.sort(Comparator.comparing(TileSourceInfo::getName));
allDefaultLayers.sort(Comparator.comparing(info -> info.getCategory().getDescription()));
@ -498,7 +525,7 @@ public class MapWithAILayerInfo {
}
if (changed) {
save();
MainApplication.worker.execute(this::save);
}
}

Wyświetl plik

@ -1,18 +1,18 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.data.mapwithai;
import javax.annotation.Nonnull;
import org.openstreetmap.josm.data.sources.ISourceType;
import jakarta.annotation.Nonnull;
/**
* Type of MapWithAI entry
*
* @author Taylor Smock
*/
public enum MapWithAIType implements ISourceType<MapWithAIType> {
FACEBOOK("facebook"), THIRD_PARTY("thirdParty"), ESRI("esri"), ESRI_FEATURE_SERVER("esriFeatureServer"),
MAPBOX_VECTOR_TILE("mvt"), PMTILES("pmtiles");
FACEBOOK("facebook"), THIRD_PARTY("thirdParty"), ESRI("esri"), ESRI_FEATURE_SERVER(
"esriFeatureServer"), MAPBOX_VECTOR_TILE("mvt"), PMTILES("pmtiles"), OVERTURE("overture");
private final String typeString;

Wyświetl plik

@ -4,9 +4,6 @@ package org.openstreetmap.josm.plugins.mapwithai.data.validation.tests;
import static org.openstreetmap.josm.tools.I18n.marktr;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -33,6 +30,9 @@ import org.openstreetmap.josm.plugins.mapwithai.tools.Access;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.Pair;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
/**
* A test for routing islands
*
@ -179,9 +179,11 @@ public class RoutingIslandsTest extends Test {
.filter(way -> !incomingWays.contains(way) || !outgoingWays.contains(way))
.filter(way -> Access.getPositiveAccessValues().contains(
getDefaultAccessTags(way).getOrDefault(currentTransportMode, Access.AccessTags.NO.getKey())))
.collect(Collectors.toSet())).stream()
.map(way -> new Pair<>((incomingWays.containsAll(way) ? marktr("outgoing") : marktr("incoming")), way))
.collect(Collectors.toList());
.collect(Collectors.toSet()))
.stream()
.map(way -> new Pair<>(
(incomingWays.containsAll(way) ? marktr("outgoing") : marktr("incoming")), way))
.toList();
createErrors(problematic, currentTransportMode);
}
@ -198,9 +200,9 @@ public class RoutingIslandsTest extends Test {
private static void findConnectedWays(String currentTransportMode, Collection<Way> potentialWays,
Collection<Way> incomingWays, Collection<Way> outgoingWays) {
potentialWays.stream().filter(Way::isUsable).filter(Way::isOutsideDownloadArea).forEach(way -> {
Node firstNode = firstNode(way, currentTransportMode);
Node lastNode = lastNode(way, currentTransportMode);
Integer isOneway = isOneway(way, currentTransportMode);
final var firstNode = firstNode(way, currentTransportMode);
final var lastNode = lastNode(way, currentTransportMode);
final var isOneway = isOneway(way, currentTransportMode);
if (firstNode != null && firstNode.isOutsideDownloadArea()) {
incomingWays.add(way);
}
@ -223,14 +225,14 @@ public class RoutingIslandsTest extends Test {
* @return a list of sets of ways that are connected
*/
private static List<Set<Way>> collectConnected(Collection<Way> ways) {
ArrayList<Set<Way>> collected = new ArrayList<>();
ArrayList<Way> listOfWays = new ArrayList<>(ways);
final int maxLoop = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
for (int i = 0; i < listOfWays.size(); i++) {
Way initial = listOfWays.get(i);
final var collected = new ArrayList<Set<Way>>();
final var listOfWays = new ArrayList<>(ways);
final var maxLoop = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
for (var i = 0; i < listOfWays.size(); i++) {
final var initial = listOfWays.get(i);
Set<Way> connected = new HashSet<>();
connected.add(initial);
int loopCounter = 0;
var loopCounter = 0;
while (!getConnected(connected) && loopCounter < maxLoop) {
loopCounter++;
}
@ -281,8 +283,8 @@ public class RoutingIslandsTest extends Test {
*/
public static void checkForUnconnectedWays(Collection<Way> incoming, Collection<Way> outgoing,
String currentTransportMode) {
int loopCount = 0;
int maxLoops = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
var loopCount = 0;
var maxLoops = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
do {
loopCount++;
} while (loopCount <= maxLoops && getWaysFor(incoming, currentTransportMode,
@ -325,11 +327,10 @@ public class RoutingIslandsTest extends Test {
* clean up and work with via ways
*/
public static boolean checkAccessibility(Way from, Way to, String currentTransportMode) {
boolean isAccessible = true;
var isAccessible = true;
List<Relation> relations = from.getReferrers().stream().distinct().filter(Relation.class::isInstance)
.map(Relation.class::cast).filter(relation -> "restriction".equals(relation.get("type")))
.collect(Collectors.toList());
.map(Relation.class::cast).filter(relation -> "restriction".equals(relation.get("type"))).toList();
for (Relation relation : relations) {
if (((relation.hasKey("except") && relation.get("except").contains(currentTransportMode))
|| (currentTransportMode == null || currentTransportMode.trim().isEmpty()))
@ -432,7 +433,7 @@ public class RoutingIslandsTest extends Test {
* @return The map of access tags to access
*/
public static TagMap getDefaultAccessTags(OsmPrimitive primitive) {
TagMap access = new TagMap();
final var access = new TagMap();
final TagMap tags;
if (primitive.hasKey(HIGHWAY)) {
tags = getDefaultHighwayAccessTags(primitive.getKeys());
@ -451,13 +452,9 @@ public class RoutingIslandsTest extends Test {
}
private static String getCachedDirectionMode(String direction, String mode) {
if (!DIRECTION_MAP.containsKey(direction)) {
DIRECTION_MAP.put(direction, new HashMap<>(Access.getTransportModes().size()));
}
DIRECTION_MAP.computeIfAbsent(direction, d -> new HashMap<>(Access.getTransportModes().size()));
final var directionMap = DIRECTION_MAP.get(direction);
if (!directionMap.containsKey(mode)) {
directionMap.put(mode, direction.concat(mode));
}
directionMap.computeIfAbsent(mode, direction::concat);
return directionMap.get(mode);
}

Wyświetl plik

@ -44,8 +44,8 @@ public class StreetAddressTest extends Test {
public static final double BBOX_EXPANSION = 0.002;
private static final String ADDR_STREET = "addr:street";
private final Set<OsmPrimitive> namePrimitiveMap = new HashSet<>();
private final Map<Point2D, Set<String>> nameMap = new HashMap<>();
private final Map<Point2D, List<OsmPrimitive>> primitiveCellMap = new HashMap<>();
private final Map<Point, Set<String>> nameMap = new HashMap<>();
private final Map<Point, List<OsmPrimitive>> primitiveCellMap = new HashMap<>();
/**
* Classified highways. This uses a {@link Set} instead of a {@link List} since
* the MapWithAI code doesn't care about order.
@ -146,7 +146,8 @@ public class StreetAddressTest extends Test {
final var n1 = nodes.get(i);
final var n2 = nodes.get(i + 1);
for (Point2D cell : ValUtil.getSegmentCells(n1, n2, gridDetail)) {
this.nameMap.computeIfAbsent(cell, k -> new HashSet<>()).addAll(names);
this.nameMap.computeIfAbsent(new Point(cell.getX(), cell.getY()), k -> new HashSet<>())
.addAll(names);
}
}
} else if (hasStreetAddressTags(primitive) && !primitive.isOutsideDownloadArea()) {
@ -159,7 +160,7 @@ public class StreetAddressTest extends Test {
if (en != null) {
long x = (long) Math.floor(en.getX() * gridDetail);
long y = (long) Math.floor(en.getY() * gridDetail);
final var point = new Point2D.Double(x, y);
final var point = new Point(x, y);
primitiveCellMap.computeIfAbsent(point, p -> new ArrayList<>()).add(primitive);
}
}
@ -174,7 +175,7 @@ public class StreetAddressTest extends Test {
.filter(s -> !s.isEmpty()).collect(Collectors.toSet());
}
private Collection<String> getSurroundingHighwayNames(Point2D point2D) {
private Collection<String> getSurroundingHighwayNames(Point point2D) {
if (this.nameMap.isEmpty()) {
return Collections.emptySet();
}
@ -183,8 +184,9 @@ public class StreetAddressTest extends Test {
while (surroundingWays.isEmpty()) {
for (int x = -surrounding; x <= surrounding; x++) {
for (int y = -surrounding; y <= surrounding; y++) {
final var key = new Point2D.Double((long) Math.floor(point2D.getX() + x),
(long) Math.floor(point2D.getY() + y));
final var xPoint = (long) Math.floor(point2D.x() + x);
final var yPoint = (long) Math.floor(point2D.y() + y);
final var key = new Point(xPoint, yPoint);
if (this.nameMap.containsKey(key)) {
surroundingWays.addAll(this.nameMap.get(key));
}
@ -303,4 +305,20 @@ public class StreetAddressTest extends Test {
bbox.add(bbox.getTopLeftLon() - degree, bbox.getTopLeftLat() + degree);
return bbox;
}
private record Point(double x, double y) implements Comparable<Point> {
@Override
public int compareTo(Point other) {
if (other.x == this.x && other.y == this.y) {
return 0;
}
if (other.x < this.x) {
return -1;
}
if (other.x > this.x) {
return 1;
}
return Double.compare(other.y, this.y);
}
}}

Wyświetl plik

@ -51,6 +51,7 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
columnDataRetrieval.add(info -> Optional.ofNullable(info.getTermsOfUseURL()).orElse(""));
columnDataRetrieval.add(i -> MapWithAILayerInfo.getInstance().getLayers().contains(i));
MapWithAILayerInfo.getInstance().addFinishListener(() -> GuiHelper.runInEDT(this::fireTableDataChanged));
MapWithAILayerInfo.SHOW_PREVIEW.addWeakListener(l -> GuiHelper.runInEDT(this::fireTableDataChanged));
}
/**
@ -60,10 +61,11 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
* @return The imagery info at the given row number
*/
public static MapWithAIInfo getRow(int row) {
if (row == 0 && MapWithAILayerInfo.getInstance().getAllDefaultLayers().isEmpty()) {
final var layers = MapWithAILayerInfo.getInstance().getAllDefaultLayers();
if (row == 0 && layers.isEmpty()) {
return new MapWithAIInfo(tr("Loading"), "");
}
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
return layers.get(row);
}
@Override

Wyświetl plik

@ -4,22 +4,6 @@ package org.openstreetmap.josm.plugins.mapwithai.gui.preferences.mapwithai;
import static org.openstreetmap.josm.tools.I18n.marktr;
import static org.openstreetmap.josm.tools.I18n.tr;
import javax.annotation.Nonnull;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
@ -43,6 +27,22 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import org.openstreetmap.gui.jmapviewer.Coordinate;
import org.openstreetmap.gui.jmapviewer.MapPolygonImpl;
import org.openstreetmap.gui.jmapviewer.MapRectangleImpl;
@ -52,6 +52,7 @@ import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.preferences.NamedColorProperty;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.bbox.JosmMapViewer;
import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
import org.openstreetmap.josm.gui.preferences.imagery.ImageryProvidersPanel;
import org.openstreetmap.josm.gui.util.GuiHelper;
@ -72,6 +73,8 @@ import org.openstreetmap.josm.tools.ListenerList;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.OpenBrowser;
import jakarta.annotation.Nonnull;
/**
* A panel displaying imagery providers. Largely duplicates
* {@link ImageryProvidersPanel}.
@ -376,7 +379,7 @@ public class MapWithAIProvidersPanel extends JPanel {
// Add default item map
defaultMap = new SlippyMapBBoxChooser();
defaultMap.setTileSource(SlippyMapBBoxChooser.DefaultOsmTileSourceProvider.get()); // for attribution
defaultMap.setTileSource(JosmMapViewer.DefaultOsmTileSourceProvider.get()); // for attribution
defaultMap.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
@ -408,36 +411,36 @@ public class MapWithAIProvidersPanel extends JPanel {
defaultTableListener = new DefListSelectionListener();
defaultTable.getSelectionModel().addListSelectionListener(defaultTableListener);
defaultToolbar = new JToolBar(JToolBar.VERTICAL);
defaultToolbar = new JToolBar(SwingConstants.VERTICAL);
defaultToolbar.setFloatable(false);
defaultToolbar.setBorderPainted(false);
defaultToolbar.setOpaque(false);
defaultToolbar.add(new ReloadAction());
add(defaultToolbar, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 5, 0));
add(defaultToolbar, GBC.eol().anchor(GridBagConstraints.SOUTH).insets(0, 0, 5, 0));
final var help = new HtmlPanel(
tr("New default entries can be added in the <a href=\"{0}\">GitHub Repository</a>.",
"https://github.com/JOSM/MapWithAI/blob/pages/json/sources.json"));
help.enableClickableHyperlinks();
add(help, GBC.eol().insets(10, 0, 0, 0).fill(GBC.HORIZONTAL));
add(help, GBC.eol().insets(10, 0, 0, 0).fill(GridBagConstraints.HORIZONTAL));
final var activate = new ActivateAction();
defaultTable.getSelectionModel().addListSelectionListener(activate);
final var btnActivate = new JButton(activate);
middleToolbar = new JToolBar(JToolBar.HORIZONTAL);
middleToolbar = new JToolBar(SwingConstants.HORIZONTAL);
middleToolbar.setFloatable(false);
middleToolbar.setBorderPainted(false);
middleToolbar.setOpaque(false);
middleToolbar.add(btnActivate);
add(middleToolbar, GBC.eol().anchor(GBC.CENTER).insets(5, 5, 5, 0));
add(middleToolbar, GBC.eol().anchor(GridBagConstraints.CENTER).insets(5, 5, 5, 0));
add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
final var scroll = new JScrollPane(activeTable);
scroll.setPreferredSize(new Dimension(200, 200));
activeToolbar = new JToolBar(JToolBar.VERTICAL);
activeToolbar = new JToolBar(SwingConstants.VERTICAL);
activeToolbar.setFloatable(false);
activeToolbar.setBorderPainted(false);
activeToolbar.setOpaque(false);
@ -448,7 +451,7 @@ public class MapWithAIProvidersPanel extends JPanel {
add(new JLabel(tr("Selected entries:")), GBC.eol().insets(5, 0, 0, 0));
add(scroll, GBC.std().fill(GridBagConstraints.BOTH).span(GridBagConstraints.RELATIVE).weight(1.0, 0.4)
.insets(5, 0, 0, 5));
add(activeToolbar, GBC.eol().anchor(GBC.NORTH).insets(0, 0, 5, 5));
add(activeToolbar, GBC.eol().anchor(GridBagConstraints.NORTH).insets(0, 0, 5, 5));
}
}

Wyświetl plik

@ -6,13 +6,12 @@ import java.util.Optional;
import org.openstreetmap.josm.io.CachedFile;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Utils;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.json.JsonStructure;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParsingException;
/**
* Read sources for MapWithAI
@ -42,24 +41,26 @@ public abstract class CommonSourceReader<T> implements AutoCloseable {
this.cachedFile = new CachedFile(this.source);
}
this.cachedFile.setFastFail(this.fastFail);
try (JsonReader reader = Json.createReader(cachedFile.setMaxAge(CachedFile.DAYS)
try (JsonParser reader = Json.createParser(cachedFile.setMaxAge(CachedFile.DAYS)
.setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince).getContentReader())) {
JsonStructure struct = reader.read();
if (JsonValue.ValueType.OBJECT == struct.getValueType()) {
final var jsonObject = struct.asJsonObject();
return Optional.ofNullable(this.parseJson(jsonObject));
while (reader.hasNext()) {
if (reader.hasNext() && reader.next() == JsonParser.Event.START_OBJECT) {
return Optional.ofNullable(this.parseJson(reader));
}
}
} catch (JsonParsingException jsonParsingException) {
Logging.error(jsonParsingException);
}
return Optional.empty();
}
}
/**
* Parses MapWithAI entry sources
*
* @param jsonObject The json of the data sources
* @param parser The json of the data sources. This will be in the {@link JsonParser.Event#START_OBJECT} state.
* @return The parsed entries
*/
public abstract T parseJson(JsonObject jsonObject);
public abstract T parseJson(JsonParser parser);
/**
* Sets whether opening HTTP connections should fail fast, i.e., whether a

Wyświetl plik

@ -10,9 +10,9 @@ import java.util.stream.Collectors;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
import org.openstreetmap.josm.tools.Pair;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
/**
* Read conflation entries from JSON
@ -44,12 +44,12 @@ public class ConflationSourceReader extends CommonSourceReader<Map<MapWithAICate
/**
* Parses MapWithAI entry sources
*
* @param jsonObject The json of the data sources
* @param parser The json of the data sources
* @return The parsed entries
*/
@Override
public Map<MapWithAICategory, List<String>> parseJson(JsonObject jsonObject) {
return jsonObject.entrySet().stream().flatMap(i -> parse(i).stream())
public Map<MapWithAICategory, List<String>> parseJson(JsonParser parser) {
return parser.getObjectStream().flatMap(i -> parse(i).stream())
.collect(Collectors.groupingBy(p -> p.a, Collectors.mapping(p -> p.b, Collectors.toList())));
}

Wyświetl plik

@ -1,9 +1,6 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
@ -36,6 +33,8 @@ import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.Logging;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonNumber;
@ -55,6 +54,7 @@ public class ESRISourceReader {
/** The cache storing ESRI source information (json) */
public static final CacheAccess<String, String> SOURCE_CACHE = JCSCacheManager.getCache("mapwithai:esrisources", 5,
50_000, new File(Config.getDirs().getCacheDirectory(true), "mapwithai").getPath());
private static final String ACCESS_INFORMATION = "accessInformation";
private final MapWithAIInfo source;
private boolean fastFail;
private final List<MapWithAICategory> ignoreConflationCategories;
@ -164,7 +164,7 @@ public class ESRISourceReader {
newInfo.setName(feature.getString("title", feature.getString("name")));
final var extent = feature.getJsonArray("extent").getValuesAs(JsonArray.class).stream()
.flatMap(array -> array.getValuesAs(JsonNumber.class).stream()).map(JsonNumber::doubleValue)
.map(Object::toString).toArray(String[]::new);
.map(d -> Double.toString(d)).toArray(String[]::new);
final var imageryBounds = new ImageryBounds(String.join(",", extent[1], extent[0], extent[3], extent[2]), ",");
newInfo.setBounds(imageryBounds);
newInfo.setSourceType(MapWithAIType.ESRI_FEATURE_SERVER);
@ -187,9 +187,9 @@ public class ESRISourceReader {
if (this.ignoreConflationCategories.contains(newInfo.getCategory())) {
newInfo.setConflation(false);
}
if (feature.containsKey("accessInformation")
&& feature.get("accessInformation").getValueType() != JsonValue.ValueType.NULL) {
newInfo.setAttributionText(feature.getString("accessInformation"));
if (feature.containsKey(ACCESS_INFORMATION)
&& feature.get(ACCESS_INFORMATION).getValueType() != JsonValue.ValueType.NULL) {
newInfo.setAttributionText(feature.getString(ACCESS_INFORMATION));
}
newInfo.setDescription(feature.getString("snippet"));
if (newInfo.getSource() != null) {

Wyświetl plik

@ -17,9 +17,9 @@ import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
import org.openstreetmap.josm.tools.Territories;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
/**
* Reader to parse the list of available MapWithAI servers from an JSON
@ -54,12 +54,12 @@ public class MapWithAISourceReader extends CommonSourceReader<List<MapWithAIInfo
/**
* Parses MapWithAI entry sources
*
* @param jsonObject The json of the data sources
* @param parser The json of the data sources
* @return The parsed entries
*/
@Override
public List<MapWithAIInfo> parseJson(JsonObject jsonObject) {
return jsonObject.entrySet().stream().map(MapWithAISourceReader::parse).collect(Collectors.toList());
public List<MapWithAIInfo> parseJson(JsonParser parser) {
return parser.getObjectStream().map(MapWithAISourceReader::parse).collect(Collectors.toList());
}
private static MapWithAIInfo parse(Map.Entry<String, JsonValue> entry) {

Wyświetl plik

@ -0,0 +1,146 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.imagery.ImageryInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
import org.openstreetmap.josm.plugins.pmtiles.lib.PMTiles;
import org.openstreetmap.josm.tools.Logging;
import jakarta.annotation.Nullable;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
/**
* Read data from overture sources
*/
public class OvertureSourceReader extends CommonSourceReader<List<MapWithAIInfo>> implements Closeable {
private final MapWithAIInfo source;
public OvertureSourceReader(MapWithAIInfo source) {
super(source.getUrl());
this.source = source;
}
@Override
public List<MapWithAIInfo> parseJson(JsonParser jsonParser) {
final var jsonObject = jsonParser.getObject();
if (jsonObject.containsKey("releases")) {
return parseRoot(jsonObject);
}
return Collections.emptyList();
}
private List<MapWithAIInfo> parseRoot(JsonObject jsonObject) {
final var info = new ArrayList<MapWithAIInfo>(6 * 4);
final var releases = jsonObject.get("releases");
final var baseUri = URI.create(this.source.getUrl()).resolve("./"); // safe since we created an URI from the source to get to this point
if (releases instanceof JsonArray rArray) {
rArray.parallelStream().flatMap(value -> parseReleases(baseUri, value)).filter(Objects::nonNull)
.forEachOrdered(info::add);
}
info.trimToSize();
return info;
}
private Stream<MapWithAIInfo> parseReleases(URI baseUri, JsonValue value) {
if (value instanceof JsonObject release && release.containsKey("release_id") && release.containsKey("files")) {
final var id = release.get("release_id");
final var files = release.get("files");
if (id instanceof JsonString sId && files instanceof JsonArray fArray) {
final String releaseId = sId.getString();
return fArray.parallelStream().map(file -> parseFile(baseUri, releaseId, file));
}
}
return Stream.empty();
}
/**
* Parse the individual file from the files array
* @param baseUri The base URI (if the href is relative)
* @param releaseId The release id to differentiate it from other releases with the same theme
* @param file The file object
* @return The info, if it was parsed. Otherwise {@code null}.
*/
@Nullable
private MapWithAIInfo parseFile(URI baseUri, String releaseId, JsonValue file) {
if (file instanceof JsonObject fObj && fObj.containsKey("theme") && fObj.containsKey("href")) {
final JsonValue vTheme = fObj.get("theme");
final JsonValue vHref = fObj.get("href");
try {
if (vTheme instanceof JsonString sTheme && vHref instanceof JsonString href) {
final var theme = sTheme.getString();
final URI uri;
if (href.getString().startsWith("./") || href.getString().startsWith("../")) {
uri = baseUri.resolve(href.getString());
} else {
uri = new URI(href.getString());
}
return buildSource(uri, releaseId, theme);
}
} catch (URISyntaxException uriSyntaxException) {
Logging.debug(uriSyntaxException);
}
}
return null;
}
private MapWithAIInfo buildSource(URI uri, String releaseId, String theme) {
final var info = new MapWithAIInfo(this.source);
info.setUrl(uri.toString());
info.setName(this.source.getName() + ": " + theme + " - " + releaseId);
if ("addresses".equals(theme)) {
info.setCategory(MapWithAICategory.ADDRESS);
} else if ("buildings".equals(theme)) {
info.setCategory(MapWithAICategory.BUILDING);
} else {
info.setCategory(MapWithAICategory.OTHER);
}
// Addresses and places are "interesting". Only removing "transportation" since that currently causes crashes.
if ("transportation".equals(theme)) {
return null;
}
final var categories = EnumSet.of(this.source.getCategory(),
this.source.getAdditionalCategories().toArray(MapWithAICategory[]::new));
categories.removeIf(MapWithAICategory.OTHER::equals);
info.setAdditionalCategories(new ArrayList<>(categories));
info.setId(info.getName());
if (uri.getPath().endsWith(".pmtiles")) {
info.setSourceType(MapWithAIType.PMTILES);
// Set additional information
try {
final var header = PMTiles.readHeader(uri);
final var metadata = PMTiles.readMetadata(header);
final var bounds = new Bounds(header.minLatitude(), header.minLongitude(), header.maxLatitude(),
header.maxLongitude());
info.setBounds(new ImageryInfo.ImageryBounds(bounds.encodeAsString(","), ","));
if (metadata.containsKey("name") && metadata.get("name")instanceof JsonString name) {
info.setName(name.getString() + " - " + releaseId);
}
if (metadata.containsKey("description")
&& metadata.get("description")instanceof JsonString description) {
info.setDescription(description.getString());
}
} catch (IOException ioException) {
Logging.error(ioException);
}
}
return info;
}
}

Wyświetl plik

@ -60,12 +60,13 @@ public final class MapPaintUtils {
* Safe colors
*/
public enum SafeColors {
RED(Color.RED), ORANGE(Color.ORANGE), GOLD(Objects.requireNonNull(ColorHelper.html2color("#ffd700"))),
LIME(Objects.requireNonNull(ColorHelper.html2color("#00ff00"))), CYAN(Color.CYAN),
DODGER_BLUE(Objects.requireNonNull(ColorHelper.html2color("#1e90ff"))),
AI_MAGENTA(Objects.requireNonNull(ColorHelper.html2color("#ff26d4"))), PINK(Color.PINK),
LIGHT_GREY(Objects.requireNonNull(ColorHelper.html2color("#d3d3d3"))),
LINEN(Objects.requireNonNull(ColorHelper.html2color("#faf0e6")));
RED(Color.RED), ORANGE(Color.ORANGE), GOLD(Objects.requireNonNull(ColorHelper.html2color("#ffd700"))), LIME(
Objects.requireNonNull(ColorHelper.html2color("#00ff00"))), CYAN(Color.CYAN), DODGER_BLUE(
Objects.requireNonNull(ColorHelper.html2color("#1e90ff"))), AI_MAGENTA(
Objects.requireNonNull(ColorHelper.html2color("#ff26d4"))), PINK(
Color.PINK), LIGHT_GREY(
Objects.requireNonNull(ColorHelper.html2color("#d3d3d3"))), LINEN(
Objects.requireNonNull(ColorHelper.html2color("#faf0e6")));
private final Color color;
@ -159,7 +160,7 @@ public final class MapPaintUtils {
return;
}
List<String> sources = ds.allPrimitives().stream().map(MapPaintUtils::getSourceValue).filter(Objects::nonNull)
.distinct().collect(Collectors.toList());
.map(s -> s.replace('.', '_')).distinct().collect(Collectors.toList());
if (!styleSource.isLoaded()) {
styleSource.loadStyleSource();
}

1
src/test/data 120000
Wyświetl plik

@ -0,0 +1 @@
resources

Wyświetl plik

@ -7,7 +7,20 @@
},
"response" : {
"status" : 200,
"body" : "{\n \"Taylor's Address Conflation Server\" : {\n \"categories\" : [\n \"addresses\"\n ],\n \"description\" : \"Originally developed for use with local datasets, it now accepts external datasets for conflation purposes. In the event of a failure, the plugin will use the original dataset.\",\n \"license\" : \"AGPL\",\n \"source\" : \"https://gitlab.com/smocktaylor/serve_osm_files/\",\n \"url\" : \"https://importdata.riverviewtechnologies.com/conflate\"\n }\n}\n",
"jsonBody": {
"Taylor's Address Conflation Server": {
"categories": [
"addresses"
],
"description": "Originally developed for use with local datasets, it now accepts external datasets for conflation purposes. In the event of a failure, the plugin will use the original dataset.",
"license": "AGPL",
"source": "{{ request.baseUrl }}/smocktaylor/serve_osm_files/",
"url": "{{ request.baseUrl }}/conflate"
}
},
"transformers": [
"response-template"
],
"headers" : {
"Accept-Ranges" : "bytes",
"Cache-Control" : "max-age=600",

Wyświetl plik

@ -0,0 +1,641 @@
{
"id" : "23497750-675c-440a-986a-34dd14b047f8",
"name" : "josm_mapwithai_json_sourcesjson",
"request" : {
"urlPattern" : "\/MapWithAI\/?json\/sources.json",
"method" : "GET"
},
"response" : {
"status" : 200,
"jsonBody": {
"Statewide Aggregate Addresses in Colorado 2019 (Public)": {
"countries": {
"US-CO": [
"addr:housenumber"
]
},
"license": "Public Domain",
"osm_compatible": "yes",
"parameters": [],
"permission_url": "{{ request.baseUrl }}/wiki/Import/Colorado_Addresses",
"url": "{{ request.baseUrl }}/coloradoAddresses/map?bbox={bbox}"
},
"MapWithAI": {
"countries": {
"AE": [
"highway"
],
"AF": [
"highway"
],
"AG": [
"highway"
],
"AI": [
"highway"
],
"AL": [
"highway"
],
"AM": [
"highway"
],
"AO": [
"highway"
],
"AR": [
"highway"
],
"AT": [
"highway"
],
"AU": [
"highway"
],
"AZ": [
"highway"
],
"BA": [
"highway"
],
"BB": [
"highway"
],
"BD": [
"highway"
],
"BE": [
"highway"
],
"BF": [
"highway"
],
"BG": [
"highway"
],
"BI": [
"highway"
],
"BJ": [
"highway"
],
"BL": [
"highway"
],
"BN": [
"highway"
],
"BO": [
"highway"
],
"BR": [
"highway"
],
"BS": [
"highway"
],
"BT": [
"highway"
],
"BW": [
"highway"
],
"BY": [
"highway"
],
"BZ": [
"highway"
],
"CA": [
"building",
"highway"
],
"CD": [
"highway"
],
"CF": [
"highway"
],
"CG": [
"highway"
],
"CH": [
"highway"
],
"CI": [
"highway"
],
"CL": [
"highway"
],
"CM": [
"highway"
],
"CN": [
"highway"
],
"CO": [
"highway"
],
"CR": [
"highway"
],
"CU": [
"highway"
],
"CY": [
"highway"
],
"CZ": [
"highway"
],
"DE": [
"highway"
],
"DJ": [
"highway"
],
"DK": [
"highway"
],
"DM": [
"highway"
],
"DO": [
"highway"
],
"DZ": [
"highway"
],
"EC": [
"highway"
],
"EE": [
"highway"
],
"EG": [
"highway"
],
"EH": [
"highway"
],
"ER": [
"highway"
],
"ES": [
"highway"
],
"ET": [
"highway"
],
"FK": [
"highway"
],
"FI": [
"highway"
],
"FJ": [
"highway"
],
"FR": [
"highway"
],
"GA": [
"highway"
],
"GB": [
"highway"
],
"GD": [
"highway"
],
"GE": [
"highway"
],
"GF": [
"highway"
],
"GH": [
"highway"
],
"GM": [
"highway"
],
"GN": [
"highway"
],
"GP": [
"highway"
],
"GQ": [
"highway"
],
"GR": [
"highway"
],
"GT": [
"highway"
],
"GW": [
"highway"
],
"GY": [
"highway"
],
"HN": [
"highway"
],
"HR": [
"highway"
],
"HT": [
"highway"
],
"HU": [
"highway"
],
"ID": [
"highway"
],
"IE": [
"highway"
],
"IL": [
"highway"
],
"IN": [
"highway"
],
"IQ": [
"highway"
],
"IS": [
"highway"
],
"IT": [
"highway"
],
"JM": [
"highway"
],
"JO": [
"highway"
],
"JP": [
"highway"
],
"KE": [
"highway"
],
"KG": [
"highway"
],
"KH": [
"highway"
],
"KN": [
"highway"
],
"KY": [
"highway"
],
"KR": [
"highway"
],
"KW": [
"highway"
],
"KZ": [
"highway"
],
"LA": [
"highway"
],
"LB": [
"highway"
],
"LC": [
"highway"
],
"LK": [
"highway"
],
"LR": [
"highway"
],
"LS": [
"highway"
],
"LT": [
"highway"
],
"LU": [
"highway"
],
"LV": [
"highway"
],
"LY": [
"highway"
],
"MA": [
"highway"
],
"MD": [
"highway"
],
"ME": [
"highway"
],
"MF": [
"highway"
],
"MG": [
"highway"
],
"MK": [
"highway"
],
"ML": [
"highway"
],
"MM": [
"highway"
],
"MN": [
"highway"
],
"MQ": [
"highway"
],
"MR": [
"highway"
],
"MS": [
"highway"
],
"MW": [
"highway"
],
"MX": [
"highway"
],
"MY": [
"highway"
],
"MZ": [
"highway"
],
"NA": [
"highway"
],
"NE": [
"highway"
],
"NG": [
"highway"
],
"NI": [
"highway"
],
"NL": [
"highway"
],
"NL-BQ2": [
"highway"
],
"NL-BQ3": [
"highway"
],
"NO": [
"highway"
],
"NP": [
"highway"
],
"NZ": [
"highway"
],
"OM": [
"highway"
],
"PA": [
"highway"
],
"PE": [
"highway"
],
"PF": [
"highway"
],
"PG": [
"highway"
],
"PH": [
"highway"
],
"PK": [
"highway"
],
"PL": [
"highway"
],
"PR": [
"highway"
],
"PS": [
"highway"
],
"PT": [
"highway"
],
"PY": [
"highway"
],
"QA": [
"highway"
],
"RO": [
"highway"
],
"RS": [
"highway"
],
"RS-KM": [
"highway"
],
"RU": [
"highway"
],
"RW": [
"highway"
],
"SA": [
"highway"
],
"SB": [
"highway"
],
"SD": [
"highway"
],
"SE": [
"highway"
],
"SG": [
"highway"
],
"SI": [
"highway"
],
"SK": [
"highway"
],
"SL": [
"highway"
],
"SN": [
"highway"
],
"SO": [
"highway"
],
"SR": [
"highway"
],
"SS": [
"highway"
],
"ST": [
"highway"
],
"SV": [
"highway"
],
"SX": [
"highway"
],
"SZ": [
"highway"
],
"TC": [
"highway"
],
"TD": [
"highway"
],
"TG": [
"highway"
],
"TH": [
"highway"
],
"TJ": [
"highway"
],
"TL": [
"highway"
],
"TM": [
"highway"
],
"TN": [
"highway"
],
"TR": [
"highway"
],
"TT": [
"highway"
],
"TW": [
"highway"
],
"TZ": [
"building",
"highway"
],
"UA": [
"highway"
],
"UG": [
"building",
"highway"
],
"US": [
"building",
"highway"
],
"US-AK": [
"building"
],
"US-HI": [
"building"
],
"UY": [
"highway"
],
"UZ": [
"highway"
],
"VC": [
"highway"
],
"VE": [
"highway"
],
"VG": [
"highway"
],
"VN": [
"highway"
],
"VU": [
"highway"
],
"YE": [
"highway"
],
"ZA": [
"highway"
],
"ZM": [
"highway"
],
"ZW": [
"highway"
]
},
"default": true,
"license": "ODBL",
"osm_compatible": "yes",
"parameters": [
{
"description": "buildings",
"enabled": true,
"parameter": "result_type=road_building_vector_xml"
}
],
"permission_url": "{{ request.baseUrl }}/facebookmicrosites/Open-Mapping-At-Facebook/wiki/FAQ",
"terms_of_use_url": "{{ request.baseUrl }}/doc/license/MapWithAILicense.pdf",
"privacy_policy_url": "{{ request.baseUrl }}/doc/license/MapWithAIPrivacyPolicy.pdf#page=3",
"url": "{{ request.baseUrl }}/maps/ml_roads?conflate_with_osm=true&theme=ml_road_vector&collaborator=josm&token=ASb3N5o9HbX8QWn8G_NtHIRQaYv3nuG2r7_f3vnGld3KhZNCxg57IsaQyssIaEw5rfRNsPpMwg4TsnrSJtIJms5m&hash=ASawRla3rBcwEjY4HIY&bbox={bbox}"
}
},
"transformers": [
"response-template"
],
"headers" : {
"Accept-Ranges" : "bytes",
"Cache-Control" : "max-age=600",
"Content-Type" : "application/json",
"Expires" : "Tue, 21 Apr 2020 19:51:40 UTC",
"Last-Modified" : "Tue, 21 Apr 2020 17:34:08 GMT",
"Vary" : "Origin",
"Date" : "Tue, 21 Apr 2020 19:41:40 GMT"
}
},
"uuid" : "23497750-675c-440a-986a-34dd14b047f8",
"persistent" : true,
"insertionIndex" : 1
}

Wyświetl plik

@ -4,14 +4,11 @@ package org.openstreetmap.josm.plugins.mapwithai.actions;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import javax.imageio.ImageIO;
import javax.swing.Action;
import javax.swing.ImageIcon;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -19,20 +16,23 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.stream.Collectors;
import org.awaitility.Awaitility;
import org.awaitility.Durations;
import javax.imageio.ImageIO;
import javax.swing.Action;
import javax.swing.ImageIcon;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.DataSource;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
@ -42,16 +42,16 @@ import org.openstreetmap.josm.plugins.mapwithai.testutils.annotations.MapWithAIS
import org.openstreetmap.josm.plugins.mapwithai.testutils.annotations.NoExceptions;
import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
import org.openstreetmap.josm.testutils.annotations.Projection;
import org.openstreetmap.josm.testutils.annotations.ThreadSync;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Logging;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.matching.AnythingPattern;
import com.github.tomakehurst.wiremock.matching.EqualToPattern;
import com.github.tomakehurst.wiremock.matching.StringValuePattern;
import com.github.tomakehurst.wiremock.matching.UrlPathPattern;
import jakarta.json.Json;
/**
* Test class for {@link AddMapWithAILayerAction}
@ -63,11 +63,45 @@ import com.github.tomakehurst.wiremock.matching.UrlPathPattern;
@MapWithAISources
@Projection
class AddMapWithAILayerActionTest {
@Test
void testAddMapWithAILayerActionTest() {
MapWithAIInfo info = MapWithAILayerInfo.getInstance().getLayers().stream()
private static class ThreadSyncMWAI extends ThreadSync.ThreadSyncExtension {
public ThreadSyncMWAI() {
this.registerForkJoinPool(MapWithAIDataUtils.getForkJoinPool());
}
}
@RegisterExtension
static ThreadSyncMWAI threadSync = new ThreadSyncMWAI();
private static MapWithAIInfo info;
private static MapWithAIInfo backupInfo;
@BeforeEach
void setup(WireMockRuntimeInfo wireMockRuntimeInfo) {
final Map<String, StringValuePattern> parameterMap = new HashMap<>();
final AnythingPattern anythingPattern = new AnythingPattern();
parameterMap.put("geometryType", anythingPattern);
parameterMap.put("geometry", anythingPattern);
parameterMap.put("inSR", new EqualToPattern("4326"));
parameterMap.put("f", new EqualToPattern("geojson"));
parameterMap.put("outfields", new EqualToPattern("*"));
parameterMap.put("result_type", new EqualToPattern("road_building_vector_xml"));
parameterMap.put("resultOffset", anythingPattern);
wireMockRuntimeInfo.getWireMock()
.register(WireMock.get(new UrlPathPattern(new EqualToPattern("/query"), false))
.withQueryParams(parameterMap)
.willReturn(WireMock.aResponse()
.withBody(Json.createObjectBuilder().add("type", "FeatureCollection")
.add("features", Json.createArrayBuilder().build()).build().toString()))
.atPriority(Integer.MIN_VALUE));
info = MapWithAILayerInfo.getInstance().getLayers().stream()
.filter(i -> i.getName().equalsIgnoreCase("MapWithAI")).findAny().orElse(null);
assertNotNull(info);
info = new MapWithAIInfo(info);
}
@Test
void testAddMapWithAILayerActionTest() {
AddMapWithAILayerAction action = new AddMapWithAILayerAction(info);
assertDoesNotThrow(() -> action.actionPerformed(null));
OsmDataLayer osmLayer = new OsmDataLayer(new DataSet(), "TEST DATA", null);
@ -78,7 +112,7 @@ class AddMapWithAILayerActionTest {
assertNull(MapWithAIDataUtils.getLayer(false));
action.updateEnabledState();
action.actionPerformed(null);
Awaitility.await().atMost(Durations.FIVE_SECONDS).until(() -> MapWithAIDataUtils.getLayer(false) != null);
threadSync.threadSync();
assertNotNull(MapWithAIDataUtils.getLayer(false));
MainApplication.getLayerManager().removeLayer(MapWithAIDataUtils.getLayer(false));
@ -90,7 +124,7 @@ class AddMapWithAILayerActionTest {
action.updateEnabledState();
action.actionPerformed(null);
Awaitility.await().atMost(Durations.FIVE_SECONDS).until(() -> !layer.getDataSet().isEmpty());
threadSync.threadSync();
assertFalse(layer.getDataSet().isEmpty());
MainApplication.getLayerManager().removeLayer(MapWithAIDataUtils.getLayer(false));
@ -100,69 +134,40 @@ class AddMapWithAILayerActionTest {
mapwithaiLayer.getDataSet()
.addDataSource(new DataSource(new Bounds(39.095376, -108.4495519, 39.0987811, -108.4422314), ""));
action.actionPerformed(null);
Awaitility.await().atMost(Durations.FIVE_SECONDS).until(() -> !mapwithaiLayer.getDataSet().isEmpty());
threadSync.threadSync();
assertFalse(mapwithaiLayer.getDataSet().isEmpty());
}
@Test
void testRemoteIcon() throws IOException, ExecutionException, InterruptedException {
void testRemoteIcon(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException {
final ImageIcon blankImage = ImageProvider.createBlankIcon(ImageProvider.ImageSizes.LARGEICON);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// BufferedImage is what the current implementation uses. Otherwise, we will
// have to copy it into a BufferedImage.
assertTrue(blankImage.getImage() instanceof BufferedImage);
final BufferedImage bi = (BufferedImage) blankImage.getImage();
final BufferedImage bi = assertInstanceOf(BufferedImage.class, blankImage.getImage());
ImageIO.write(bi, "png", byteArrayOutputStream);
byte[] originalImage = byteArrayOutputStream.toByteArray();
final WireMockServer wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort());
try {
wireMockServer.start();
wireMockServer.addStubMapping(wireMockServer
.stubFor(WireMock.get("/icon").willReturn(WireMock.aResponse().withBody(originalImage))));
final MapWithAIInfo info = MapWithAILayerInfo.getInstance().getLayers().stream()
.filter(i -> i.getName().equalsIgnoreCase("MapWithAI")).findAny().orElse(null);
assertNotNull(info);
wireMockRuntimeInfo.getWireMock()
.register(WireMock.get("/icon").willReturn(WireMock.aResponse().withBody(originalImage)));
final MapWithAIInfo remoteInfo = new MapWithAIInfo(info);
remoteInfo.setIcon(wireMockServer.baseUrl() + "/icon");
remoteInfo.setIcon(wireMockRuntimeInfo.getHttpBaseUrl() + "/icon");
final AddMapWithAILayerAction action = new AddMapWithAILayerAction(remoteInfo);
GuiHelper.runInEDTAndWait(() -> {
/* Sync EDT */});
MainApplication.worker.submit(() -> {
/* Sync worker thread */}).get();
threadSync.threadSync();
final Object image = action.getValue(Action.LARGE_ICON_KEY);
assertTrue(image instanceof ImageIcon);
final ImageIcon attachedIcon = (ImageIcon) image;
assertTrue(attachedIcon.getImage() instanceof BufferedImage);
final ImageIcon attachedIcon = assertInstanceOf(ImageIcon.class, image);
final BufferedImage bufferedImage = assertInstanceOf(BufferedImage.class, attachedIcon.getImage());
byteArrayOutputStream.reset();
ImageIO.write((BufferedImage) attachedIcon.getImage(), "png", byteArrayOutputStream);
ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
final byte[] downloadedImage = byteArrayOutputStream.toByteArray();
assertArrayEquals(originalImage, downloadedImage);
} finally {
wireMockServer.stop();
}
}
@Test
void testNonRegression22683() throws ExecutionException, InterruptedException {
final MapWithAIInfo info = MapWithAILayerInfo.getInstance().getLayers().stream()
.filter(i -> i.getName().equalsIgnoreCase("MapWithAI")).findAny().orElse(null);
assertNotNull(info);
void testNonRegression22683(WireMockRuntimeInfo wireMockRuntimeInfo) {
final OsmDataLayer layer = new OsmDataLayer(new DataSet(), "testNonRegression22683", null);
layer.getDataSet().addDataSource(new DataSource(new Bounds(0, 0, 0.001, 0.001), "Area 1"));
layer.getDataSet().addDataSource(new DataSource(new Bounds(-0.001, -0.001, 0, 0), "Area 2"));
MainApplication.getLayerManager().addLayer(layer);
final WireMockServer server = new WireMockServer(WireMockConfiguration.options().dynamicPort());
final Map<String, StringValuePattern> parameterMap = new HashMap<>();
final AnythingPattern anythingPattern = new AnythingPattern();
parameterMap.put("geometryType", anythingPattern);
parameterMap.put("geometry", anythingPattern);
parameterMap.put("inSR", new EqualToPattern("4326"));
parameterMap.put("f", new EqualToPattern("geojson"));
parameterMap.put("outfields", new EqualToPattern("*"));
parameterMap.put("result_type", new EqualToPattern("road_building_vector_xml"));
parameterMap.put("resultOffset", anythingPattern);
server.stubFor(WireMock.get(new UrlPathPattern(new EqualToPattern("/query"), false))
.withQueryParams(parameterMap).willReturn(WireMock.aResponse().withBody("{\"test\":0}")));
final List<LogRecord> logs = new ArrayList<>();
Handler testHandler = new Handler() {
@Override
@ -182,22 +187,16 @@ class AddMapWithAILayerActionTest {
};
Logging.getLogger().addHandler(testHandler);
try {
server.start();
info.setUrl(server.baseUrl());
info.setUrl(wireMockRuntimeInfo.getHttpBaseUrl());
info.setSourceType(MapWithAIType.ESRI_FEATURE_SERVER);
final AddMapWithAILayerAction action = new AddMapWithAILayerAction(info);
Logging.clearLastErrorAndWarnings();
assertDoesNotThrow(() -> action.actionPerformed(null));
GuiHelper.runInEDTAndWait(() -> {
/* Sync thread */ });
MainApplication.worker.submit(() -> {
/* Sync thread */ }).get();
threadSync.threadSync();
final List<LogRecord> ides = logs.stream()
.filter(record -> record.getThrown() instanceof IllegalArgumentException)
.collect(Collectors.toList());
.filter(record -> record.getThrown() instanceof IllegalArgumentException).toList();
assertTrue(ides.isEmpty(), ides.stream().map(LogRecord::getMessage).collect(Collectors.joining("\n")));
} finally {
server.stop();
Logging.getLogger().removeHandler(testHandler);
}
}

Some files were not shown because too many files have changed in this diff Show More