StreetsideDownloader.java
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.streetside.io.download;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.Logging;
/**
* Class that concentrates all the ways of downloading of the plugin. All the
* download petitions will be managed one by one.
*
* @author nokutu
*/
public final class StreetsideDownloader {
/** Possible download modes. */
public enum DOWNLOAD_MODE {
// i18n: download mode for Streetside images
VISIBLE_AREA("visibleArea", I18n.tr("everything in the visible area")),
// i18n: download mode for Streetside images
OSM_AREA("osmArea", I18n.tr("areas with downloaded OSM-data")),
// i18n: download mode for Streetside images
MANUAL_ONLY("manualOnly", I18n.tr("only when manually requested"));
public final static DOWNLOAD_MODE DEFAULT = OSM_AREA;
private final String prefId;
private final String label;
DOWNLOAD_MODE(String prefId, String label) {
this.prefId = prefId;
this.label = label;
}
/**
* @return the ID that is used to represent this download mode in the JOSM preferences
*/
public String getPrefId() {
return prefId;
}
/**
* @return the (internationalized) label describing this download mode
*/
public String getLabel() {
return label;
}
public static DOWNLOAD_MODE fromPrefId(String prefId) {
for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
if (mode.getPrefId().equals(prefId)) {
return mode;
}
}
return DEFAULT;
}
public static DOWNLOAD_MODE fromLabel(String label) {
for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
if (mode.getLabel().equals(label)) {
return mode;
}
}
return DEFAULT;
}
}
/** Max area to be downloaded */
private static final double MAX_AREA = StreetsideProperties.MAX_DOWNLOAD_AREA.get();
/** Executor that will run the petitions. */
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
/**
* Indicates whether the last download request has been rejected because it requested an area that was too big.
* Iff true, the last download has been rejected, if false, it was executed.
*/
private static boolean stoppedDownload;
private StreetsideDownloader() {
// Private constructor to avoid instantiation
}
/**
* Gets all the images in a square. It downloads all the images of all the
* sequences that pass through the given rectangle.
*
* @param minLatLon The minimum latitude and longitude of the rectangle.
* @param maxLatLon The maximum latitude and longitude of the rectangle
*/
public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
if (minLatLon == null || maxLatLon == null) {
throw new IllegalArgumentException();
}
getImages(new Bounds(minLatLon, maxLatLon));
}
/**
* Gets the images within the given bounds.
*
* @param bounds A {@link Bounds} object containing the area to be downloaded.
*/
public static void getImages(Bounds bounds) {
run(new StreetsideSquareDownloadRunnable(bounds));
}
/**
* Returns the current download mode.
*
* @return the currently enabled {@link DOWNLOAD_MODE}
*/
public static DOWNLOAD_MODE getMode() {
return DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get());
}
private static void run(Runnable t) {
executor.execute(t);
}
/**
* If some part of the current view has not been downloaded, it is downloaded.
*/
public static void downloadVisibleArea() {
Bounds view = MainApplication.getMap().mapView.getRealBounds();
if (isAreaTooBig(view.getArea())) {
return;
}
if (isViewDownloaded(view)) {
return;
}
StreetsideLayer.getInstance().getData().getBounds().add(view);
getImages(view);
}
private static boolean isViewDownloaded(Bounds view) {
int n = 15;
boolean[][] inside = new boolean[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (isInBounds(new LatLon(view.getMinLat()
+ (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
view.getMinLon() + (view.getMaxLon() - view.getMinLon())
* ((double) j / n)))) {
inside[i][j] = true;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (!inside[i][j])
return false;
}
}
return true;
}
/**
* Checks if the given {@link LatLon} object lies inside the bounds of the
* image.
*
* @param latlon The coordinates to check.
*
* @return true if it lies inside the bounds; false otherwise;
*/
private static boolean isInBounds(LatLon latlon) {
return StreetsideLayer.getInstance().getData().getBounds().parallelStream().anyMatch(b -> b.contains(latlon));
}
/**
* Downloads all images of the area covered by the OSM data.
*/
public static void downloadOSMArea() {
if (MainApplication.getLayerManager().getEditLayer() == null) {
return;
}
if (isAreaTooBig(MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().parallelStream().map(Bounds::getArea).reduce(0.0, Double::sum))) {
return;
}
MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream().filter(bounds -> !StreetsideLayer.getInstance().getData().getBounds().contains(bounds)).forEach(bounds -> {
StreetsideLayer.getInstance().getData().getBounds().add(bounds);
StreetsideDownloader.getImages(bounds.getMin(), bounds.getMax());
});
}
/**
* Checks if the area for which Streetside images should be downloaded is too big. This means that probably
* lots of Streetside images are going to be downloaded, slowing down the
* program too much. A notification is shown when the download has stopped or continued.
*/
private static boolean isAreaTooBig(final double area) {
final boolean tooBig = area > MAX_AREA;
if (!stoppedDownload && tooBig) {
new Notification(
I18n.tr("The Streetside layer has stopped downloading images, because the requested area is too big!") + (
getMode() == DOWNLOAD_MODE.VISIBLE_AREA
? "\n"+I18n.tr("To solve this problem, you could zoom in and load a smaller area of the map.")
: (getMode() == DOWNLOAD_MODE.OSM_AREA ? "\n"+I18n.tr("To solve this problem, you could switch to download mode ''{0}'' and load Streetside images for a smaller portion of the map.", DOWNLOAD_MODE.MANUAL_ONLY): "")
)
).setIcon(StreetsidePlugin.LOGO.get()).setDuration(Notification.TIME_LONG).show();
}
if (stoppedDownload && !tooBig) {
new Notification("The Streetside layer now continues to download images…").setIcon(StreetsidePlugin.LOGO.get()).show();
}
stoppedDownload = tooBig;
return tooBig;
}
/**
* Stops all running threads.
*/
public static void stopAll() {
executor.shutdownNow();
try {
executor.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Logging.error(e);
}
executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
}
}