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