source: osm/applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideData.java

Last change on this file was 36228, checked in by taylor.smock, 19 months ago

StreetSide: Update to official API

This also moves the plugin to Java 21 (mostly for virtual threads), reformats the
code to match the JOSM standard (4 spaces), and fixes a bunch of lint issues.

Additionally, a lot of cruft from when this plugin was copied from Mapillary was
removed. That was largely related to image import, uploading, and login.

File size: 13.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.streetside;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.Comparator;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Objects;
12import java.util.concurrent.CopyOnWriteArrayList;
13import java.util.function.Consumer;
14
15import org.apache.commons.jcs3.access.CacheAccess;
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.Data;
18import org.openstreetmap.josm.data.DataSource;
19import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.osm.BBox;
22import org.openstreetmap.josm.data.osm.QuadBuckets;
23import org.openstreetmap.josm.gui.MainApplication;
24import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils;
25import org.openstreetmap.josm.plugins.streetside.cache.Caches;
26import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
27import org.openstreetmap.josm.plugins.streetside.gui.StreetsideViewerDialog;
28import org.openstreetmap.josm.plugins.streetside.gui.imageinfo.ImageInfoPanel;
29import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
30
31/**
32 * Database class for all the {@link StreetsideAbstractImage} objects.
33 *
34 * @author nokutu
35 * @author renerr18 (extended for Streetside)
36 * @see StreetsideAbstractImage
37 */
38public class StreetsideData implements Data {
39 private final QuadBuckets<StreetsideImage> images = new QuadBuckets<>();
40 private final List<StreetsideImage> sortedImages = new ArrayList<>();
41 /**
42 * Listeners of the class.
43 */
44 private final List<StreetsideDataListener> listeners = new CopyOnWriteArrayList<>();
45 /**
46 * The bounds of the areas for which the pictures have been downloaded.
47 */
48 private final List<Bounds> bounds;
49 /**
50 * The image currently selected, this is the one being shown.
51 */
52 private StreetsideImage selectedImage;
53 /**
54 * The image under the cursor.
55 */
56 private StreetsideImage highlightedImage;
57
58 /**
59 * Creates a new object and adds the initial set of listeners.
60 */
61 protected StreetsideData() {
62 selectedImage = null;
63 bounds = new CopyOnWriteArrayList<>();
64
65 // Adds the basic set of listeners.
66 Arrays.stream(StreetsidePlugin.getStreetsideDataListeners()).forEach(this::addListener);
67 addListener(StreetsideViewerDialog.getInstance().getStreetsideViewerPanel());
68 addListener(StreetsideMainDialog.getInstance());
69 addListener(ImageInfoPanel.getInstance());
70 }
71
72 /**
73 * Downloads surrounding images of this mapillary image in background threads
74 *
75 * @param streetsideImage the image for which the surrounding images should be downloaded
76 */
77 private void downloadSurroundingImages(StreetsideImage streetsideImage) {
78 MainApplication.worker.execute(() -> downloadSurrounding(streetsideImage, CacheUtils::downloadPicture));
79 }
80
81 /**
82 * Downloads surrounding images of this mapillary image in background threads
83 *
84 * @param streetsideImage the image for which the surrounding images should be downloaded
85 */
86 public void downloadSurroundingCubemaps(StreetsideImage streetsideImage) {
87 MainApplication.worker.execute(() -> downloadSurrounding(streetsideImage, CacheUtils::downloadCubemap));
88 }
89
90 private void downloadSurrounding(StreetsideImage streetsideImage, Consumer<StreetsideImage> imageDownloader) {
91 final int prefetchCount = StreetsideProperties.PRE_FETCH_IMAGE_COUNT.get();
92 CacheAccess<String, BufferedImageCacheEntry> imageCache = Caches.ImageCache.getInstance().getCache();
93
94 StreetsideImage nextImage = next(streetsideImage);
95 StreetsideImage prevImage = previous(streetsideImage);
96
97 for (var i = 0; i < prefetchCount; i++) {
98 if (nextImage != null) {
99 if (imageCache.get(nextImage.id()) == null) {
100 imageDownloader.accept(nextImage);
101 }
102 nextImage = next(nextImage);
103 }
104 if (prevImage != null) {
105 if (imageCache.get(prevImage.id()) == null) {
106 imageDownloader.accept(prevImage);
107 }
108 prevImage = previous(prevImage);
109 }
110 }
111 }
112
113 /**
114 * Adds an StreetsideImage to the object, and then repaints mapView.
115 *
116 * @param image The image to be added.
117 */
118 public void add(StreetsideImage image) {
119 add(image, true);
120 }
121
122 /**
123 * Adds a StreetsideImage to the object, but doesn't repaint mapView. This is
124 * needed for concurrency.
125 *
126 * @param image The image to be added.
127 * @param update Whether the map must be updated or not
128 * (updates are currently unsupported by Streetside).
129 */
130 public void add(StreetsideImage image, boolean update) {
131 if (this.images.contains(image)) {
132 return;
133 }
134 this.images.add(image);
135 if (update) {
136 StreetsideLayer.invalidateInstance();
137 }
138 fireImagesAdded();
139 }
140
141 /**
142 * Adds a set of StreetsideImages to the object, and then repaints mapView.
143 *
144 * @param images The set of images to be added.
145 */
146 public void addAll(Collection<StreetsideImage> images) {
147 addAll(images, true);
148 }
149
150 /**
151 * Adds a set of {link StreetsideAbstractImage} objects to this object.
152 *
153 * @param newImages The set of images to be added.
154 * @param update Whether the map must be updated or not.
155 */
156 public void addAll(Collection<StreetsideImage> newImages, boolean update) {
157 newImages = new HashSet<>(newImages);
158 newImages.removeIf(this.images::contains);
159 images.addAll(newImages);
160 sortedImages.addAll(newImages);
161 sortedImages.sort(Comparator.naturalOrder());
162 if (update) {
163 StreetsideLayer.invalidateInstance();
164 }
165 fireImagesAdded();
166 }
167
168 /**
169 * Adds a new listener.
170 *
171 * @param lis Listener to be added.
172 */
173 public final void addListener(final StreetsideDataListener lis) {
174 listeners.add(lis);
175 }
176
177 public List<Bounds> getBounds() {
178 return bounds;
179 }
180
181 /**
182 * Removes a listener.
183 *
184 * @param lis Listener to be removed.
185 */
186 public void removeListener(StreetsideDataListener lis) {
187 listeners.remove(lis);
188 }
189
190 /**
191 * Returns the image under the mouse cursor.
192 *
193 * @return The image under the mouse cursor.
194 */
195 public StreetsideImage getHighlightedImage() {
196 return highlightedImage;
197 }
198
199 /**
200 * Highlights the image under the cursor.
201 *
202 * @param image The image under the cursor.
203 */
204 public void setHighlightedImage(StreetsideImage image) {
205 highlightedImage = image;
206 }
207
208 /**
209 * Returns a Set containing all images.
210 *
211 * @return A Set object containing all images.
212 */
213 public Collection<StreetsideImage> getImages() {
214 return images;
215 }
216
217 /**
218 * Sets a new {@link Collection} object as the used set of images.
219 * Any images that are already present, are removed.
220 *
221 * @param newImages the new image list (previously set images are completely replaced)
222 */
223 public void setImages(Collection<StreetsideImage> newImages) {
224 synchronized (this) {
225 this.images.clear();
226 this.sortedImages.clear();
227 this.addAll(newImages);
228 }
229 }
230
231 /**
232 * Returns the StreetsideImage object that is currently selected.
233 *
234 * @return The selected StreetsideImage object.
235 */
236 public StreetsideImage getSelectedImage() {
237 return selectedImage;
238 }
239
240 /**
241 * Selects a new image.If the user does ctrl + click, this isn't triggered.
242 *
243 * @param image The StreetsideImage which is going to be selected
244 */
245 public void setSelectedImage(StreetsideImage image) {
246 setSelectedImage(image, false);
247 }
248
249 private void fireImagesAdded() {
250 listeners.stream().filter(Objects::nonNull).forEach(StreetsideDataListener::imagesAdded);
251 }
252
253 /**
254 * If the selected StreetsideImage is part of a StreetsideSequence then the
255 * following visible StreetsideImage is selected. In case there is none, does
256 * nothing.
257 *
258 * @throws IllegalStateException if the selected image is null or the selected image doesn't
259 * belong to a sequence.
260 */
261 public void selectNext() {
262 selectNext(StreetsideProperties.MOVE_TO_IMG.get());
263 }
264
265 /**
266 * If the selected StreetsideImage is part of a StreetsideSequence then the
267 * following visible StreetsideImage is selected. In case there is none, does
268 * nothing.
269 *
270 * @param moveToPicture True if the view must me moved to the next picture.
271 * @throws IllegalStateException if the selected image is null or the selected image doesn't
272 * belong to a sequence.
273 */
274 public void selectNext(boolean moveToPicture) {
275 StreetsideImage tempImage = selectedImage;
276 if (selectedImage != null) {
277 while (next(tempImage) != null) {
278 tempImage = next(tempImage);
279 if (tempImage != null && tempImage.visible()) {
280 setSelectedImage(tempImage, moveToPicture);
281 break;
282 }
283 }
284 }
285 }
286
287 /**
288 * If the selected StreetsideImage is part of a StreetsideSequence then the
289 * previous visible StreetsideImage is selected. In case there is none, does
290 * nothing.
291 *
292 * @throws IllegalStateException if the selected image is null or the selected image doesn't
293 * belong to a sequence.
294 */
295 public void selectPrevious() {
296 selectPrevious(StreetsideProperties.MOVE_TO_IMG.get());
297 }
298
299 /**
300 * If the selected StreetsideImage is part of a StreetsideSequence then the
301 * previous visible StreetsideImage is selected. In case there is none, does
302 * nothing. * @throws IllegalStateException if the selected image is null or
303 * the selected image doesn't belong to a sequence.
304 *
305 * @param moveToPicture True if the view must me moved to the previous picture.
306 * @throws IllegalStateException if the selected image is null or the selected image doesn't
307 * belong to a sequence.
308 */
309 public void selectPrevious(boolean moveToPicture) {
310 if (selectedImage != null) {
311 StreetsideImage tempImage = selectedImage;
312 while (previous(tempImage) != null) {
313 tempImage = previous(tempImage);
314 if (tempImage != null && tempImage.visible()) {
315 setSelectedImage(tempImage, moveToPicture);
316 break;
317 }
318 }
319 }
320 }
321
322 /**
323 * Selects a new image.If the user does ctrl+click, this isn't triggered. You
324 * can choose whether to center the view on the new image or not.
325 *
326 * @param image The {@link StreetsideImage} which is going to be selected.
327 * @param zoom True if the view must be centered on the image; false otherwise.
328 */
329 public void setSelectedImage(StreetsideImage image, boolean zoom) {
330 StreetsideImage oldImage = selectedImage;
331 selectedImage = image;
332 final var mv = StreetsidePlugin.getMapView();
333 if (image != null && mv != null) {
334 // Downloading thumbnails of surrounding pictures.
335 downloadSurroundingImages(image);
336 }
337 if (mv != null && zoom && selectedImage != null) {
338 mv.zoomTo(selectedImage);
339 }
340 fireSelectedImageChanged(oldImage, selectedImage);
341 StreetsideLayer.invalidateInstance();
342 }
343
344 private void fireSelectedImageChanged(StreetsideImage oldImage, StreetsideImage newImage) {
345 listeners.stream().filter(Objects::nonNull).forEach(lis -> lis.selectedImageChanged(oldImage, newImage));
346 }
347
348 @Override
349 public Collection<DataSource> getDataSources() {
350 return Collections.emptyList();
351 }
352
353 /**
354 * Get the next image
355 * @param current The current image
356 * @return The next image, if available
357 */
358 public StreetsideImage next(StreetsideImage current) {
359 final int currentIndex = sortedImages.indexOf(current);
360 if (currentIndex + 1 >= sortedImages.size()) {
361 return null;
362 }
363 return sortedImages.get(currentIndex + 1);
364 }
365
366 /**
367 * Get the previous image
368 * @param current The current image
369 * @return The previous image, if available
370 */
371 public StreetsideImage previous(StreetsideImage current) {
372 final int currentIndex = sortedImages.indexOf(current);
373 if (currentIndex - 1 < 0) {
374 return null;
375 }
376 return sortedImages.get(currentIndex - 1);
377 }
378
379 /**
380 * Search for images
381 * @param target The image to look around
382 * @param v The {@link LatLon} degrees to search around
383 * @return The images found
384 */
385 public Collection<StreetsideImage> search(StreetsideImage target, double v) {
386 final var searchBox = new BBox(target);
387 searchBox.addLatLon(new LatLon(target.lat(), target.lon()), v);
388 return this.images.search(searchBox);
389 }
390
391 /**
392 * Search for images
393 * @param searchBox The box to search images in
394 * @return The images found
395 */
396 public Collection<StreetsideImage> search(BBox searchBox) {
397 return this.images.search(searchBox);
398 }
399}
Note: See TracBrowser for help on using the repository browser.