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}