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