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}