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