    001// License: GPL. For details, see LICENSE file.
    002package org.openstreetmap.josm.plugins.streetside.io.download;
    003
    004import java.util.concurrent.ArrayBlockingQueue;
    005import java.util.concurrent.ThreadPoolExecutor;
    006import java.util.concurrent.TimeUnit;
    007
    008import org.apache.log4j.Logger;
    009import org.openstreetmap.josm.data.Bounds;
    010import org.openstreetmap.josm.data.coor.LatLon;
    011import org.openstreetmap.josm.gui.MainApplication;
    012import org.openstreetmap.josm.gui.Notification;
    013import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    014import org.openstreetmap.josm.plugins.streetside.StreetsidePlugin;
    015import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
    016import org.openstreetmap.josm.tools.I18n;
    017
    018/**
    019 * Class that concentrates all the ways of downloading of the plugin. All the
    020 * download petitions will be managed one by one.
    021 *
    022 * @author nokutu
    023 */
    024public final class StreetsideDownloader {
    025
    026  final static Logger logger = Logger.getLogger(StreetsideDownloader.class);
    027
    028  /** Possible download modes. */
    029  public enum DOWNLOAD_MODE {
    030    VISIBLE_AREA("visibleArea", I18n.tr("everything in the visible area")),
    031
    032    OSM_AREA("osmArea", I18n.tr("areas with downloaded OSM-data")),
    033
    034    MANUAL_ONLY("manualOnly", I18n.tr("only when manually requested"));
    035
    036    public final static DOWNLOAD_MODE DEFAULT = OSM_AREA;
    037
    038    private final String prefId;
    039    private final String label;
    040
    041    DOWNLOAD_MODE(String prefId, String label) {
    042      this.prefId = prefId;
    043      this.label = label;
    044    }
    045
    046    /**
    047     * @return the ID that is used to represent this download mode in the JOSM preferences
    048     */
    049    public String getPrefId() {
    050      return prefId;
    051    }
    052
    053    /**
    054     * @return the (internationalized) label describing this download mode
    055     */
    056    public String getLabel() {
    057      return label;
    058    }
    059
    060    public static DOWNLOAD_MODE fromPrefId(String prefId) {
    061      for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
    062        if (mode.getPrefId().equals(prefId)) {
    063          return mode;
    064        }
    065      }
    066      return DEFAULT;
    067    }
    068
    069    public static DOWNLOAD_MODE fromLabel(String label) {
    070      for (DOWNLOAD_MODE mode : DOWNLOAD_MODE.values()) {
    071        if (mode.getLabel().equals(label)) {
    072          return mode;
    073        }
    074      }
    075      return DEFAULT;
    076    }
    077  }
    078
    079  /** Max area to be downloaded */
    080  private static final double MAX_AREA = StreetsideProperties.MAX_DOWNLOAD_AREA.get();
    081
    082  /** Executor that will run the petitions. */
    083  private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
    084    3, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
    085
    086  /**
    087   * Indicates whether the last download request has been rejected because it requested an area that was too big.
    088   * Iff true, the last download has been rejected, if false, it was executed.
    089   */
    090  private static boolean stoppedDownload;
    091
    092  private StreetsideDownloader() {
    093    // Private constructor to avoid instantiation
    094  }
    095
    096  /**
    097   * Gets all the images in a square. It downloads all the images of all the
    098   * sequences that pass through the given rectangle.
    099   *
    100   * @param minLatLon The minimum latitude and longitude of the rectangle.
    101   * @param maxLatLon The maximum latitude and longitude of the rectangle
    102   */
    103  public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
    104    if (minLatLon == null || maxLatLon == null) {
    105      throw new IllegalArgumentException();
    106    }
    107    getImages(new Bounds(minLatLon, maxLatLon));
    108  }
    109
    110  /**
    111   * Gets the images within the given bounds.
    112   *
    113   * @param bounds A {@link Bounds} object containing the area to be downloaded.
    114   */
    115  public static void getImages(Bounds bounds) {
    116    run(new StreetsideSquareDownloadRunnable(bounds));
    117  }
    118
    119  /**
    120   * Returns the current download mode.
    121   *
    122   * @return the currently enabled {@link DOWNLOAD_MODE}
    123   */
    124  public static DOWNLOAD_MODE getMode() {
    125    return DOWNLOAD_MODE.fromPrefId(StreetsideProperties.DOWNLOAD_MODE.get());
    126  }
    127
    128  private static void run(Runnable t) {
    129    executor.execute(t);
    130  }
    131
    132  /**
    133   * If some part of the current view has not been downloaded, it is downloaded.
    134   */
    135  public static void downloadVisibleArea() {
    136    Bounds view = MainApplication.getMap().mapView.getRealBounds();
    137    if (isAreaTooBig(view.getArea())) {
    138      return;
    139    }
    140    if (isViewDownloaded(view)) {
    141      return;
    142    }
    143    StreetsideLayer.getInstance().getData().getBounds().add(view);
    144    getImages(view);
    145  }
    146
    147  private static boolean isViewDownloaded(Bounds view) {
    148    int n = 15;
    149    boolean[][] inside = new boolean[n][n];
    150    for (int i = 0; i < n; i++) {
    151      for (int j = 0; j < n; j++) {
    152        if (isInBounds(new LatLon(view.getMinLat()
    153          + (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
    154          view.getMinLon() + (view.getMaxLon() - view.getMinLon())
    155            * ((double) j / n)))) {
    156          inside[i][j] = true;
    157        }
    158      }
    159    }
    160    for (int i = 0; i < n; i++) {
    161      for (int j = 0; j < n; j++) {
    162        if (!inside[i][j])
    163          return false;
    164      }
    165    }
    166    return true;
    167  }
    168
    169  /**
    170   * Checks if the given {@link LatLon} object lies inside the bounds of the
    171   * image.
    172   *
    173   * @param latlon The coordinates to check.
    174   *
    175   * @return true if it lies inside the bounds; false otherwise;
    176   */
    177  private static boolean isInBounds(LatLon latlon) {
    178    return StreetsideLayer.getInstance().getData().getBounds().parallelStream().anyMatch(b -> b.contains(latlon));
    179  }
    180
    181  /**
    182   * Downloads all images of the area covered by the OSM data.
    183   */
    184  public static void downloadOSMArea() {
    185    if (MainApplication.getLayerManager().getEditLayer() == null) {
    186      return;
    187    }
    188    if (isAreaTooBig(MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().parallelStream().map(Bounds::getArea).reduce(0.0, Double::sum))) {
    189      return;
    190    }
    191    MainApplication.getLayerManager().getEditLayer().data.getDataSourceBounds().stream().filter(bounds -> !StreetsideLayer.getInstance().getData().getBounds().contains(bounds)).forEach(bounds -> {
    192      StreetsideLayer.getInstance().getData().getBounds().add(bounds);
    193      StreetsideDownloader.getImages(bounds.getMin(), bounds.getMax());
    194    });
    195  }
    196
    197  /**
    198   * Checks if the area for which Streetside images should be downloaded is too big. This means that probably
    199   * lots of Streetside images are going to be downloaded, slowing down the
    200   * program too much. A notification is shown when the download has stopped or continued.
    201   * @param area area to check
    202   * @return {@code true} if the area is too big
    203   */
    204  private static boolean isAreaTooBig(final double area) {
    205    final boolean tooBig = area > MAX_AREA;
    206    if (!stoppedDownload && tooBig) {
    207      new Notification(
    208        I18n.tr("The Streetside layer has stopped downloading images, because the requested area is too big!") + (
    209          getMode() == DOWNLOAD_MODE.VISIBLE_AREA
    210          ? "\n"+I18n.tr("To solve this problem, you could zoom in and load a smaller area of the map.")
    211          : (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): "")
    212        )
    213      ).setIcon(StreetsidePlugin.LOGO.get()).setDuration(Notification.TIME_LONG).show();
    214    }
    215    if (stoppedDownload && !tooBig) {
    216      new Notification("The Streetside layer now continues to download images…").setIcon(StreetsidePlugin.LOGO.get()).show();
    217    }
    218    stoppedDownload = tooBig;
    219    return tooBig;
    220  }
    221
    222  /**
    223   * Stops all running threads.
    224   */
    225  public static void stopAll() {
    226    executor.shutdownNow();
    227    try {
    228      executor.awaitTermination(30, TimeUnit.SECONDS);
    229    } catch (InterruptedException e) {
    230      logger.error(e);
    231    }
    232    executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
    233      new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());
    234  }
    235}