    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}