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}