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