    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}