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