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}