kopia lustrzana https://github.com/JOSM/MapWithAI
Porównaj commity
11 Commity
Autor | SHA1 | Data |
---|---|---|
![]() |
cecb9f40be | |
![]() |
7f725f7e4c | |
![]() |
f7c1d3d8b0 | |
![]() |
fbd3f10541 | |
![]() |
faa031e49c | |
![]() |
ca0b8e479f | |
![]() |
ad6ec16acd | |
![]() |
d56513358d | |
![]() |
eff911c300 | |
![]() |
d13d61f13c | |
![]() |
68b9ff91c1 |
|
@ -10,8 +10,6 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- $default-branch
|
||||
schedule:
|
||||
- cron: "1 5 * * 6"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
@ -19,7 +17,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
josm-revision: ["", "r18877"]
|
||||
uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v1
|
||||
uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v2
|
||||
with:
|
||||
java-version: 17
|
||||
josm-revision: ${{ matrix.josm-revision }}
|
||||
|
|
|
@ -10,4 +10,4 @@ permissions:
|
|||
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: JOSM/JOSMPluginAction/.github/workflows/reports.yaml@v1
|
||||
uses: JOSM/JOSMPluginAction/.github/workflows/reports.yaml@v2
|
||||
|
|
18
build.gradle
18
build.gradle
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -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
|
||||
|
|
|
@ -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,10 +130,13 @@ 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.
|
||||
|
@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
|
@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,11 +23,10 @@ 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;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
|
||||
import org.openstreetmap.josm.data.Bounds;
|
||||
|
@ -86,114 +85,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 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
|
||||
*/
|
||||
private 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(tile1.y, tile2.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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends a bounds object to contain this tile
|
||||
*
|
||||
* @param currentBounds The bounds to extend
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
private final String url;
|
||||
private final boolean crop;
|
||||
private final int start;
|
||||
|
@ -240,14 +131,20 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
|
|||
final var tile = TileXYZ.tileFromBBox(lon1, lat1, lon2, lat2);
|
||||
return getRequestForTile(tile);
|
||||
}
|
||||
return url.replace("{bbox}", Double.toString(lon1) + ',' + lat1 + ',' + lon2 + ',' + lat2)
|
||||
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))
|
||||
+ (crop ? "&crop_bbox=" + DetectTaskingManagerUtils.getTaskingManagerBounds().toBBox().toStringCSV(",")
|
||||
: "")
|
||||
+ (this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER && !this.info.isConflated()
|
||||
? "&resultOffset=" + this.start
|
||||
: "");
|
||||
.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) {
|
||||
|
@ -440,19 +337,22 @@ public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
|
|||
}
|
||||
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);
|
||||
: getInputStream(getRequestForTile(tileXYZ), progressMonitor.createSubTaskMonitor(1, true));
|
||||
final var dataSet = loadTile(tileSource, tileXYZ, data);
|
||||
ds.mergeFrom(dataSet, progressMonitor);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -213,37 +214,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -213,9 +213,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
|
||||
|
@ -342,11 +346,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();
|
||||
|
@ -355,9 +360,10 @@ public class MapWithAILayerInfo {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
allDefaultLayers.add(layer);
|
||||
esriInfo.add(layer);
|
||||
}
|
||||
}
|
||||
layers.addAll(esriInfo);
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
|
@ -365,7 +371,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()));
|
||||
|
@ -501,7 +507,7 @@ public class MapWithAILayerInfo {
|
|||
}
|
||||
|
||||
if (changed) {
|
||||
save();
|
||||
MainApplication.worker.execute(this::save);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// License: GPL. For details, see LICENSE file.
|
||||
package org.openstreetmap.josm.plugins.mapwithai.backend;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.openstreetmap.josm.data.Bounds;
|
||||
|
||||
/**
|
||||
* Test class for {@link TileXYZ}
|
||||
*/
|
||||
class TileXYZTest {
|
||||
static Stream<Arguments> testTileCalculations() {
|
||||
return Stream.of(Arguments.of(39.07035, -108.5709286, 52013, 100120, 18),
|
||||
Arguments.of(39.0643941, -108.5610312, 52020, 100125, 18),
|
||||
Arguments.of(39.0643941, -108.5709286, 52013, 100125, 18),
|
||||
Arguments.of(39.07035, -108.5610312, 52020, 100120, 18));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testTileCalculations(double lat, double lon, int x, int y, int z) {
|
||||
final var tile = TileXYZ.tileFromLatLonZoom(lon, lat, z);
|
||||
assertAll(() -> assertEquals(x, tile.x()), () -> assertEquals(y, tile.y()), () -> assertEquals(z, tile.z()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the tiles calculated for a bbox are correct
|
||||
*/
|
||||
@Test
|
||||
void testNonRegressionGH44() {
|
||||
final var tiles = TileXYZ.tilesFromBBox(18, new Bounds(39.0643941, -108.5709286, 39.07035, -108.5610312))
|
||||
.toArray(TileXYZ[]::new);
|
||||
assertAll(() -> assertEquals(100125, Arrays.stream(tiles).mapToInt(TileXYZ::y).max().orElse(0)),
|
||||
() -> assertEquals(100120, Arrays.stream(tiles).mapToInt(TileXYZ::y).min().orElse(0)),
|
||||
() -> assertEquals(52013, Arrays.stream(tiles).mapToInt(TileXYZ::x).min().orElse(0)),
|
||||
() -> assertEquals(52020, Arrays.stream(tiles).mapToInt(TileXYZ::x).max().orElse(0)));
|
||||
assertEquals(48, tiles.length, "Should be 6x8 tiles (rangeClosed)");
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue