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