001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.streetside.cubemap; 003 004import java.awt.image.BufferedImage; 005import java.text.MessageFormat; 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010import java.util.concurrent.Callable; 011import java.util.concurrent.ExecutorService; 012import java.util.concurrent.Executors; 013import java.util.concurrent.Future; 014 015import org.apache.log4j.Logger; 016import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 017import org.openstreetmap.josm.plugins.streetside.StreetsideCubemap; 018import org.openstreetmap.josm.plugins.streetside.StreetsideDataListener; 019import org.openstreetmap.josm.plugins.streetside.gui.StreetsideViewerDialog; 020import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.StreetsideViewerPanel; 021import org.openstreetmap.josm.plugins.streetside.utils.CubemapBox; 022import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 023 024import javafx.scene.image.Image; 025import javafx.scene.image.ImageView; 026 027// JavaFX access in Java 8 028@SuppressWarnings("restriction") 029public class CubemapBuilder implements ITileDownloadingTaskListener, StreetsideDataListener { 030 031 final static Logger logger = Logger.getLogger(CubemapBuilder.class); 032 033 private static CubemapBuilder instance; 034 private StreetsideCubemap cubemap; 035 protected boolean cancelled; 036 private long startTime; 037 private Map<String, BufferedImage> tileImages = new HashMap<>(); 038 039 /** 040 * @return the tileImages 041 */ 042 public Map<String, BufferedImage> getTileImages() { 043 return tileImages; 044 } 045 046 /** 047 * @param tileImages the tileImages to set 048 */ 049 public void setTileImages(Map<String, BufferedImage> tileImages) { 050 this.tileImages = tileImages; 051 } 052 053 private CubemapBuilder() { 054 // private constructor to avoid instantiation 055 } 056 057 @Override 058 public void imagesAdded() { 059 // Do nothing 060 } 061 062 @Override 063 public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) { 064 startTime = System.currentTimeMillis(); 065 066 if (newImage != null) { 067 068 cubemap = null; 069 cubemap = new StreetsideCubemap(newImage.getId(), newImage.getLatLon(), newImage.getHe()); 070 cubemap.setCd(newImage.getCd()); 071 072 // download cubemap images in different threads and then subsequently 073 // set the cubeface images in JavaFX 074 downloadCubemapImages(cubemap.getId()); 075 076 long runTime = (System.currentTimeMillis()-startTime)/1000; 077 if(StreetsideProperties.DEBUGING_ENABLED.get()) { 078 logger.debug(MessageFormat.format("Completed downloading tiles for {0} in {1} seconds.", newImage.getId() , runTime)); 079 } 080 } 081 } 082 083 public void reload(String imageId) { 084 if (cubemap != null && imageId.equals(cubemap.getId())) { 085 tileImages = new HashMap<>(); 086 downloadCubemapImages(imageId); 087 } 088 } 089 090 public void downloadCubemapImages(String imageId) { 091 092 final int maxCols = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 093 final int maxRows = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 094 final int maxThreadCount = 6 * maxCols * maxRows; 095 096 int fails = 0; 097 098 int min = 0; int max = (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()?96:24)*2; 099 100 String[] message = new String[2]; 101 message[0] = MessageFormat.format("Downloading Streetside imagery for {0}", imageId); 102 message[1] = "Wait for completion……."; 103 104 long startTime = System.currentTimeMillis(); 105 106 try { 107 108 ExecutorService pool = Executors.newFixedThreadPool(maxThreadCount); 109 List<Callable<String>> tasks = new ArrayList<>(maxThreadCount); 110 111 // launch 4-tiled (low-res) downloading tasks . . . 112 if (!StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 113 for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) { 114 int tileNr = 0; 115 for (int j = 0; j < maxCols; j++) { 116 for (int k = 0; k < maxRows; k++) { 117 118 String tileId = String.valueOf(imageId + CubemapUtils.getFaceNumberForCount(i) 119 + Integer.valueOf(tileNr++).toString()); 120 tasks.add(new TileDownloadingTask(tileId)); 121 } 122 } 123 } 124 125 List<Future<String>> results = pool.invokeAll(tasks); 126 for (Future<String> ff : results) { 127 128 if(StreetsideProperties.DEBUGING_ENABLED.get()) { 129 logger.debug(MessageFormat.format("Completed tile downloading task {0} in {1} seconds.", ff.get(), (startTime - System.currentTimeMillis())/ 1000)); 130 } 131 } 132 133 // launch 16-tiled (high-res) downloading tasks 134 } else if (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 135 for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) { 136 for (int j = 0; j < maxCols; j++) { 137 for (int k = 0; k < maxRows; k++) { 138 139 String tileId = String.valueOf(imageId + CubemapUtils.getFaceNumberForCount(i) 140 + String.valueOf(Integer.valueOf(j).toString() + Integer.valueOf(k).toString())); 141 tasks.add(new TileDownloadingTask(tileId)); 142 } 143 } 144 } 145 146 List<Future<String>> results = pool.invokeAll(tasks); 147 for (Future<String> ff : results) { 148 if(StreetsideProperties.DEBUGING_ENABLED.get()) { 149 logger.debug(MessageFormat.format("Completed tile downloading task {0} in {1} seconds.",ff.get(), 150 (startTime - System.currentTimeMillis())/ 1000)); 151 } 152 } 153 } 154 } catch (Exception ee) { 155 fails++; 156 logger.error("Error loading tile for image " + imageId); 157 ee.printStackTrace(); 158 } 159 160 long stopTime = System.currentTimeMillis(); 161 long runTime = stopTime - startTime; 162 163 if (StreetsideProperties.DEBUGING_ENABLED.get()) { 164 logger.debug(MessageFormat.format("Tile imagery downloading tasks completed in {0} seconds.", runTime/1000000)); 165 } 166 167 if (fails > 0) { 168 logger.error(Integer.valueOf(fails) + " downloading tasks failed!"); 169 } 170 171 } 172 173 @Override 174 public void tileAdded(String tileId) { 175 // determine whether all tiles have been set for each of the 176 // six cubemap faces. If so, build the images for the faces 177 // and set the views in the cubemap box. 178 179 int tileCount = 0; 180 181 tileCount = CubemapBuilder.getInstance().getTileImages().keySet().size(); 182 183 int maxCols = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 184 int maxRows = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 185 186 if (tileCount == (CubemapUtils.NUM_SIDES * maxCols * maxRows)) { 187 if (StreetsideProperties.DEBUGING_ENABLED.get()) { 188 logger.debug(MessageFormat.format("{0} tile images ready for building cumbemap faces for cubemap {1}.", tileCount, 189 CubemapBuilder.getInstance().getCubemap().getId())); 190 } 191 192 buildCubemapFaces(); 193 } 194 } 195 196 @Override 197 public void tilesAdded(String[] tileIds) { 198 // determine whether all tiles have been set for each of the 199 // six cubemap faces. If so, build the images for the faces 200 // and set the views in the cubemap box. 201 202 int tileCount = 0; 203 204 tileCount = CubemapBuilder.getInstance().getTileImages().keySet().size(); 205 206 int maxCols = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 207 int maxRows = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 208 209 if (tileCount == (CubemapUtils.NUM_SIDES * maxCols * maxRows)) { 210 if (StreetsideProperties.DEBUGING_ENABLED.get()) { 211 logger.debug(MessageFormat.format("{0} tile images ready for building cumbemap faces for cubemap {1}.", tileCount, 212 CubemapBuilder.getInstance().getCubemap().getId())); 213 } 214 215 buildCubemapFaces(); 216 } 217 } 218 219 private void buildCubemapFaces() { 220 221 if (StreetsideProperties.DEBUGING_ENABLED.get()) { 222 logger.debug("Assembling cubemap tile images"); 223 } 224 225 CubemapBox cmb = StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().getCubemapBox(); 226 ImageView[] views = cmb.getViews(); 227 228 final int maxCols = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 229 final int maxRows = StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get() ? 4 : 2; 230 231 Image finalImages[] = new Image[CubemapUtils.NUM_SIDES]; 232 233 // build 4-tiled cubemap faces and crop buffers 234 if (!StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 235 for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) { 236 237 BufferedImage[] faceTileImages = new BufferedImage[maxCols * maxRows]; 238 239 for (int j = 0; j < (maxCols * maxRows); j++) { 240 String tileId = String.valueOf(getCubemap().getId() + CubemapUtils.getFaceNumberForCount(i) 241 + Integer.valueOf(j).toString()); 242 BufferedImage currentTile = tileImages.get(tileId); 243 244 faceTileImages[j] = currentTile; 245 } 246 247 BufferedImage finalImg = GraphicsUtils.buildMultiTiledCubemapFaceImage(faceTileImages); 248 249 // rotate top cubeface 180 degrees - misalignment workaround 250 if (i == 4) { 251 finalImg = GraphicsUtils.rotateImage(finalImg); 252 } 253 finalImages[i] = GraphicsUtils.convertBufferedImage2JavaFXImage(finalImg); 254 } 255 // build 16-tiled cubemap faces and crop buffers 256 } else if (StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY.get()) { 257 for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) { 258 259 int tileCount = 0; 260 261 BufferedImage[] faceTileImages = new BufferedImage[StreetsideProperties.SHOW_HIGH_RES_STREETSIDE_IMAGERY 262 .get() ? 16 : 4]; 263 264 for (int j = 0; j < maxCols; j++) { 265 for (int k = 0; k < maxRows; k++) { 266 String tileId = String.valueOf(getCubemap().getId() + CubemapUtils.getFaceNumberForCount(i) 267 + CubemapUtils.convertDoubleCountNrto16TileNr( 268 String.valueOf(Integer.valueOf(j).toString() + Integer.valueOf(k).toString()))); 269 BufferedImage currentTile = tileImages.get(tileId); 270 faceTileImages[tileCount++] = currentTile; 271 } 272 } 273 BufferedImage finalImg = GraphicsUtils.buildMultiTiledCubemapFaceImage(faceTileImages); 274 // rotate top cubeface 180 degrees - misalignment workaround 275 if (i == 4) { 276 finalImg = GraphicsUtils.rotateImage(finalImg); 277 } 278 finalImages[i] = GraphicsUtils.convertBufferedImage2JavaFXImage(finalImg); 279 } 280 } 281 282 for (int i = 0; i < CubemapUtils.NUM_SIDES; i++) { 283 views[i].setImage(finalImages[i]); 284 } 285 286 StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().revalidate(); 287 StreetsideViewerDialog.getInstance().getStreetsideViewerPanel().repaint(); 288 289 StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel() 290 .setScene(StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().getCubemapScene()); 291 292 StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().revalidate(); 293 StreetsideViewerPanel.getThreeSixtyDegreeViewerPanel().repaint(); 294 295 long endTime = System.currentTimeMillis(); 296 long runTime = (endTime - startTime) / 1000; 297 298 String message = MessageFormat.format("Completed downloading, assembling and setting cubemap imagery for cubemap {0} in {1} seconds.", cubemap.getId(), 299 runTime); 300 301 if (StreetsideProperties.DEBUGING_ENABLED.get()) { 302 logger.debug(message); 303 } 304 305 CubemapBuilder.getInstance().resetTileImages(); 306 } 307 308 private void resetTileImages() { 309 tileImages = new HashMap<>(); 310 } 311 312 /** 313 * @return the cubemap 314 */ 315 public synchronized StreetsideCubemap getCubemap() { 316 return cubemap; 317 } 318 319 /** 320 * @param cubemap 321 * the cubemap to set 322 */ 323 public static void setCubemap(StreetsideCubemap cubemap) { 324 CubemapBuilder.getInstance().cubemap = cubemap; 325 } 326 327 public static CubemapBuilder getInstance() { 328 if (instance == null) { 329 instance = new CubemapBuilder(); 330 } 331 return instance; 332 } 333 334 /** 335 * @return true, iff the singleton instance is present 336 */ 337 public static boolean hasInstance() { 338 return CubemapBuilder.instance != null; 339 } 340 341 /** 342 * Destroys the unique instance of the class. 343 */ 344 public static synchronized void destroyInstance() { 345 CubemapBuilder.instance = null; 346 } 347}