001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.streetside; 003 004import java.awt.AlphaComposite; 005import java.awt.BasicStroke; 006import java.awt.Color; 007import java.awt.Composite; 008import java.awt.Graphics2D; 009import java.awt.GraphicsEnvironment; 010import java.awt.Point; 011import java.awt.Rectangle; 012import java.awt.RenderingHints; 013import java.awt.TexturePaint; 014import java.awt.geom.Line2D; 015import java.awt.image.BufferedImage; 016import java.util.Comparator; 017import java.util.IntSummaryStatistics; 018import java.util.Optional; 019 020import javax.swing.Action; 021import javax.swing.Icon; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.data.Bounds; 025import org.openstreetmap.josm.data.osm.DataSet; 026import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 027import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; 028import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 029import org.openstreetmap.josm.gui.MainApplication; 030import org.openstreetmap.josm.gui.MapView; 031import org.openstreetmap.josm.gui.NavigatableComponent; 032import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 033import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 034import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; 035import org.openstreetmap.josm.gui.layer.Layer; 036import org.openstreetmap.josm.gui.layer.LayerManager; 037import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 038import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 039import org.openstreetmap.josm.plugins.streetside.cache.CacheUtils; 040import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog; 041import org.openstreetmap.josm.plugins.streetside.history.StreetsideRecord; 042import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader; 043import org.openstreetmap.josm.plugins.streetside.io.download.StreetsideDownloader.DOWNLOAD_MODE; 044import org.openstreetmap.josm.plugins.streetside.mode.AbstractMode; 045import org.openstreetmap.josm.plugins.streetside.mode.JoinMode; 046import org.openstreetmap.josm.plugins.streetside.mode.SelectMode; 047import org.openstreetmap.josm.plugins.streetside.utils.MapViewGeometryUtil; 048import org.openstreetmap.josm.plugins.streetside.utils.StreetsideColorScheme; 049import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties; 050import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils; 051import org.openstreetmap.josm.tools.I18n; 052import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 053 054/** 055 * This class represents the layer shown in JOSM. There can only exist one 056 * instance of this object. 057 * 058 * @author nokutu 059 */ 060public final class StreetsideLayer extends AbstractModifiableLayer implements 061ActiveLayerChangeListener, StreetsideDataListener { 062 063 /** The radius of the image marker */ 064 private static final int IMG_MARKER_RADIUS = 7; 065 /** The radius of the circular sector that indicates the camera angle */ 066 private static final int CA_INDICATOR_RADIUS = 15; 067 /** The angle of the circular sector that indicates the camera angle */ 068 private static final int CA_INDICATOR_ANGLE = 40; 069 /** Length of the edge of the small sign, which indicates that traffic signs have been found in an image. */ 070 private static final int TRAFFIC_SIGN_SIZE = 6; 071 /** A third of the height of the sign, for easier calculations */ 072 private static final double TRAFFIC_SIGN_HEIGHT_3RD = Math.sqrt( 073 Math.pow(StreetsideLayer.TRAFFIC_SIGN_SIZE, 2) - Math.pow(StreetsideLayer.TRAFFIC_SIGN_SIZE / 2d, 2) 074 ) / 3; 075 076 private static final DataSetListenerAdapter DATASET_LISTENER = 077 new DataSetListenerAdapter(e -> { 078 if (e instanceof DataChangedEvent && StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) { 079 // When more data is downloaded, a delayed update is thrown, in order to 080 // wait for the data bounds to be set. 081 MainApplication.worker.execute(StreetsideDownloader::downloadOSMArea); 082 } 083 }); 084 085 /** Unique instance of the class. */ 086 private static StreetsideLayer instance; 087 /** The nearest images to the selected image from different sequences sorted by distance from selection. */ 088 private StreetsideImage[] nearestImages = {}; 089 /** {@link StreetsideData} object that stores the database. */ 090 private final StreetsideData data; 091 092 /** Mode of the layer. */ 093 public AbstractMode mode; 094 095 private volatile TexturePaint hatched; 096 private final StreetsideLocationChangeset locationChangeset = new StreetsideLocationChangeset(); 097 098 private StreetsideLayer() { 099 super(I18n.tr("Microsoft Streetside Images")); 100 data = new StreetsideData(); 101 data.addListener(this); 102 } 103 104 /** 105 * Initializes the Layer. 106 */ 107 private void init() { 108 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 109 if (ds != null) { 110 ds.addDataSetListener(StreetsideLayer.DATASET_LISTENER); 111 } 112 MainApplication.getLayerManager().addLayer(this); 113 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 114 if (!GraphicsEnvironment.isHeadless()) { 115 setMode(new SelectMode()); 116 if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.OSM_AREA) { 117 StreetsideDownloader.downloadOSMArea(); 118 } 119 if (StreetsideDownloader.getMode() == DOWNLOAD_MODE.VISIBLE_AREA) { 120 mode.zoomChanged(); 121 } 122 } 123 // Does not execute when in headless mode 124 if (Main.main != null && !StreetsideMainDialog.getInstance().isShowing()) { 125 StreetsideMainDialog.getInstance().showDialog(); 126 } 127 if (StreetsidePlugin.getMapView() != null) { 128 StreetsideMainDialog.getInstance().getStreetsideImageDisplay().repaint(); 129 130 // There is no delete image action for Streetside (Mapillary functionality here removed). 131 132 //getLocationChangeset().addChangesetListener(StreetsideChangesetDialog.getInstance()); 133 } 134 createHatchTexture(); 135 invalidate(); 136 } 137 138 public static void invalidateInstance() { 139 if (StreetsideLayer.hasInstance()) { 140 StreetsideLayer.getInstance().invalidate(); 141 } 142 } 143 144 /** 145 * Changes the mode the the given one. 146 * 147 * @param mode The mode that is going to be activated. 148 */ 149 public void setMode(AbstractMode mode) { 150 final MapView mv = StreetsidePlugin.getMapView(); 151 if (this.mode != null && mv != null) { 152 mv.removeMouseListener(this.mode); 153 mv.removeMouseMotionListener(this.mode); 154 NavigatableComponent.removeZoomChangeListener(this.mode); 155 } 156 this.mode = mode; 157 if (mode != null && mv != null) { 158 mv.setNewCursor(mode.cursor, this); 159 mv.addMouseListener(mode); 160 mv.addMouseMotionListener(mode); 161 NavigatableComponent.addZoomChangeListener(mode); 162 StreetsideUtils.updateHelpText(); 163 } 164 } 165 166 private static synchronized void clearInstance() { 167 StreetsideLayer.instance = null; 168 } 169 170 /** 171 * Returns the unique instance of this class. 172 * 173 * @return The unique instance of this class. 174 */ 175 public static synchronized StreetsideLayer getInstance() { 176 if (StreetsideLayer.instance != null) { 177 if (!MainApplication.getLayerManager().containsLayer(StreetsideLayer.instance)) { 178 MainApplication.getLayerManager().addLayer(StreetsideLayer.instance); 179 } 180 return StreetsideLayer.instance; 181 } 182 final StreetsideLayer layer = new StreetsideLayer(); 183 StreetsideLayer.instance = layer; 184 layer.init(); 185 return layer; 186 } 187 188 /** 189 * @return if the unique instance of this layer is currently instantiated and added to the {@link LayerManager} 190 */ 191 public static boolean hasInstance() { 192 return StreetsideLayer.instance != null && MainApplication.getLayerManager().containsLayer(StreetsideLayer.instance); 193 } 194 195 /** 196 * Returns the {@link StreetsideData} object, which acts as the database of the 197 * Layer. 198 * 199 * @return The {@link StreetsideData} object that stores the database. 200 */ 201 public StreetsideData getData() { 202 return data; 203 } 204 205 /** 206 * Returns the n-nearest image, for n=1 the nearest one is returned, for n=2 the second nearest one and so on. 207 * The "n-nearest image" is picked from the list of one image from every sequence that is nearest to the currently 208 * selected image, excluding the sequence to which the selected image belongs. 209 * @param n the index for picking from the list of "nearest images", beginning from 1 210 * @return the n-nearest image to the currently selected image 211 */ 212 public synchronized StreetsideImage getNNearestImage(final int n) { 213 return n >= 1 && n <= nearestImages.length ? nearestImages[n - 1] : null; 214 } 215 216 /** 217 * Returns the {@link StreetsideLocationChangeset} object, which acts as the database of the 218 * Layer. 219 * 220 * @return The {@link MapillaryData} object that stores the database. 221 */ 222 public StreetsideLocationChangeset getLocationChangeset() { 223 return locationChangeset; 224 } 225 226 227 @Override 228 public synchronized void destroy() { 229 // TODO: Add destroy code for CubemapBuilder, et al.? @rrh 230 StreetsideLayer.clearInstance(); 231 setMode(null); 232 StreetsideRecord.getInstance().reset(); 233 AbstractMode.resetThread(); 234 StreetsideDownloader.stopAll(); 235 if (StreetsideMainDialog.hasInstance()) { 236 StreetsideMainDialog.getInstance().setImage(null); 237 StreetsideMainDialog.getInstance().updateImage(); 238 } 239 final MapView mv = StreetsidePlugin.getMapView(); 240 if (mv != null) { 241 mv.removeMouseListener(mode); 242 mv.removeMouseMotionListener(mode); 243 } 244 try { 245 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); 246 if (MainApplication.getLayerManager().getEditDataSet() != null) { 247 MainApplication.getLayerManager().getEditDataSet().removeDataSetListener(StreetsideLayer.DATASET_LISTENER); 248 } 249 } catch (final IllegalArgumentException e) { 250 // TODO: It would be ideal, to fix this properly. But for the moment let's catch this, for when a listener has already been removed. 251 } 252 super.destroy(); 253 } 254 255 @Override 256 public boolean isModified() { 257 // TODO: Add cubemap modification here? @rrh 258 return data.getImages().parallelStream().anyMatch(StreetsideAbstractImage::isModified); 259 } 260 261 @Override 262 public void setVisible(boolean visible) { 263 super.setVisible(visible); 264 getData().getImages().parallelStream().forEach(img -> img.setVisible(visible)); 265 if (MainApplication.getMap() != null) { 266 //StreetsideFilterDialog.getInstance().refresh(); 267 } 268 } 269 270 /** 271 * Initialize the hatch pattern used to paint the non-downloaded area. 272 */ 273 private void createHatchTexture() { 274 final BufferedImage bi = new BufferedImage(15, 15, BufferedImage.TYPE_INT_ARGB); 275 final Graphics2D big = bi.createGraphics(); 276 big.setColor(StreetsideProperties.BACKGROUND.get()); 277 final Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); 278 big.setComposite(comp); 279 big.fillRect(0, 0, 15, 15); 280 big.setColor(StreetsideProperties.OUTSIDE_DOWNLOADED_AREA.get()); 281 big.drawLine(0, 15, 15, 0); 282 final Rectangle r = new Rectangle(0, 0, 15, 15); 283 hatched = new TexturePaint(bi, r); 284 } 285 286 @Override 287 public synchronized void paint(final Graphics2D g, final MapView mv, final Bounds box) { 288 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 289 if (MainApplication.getLayerManager().getActiveLayer() == this) { 290 // paint remainder 291 g.setPaint(hatched); 292 g.fill(MapViewGeometryUtil.getNonDownloadedArea(mv, data.getBounds())); 293 } 294 295 // Draw the blue and red line 296 synchronized (StreetsideLayer.class) { 297 final StreetsideAbstractImage selectedImg = data.getSelectedImage(); 298 for (int i = 0; i < nearestImages.length && selectedImg != null; i++) { 299 if (i == 0) { 300 g.setColor(Color.RED); 301 } else { 302 g.setColor(Color.BLUE); 303 } 304 final Point selected = mv.getPoint(selectedImg.getMovingLatLon()); 305 final Point p = mv.getPoint(nearestImages[i].getMovingLatLon()); 306 g.draw(new Line2D.Double(p.getX(), p.getY(), selected.getX(), selected.getY())); 307 } 308 } 309 310 // Draw sequence line 311 g.setStroke(new BasicStroke(2)); 312 final StreetsideAbstractImage selectedImage = getData().getSelectedImage(); 313 for (final StreetsideSequence seq : getData().getSequences()) { 314 if (seq.getImages().contains(selectedImage)) { 315 g.setColor( 316 seq.getId() == null ? StreetsideColorScheme.SEQ_IMPORTED_SELECTED : StreetsideColorScheme.SEQ_SELECTED 317 ); 318 } else { 319 g.setColor( 320 seq.getId() == null ? StreetsideColorScheme.SEQ_IMPORTED_UNSELECTED : StreetsideColorScheme.SEQ_UNSELECTED 321 ); 322 } 323 g.draw(MapViewGeometryUtil.getSequencePath(mv, seq)); 324 } 325 /*for (final StreetsideAbstractImage imageAbs : data.getImages()) { 326 if (imageAbs.isVisible() && mv != null && mv.contains(mv.getPoint(imageAbs.getMovingLatLon()))) { 327 drawImageMarker(g, imageAbs); 328 } 329 }*/ 330 if (mode instanceof JoinMode) { 331 mode.paint(g, mv, box); 332 } 333 } 334 335 /** 336 * Draws an image marker onto the given Graphics context. 337 * @param g the Graphics context 338 * @param img the image to be drawn onto the Graphics context 339 */ 340 /*private void drawImageMarker(final Graphics2D g, final StreetsideAbstractImage img) { 341 if (img == null || img.getLatLon() == null) { 342 Logging.warn("An image is not painted, because it is null or has no LatLon!"); 343 return; 344 } 345 final StreetsideAbstractImage selectedImg = getData().getSelectedImage(); 346 final Point p = MainApplication.getMap().mapView.getPoint(img.getMovingLatLon()); 347 348 // Determine colors 349 final Color markerC; 350 final Color directionC; 351 if (selectedImg != null && getData().getMultiSelectedImages().contains(img)) { 352 markerC = img instanceof StreetsideImportedImage 353 ? StreetsideColorScheme.SEQ_IMPORTED_HIGHLIGHTED 354 : StreetsideColorScheme.SEQ_HIGHLIGHTED; 355 directionC = img instanceof StreetsideImportedImage 356 ? StreetsideColorScheme.SEQ_IMPORTED_HIGHLIGHTED_CA 357 : StreetsideColorScheme.SEQ_HIGHLIGHTED_CA; 358 } else if (selectedImg != null && selectedImg.getSequence() != null && selectedImg.getSequence().equals(img.getSequence())) { 359 markerC = img instanceof StreetsideImportedImage 360 ? StreetsideColorScheme.SEQ_IMPORTED_SELECTED 361 : StreetsideColorScheme.SEQ_SELECTED; 362 directionC = img instanceof StreetsideImportedImage 363 ? StreetsideColorScheme.SEQ_IMPORTED_SELECTED_CA 364 : StreetsideColorScheme.SEQ_SELECTED_CA; 365 } else { 366 markerC = img instanceof StreetsideImportedImage 367 ? StreetsideColorScheme.SEQ_IMPORTED_UNSELECTED 368 : StreetsideColorScheme.SEQ_UNSELECTED; 369 directionC = img instanceof StreetsideImportedImage 370 ? StreetsideColorScheme.SEQ_IMPORTED_UNSELECTED_CA 371 : StreetsideColorScheme.SEQ_UNSELECTED_CA; 372 } 373 374 // Paint direction indicator 375 g.setColor(directionC); 376 g.fillArc(p.x - StreetsideLayer.CA_INDICATOR_RADIUS, p.y - StreetsideLayer.CA_INDICATOR_RADIUS, 2 * StreetsideLayer.CA_INDICATOR_RADIUS, 2 * StreetsideLayer.CA_INDICATOR_RADIUS, (int) (90 - img.getMovingHe() - StreetsideLayer.CA_INDICATOR_ANGLE / 2d), StreetsideLayer.CA_INDICATOR_ANGLE); 377 // Paint image marker 378 g.setColor(markerC); 379 g.fillOval(p.x - StreetsideLayer.IMG_MARKER_RADIUS, p.y - StreetsideLayer.IMG_MARKER_RADIUS, 2 * StreetsideLayer.IMG_MARKER_RADIUS, 2 * StreetsideLayer.IMG_MARKER_RADIUS); 380 381 // Paint highlight for selected or highlighted images 382 if (img.equals(getData().getHighlightedImage()) || getData().getMultiSelectedImages().contains(img)) { 383 g.setColor(Color.WHITE); 384 g.setStroke(new BasicStroke(2)); 385 g.drawOval(p.x - StreetsideLayer.IMG_MARKER_RADIUS, p.y - StreetsideLayer.IMG_MARKER_RADIUS, 2 * StreetsideLayer.IMG_MARKER_RADIUS, 2 * StreetsideLayer.IMG_MARKER_RADIUS); 386 } 387 388 // TODO: reimplement detections for Bing Metadata? RRH 389 if (img instanceof StreetsideImage && !((StreetsideImage) img).getDetections().isEmpty()) { 390 final Path2D trafficSign = new Path2D.Double(); 391 trafficSign.moveTo(p.getX() - StreetsideLayer.TRAFFIC_SIGN_SIZE / 2d, p.getY() - StreetsideLayer.TRAFFIC_SIGN_HEIGHT_3RD); 392 trafficSign.lineTo(p.getX() + StreetsideLayer.TRAFFIC_SIGN_SIZE / 2d, p.getY() - StreetsideLayer.TRAFFIC_SIGN_HEIGHT_3RD); 393 trafficSign.lineTo(p.getX(), p.getY() + 2 * StreetsideLayer.TRAFFIC_SIGN_HEIGHT_3RD); 394 trafficSign.closePath(); 395 g.setColor(Color.WHITE); 396 g.fill(trafficSign); 397 g.setStroke(new BasicStroke(1)); 398 g.setColor(Color.RED); 399 g.draw(trafficSign); 400 } 401 }*/ 402 403 @Override 404 public Icon getIcon() { 405 return StreetsidePlugin.LOGO.setSize(ImageSizes.LAYER).get(); 406 } 407 408 @Override 409 public boolean isMergable(Layer other) { 410 return false; 411 } 412 413 @Override 414 public void mergeFrom(Layer from) { 415 throw new UnsupportedOperationException( 416 "This layer does not support merging yet"); 417 } 418 419 @Override 420 public Action[] getMenuEntries() { 421 return new Action[]{ 422 LayerListDialog.getInstance().createShowHideLayerAction(), 423 LayerListDialog.getInstance().createDeleteLayerAction(), 424 new LayerListPopup.InfoAction(this) 425 }; 426 } 427 428 @Override 429 public Object getInfoComponent() { 430 final IntSummaryStatistics seqSizeStats = getData().getSequences().stream().mapToInt(seq -> seq.getImages().size()).summaryStatistics(); 431 return new StringBuilder(I18n.tr("Streetside layer")) 432 .append("\n") 433 .append(I18n.tr( 434 "{0} sequences, each containing between {1} and {2} images (ΓΈ {3})", 435 getData().getSequences().size(), 436 seqSizeStats.getCount() <= 0 ? 0 : seqSizeStats.getMin(), 437 seqSizeStats.getCount() <= 0 ? 0 : seqSizeStats.getMax(), 438 seqSizeStats.getAverage() 439 )) 440 .append("\n\n") 441 .append(I18n.tr( 442 "{0} imported images", 443 getData().getImages().stream().filter(i -> i instanceof StreetsideImportedImage).count() 444 )) 445 .append("\n+ ") 446 .append(I18n.tr( 447 "{0} downloaded images", 448 getData().getImages().stream().filter(i -> i instanceof StreetsideImage).count() 449 )) 450 .append("\n= ") 451 .append(I18n.tr( 452 "{0} images in total", 453 getData().getImages().size() 454 )).toString(); 455 } 456 457 @Override 458 public String getToolTipText() { 459 return I18n.tr("{0} images in {1} sequences", getData().getImages().size(), getData().getSequences().size()); 460 } 461 462 @Override 463 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 464 if (MainApplication.getLayerManager().getActiveLayer() == this) { 465 StreetsideUtils.updateHelpText(); 466 } 467 468 if (MainApplication.getLayerManager().getEditLayer() != e.getPreviousDataLayer()) { 469 if (MainApplication.getLayerManager().getEditLayer() != null) { 470 MainApplication.getLayerManager().getEditLayer().getDataSet().addDataSetListener(StreetsideLayer.DATASET_LISTENER); 471 } 472 if (e.getPreviousDataLayer() != null) { 473 e.getPreviousDataLayer().getDataSet().removeDataSetListener(StreetsideLayer.DATASET_LISTENER); 474 } 475 } 476 } 477 478 @Override 479 public void visitBoundingBox(BoundingXYVisitor v) { 480 } 481 482 /* (non-Javadoc) 483 * @see org.openstreetmap.josm.plugins.streetside.StreetsideDataListener#imagesAdded() 484 */ 485 @Override 486 public void imagesAdded() { 487 // TODO: Never used - could this be of use? @rrh 488 updateNearestImages(); 489 } 490 491 /* (non-Javadoc) 492 * @see org.openstreetmap.josm.plugins.mapillary.StreetsideDataListener#selectedImageChanged(org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage, org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage) 493 */ 494 @Override 495 public void selectedImageChanged(StreetsideAbstractImage oldImage, StreetsideAbstractImage newImage) { 496 updateNearestImages(); 497 } 498 499 /** 500 * Returns the closest images belonging to a different sequence and 501 * different from the specified target image. 502 * 503 * @param target the image for which you want to find the nearest other images 504 * @param limit the maximum length of the returned array 505 * @return An array containing the closest images belonging to different sequences sorted by distance from target. 506 */ 507 private StreetsideImage[] getNearestImagesFromDifferentSequences(StreetsideAbstractImage target, int limit) { 508 return data.getSequences().parallelStream() 509 .filter(seq -> seq.getId() != null && !seq.getId().equals(target.getSequence().getId())) 510 .map(seq -> { // Maps sequence to image from sequence that is nearest to target 511 final Optional<StreetsideAbstractImage> resImg = seq.getImages().parallelStream() 512 .filter(img -> img instanceof StreetsideImage && img.isVisible()) 513 .min(new NearestImgToTargetComparator(target)); 514 return resImg.orElse(null); 515 }) 516 .filter(img -> // Filters out images too far away from target 517 img != null && 518 img.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) 519 < StreetsideProperties.SEQUENCE_MAX_JUMP_DISTANCE.get() 520 ) 521 .sorted(new NearestImgToTargetComparator(target)) 522 .limit(limit) 523 .toArray(StreetsideImage[]::new); 524 } 525 526 /** 527 * Returns the closest images belonging to a different sequence and 528 * different from the specified target image. 529 * 530 * @param target the image for which you want to find the nearest other images 531 * @param limit the maximum length of the returned array 532 * @return An array containing the closest images belonging to different sequences sorted by distance from target. 533 */ 534 /*private StreetsideCubemap[] getNearestCubemapsFromDifferentSequences(StreetsideAbstractImage target, int limit) { 535 return data.getSequences().parallelStream() 536 .filter(seq -> seq.getId() != null && !seq.getId().equals(target.getSequence().getId())) 537 .map(seq -> { // Maps sequence to image from sequence that is nearest to target 538 final Optional<StreetsideAbstractImage> resCb = seq.getImages().parallelStream() 539 .filter(cb -> cb instanceof StreetsideCubemap && cb.isVisible()) 540 .min(new NearestCbToTargetComparator(target)); 541 return resCb.orElse(null); 542 }) 543 .filter(cb -> // Filters out images too far away from target 544 cb != null && 545 cb.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) 546 < StreetsideProperties.SEQUENCE_MAX_JUMP_DISTANCE.get() 547 ) 548 .sorted(new NearestCbToTargetComparator(target)) 549 .limit(limit) 550 .toArray(StreetsideCubemap[]::new); 551 }*/ 552 553 private synchronized void updateNearestImages() { 554 final StreetsideAbstractImage selected = data.getSelectedImage(); 555 if (selected != null) { 556 // TODO: could this be used to pre-cache cubemaps? @rrh 557 nearestImages = getNearestImagesFromDifferentSequences(selected, 2); 558 } else { 559 nearestImages = new StreetsideImage[0]; 560 } 561 if (MainApplication.isDisplayingMapView()) { 562 StreetsideMainDialog.getInstance().redButton.setEnabled(nearestImages.length >= 1); 563 StreetsideMainDialog.getInstance().blueButton.setEnabled(nearestImages.length >= 2); 564 } 565 if (nearestImages.length >= 1) { 566 CacheUtils.downloadPicture(nearestImages[0]); 567 // TODO: download/pre-caches cubemaps here? 568 //CacheUtils.downloadCubemap(nearestImages[0]); 569 if (nearestImages.length >= 2) { 570 CacheUtils.downloadPicture(nearestImages[1]); 571 // TODO: download/pre-caches cubemaps here? 572 //CacheUtils.downloadCubemap(nearestImages[1]); 573 } 574 } 575 } 576 577 private static class NearestImgToTargetComparator implements Comparator<StreetsideAbstractImage> { 578 private final StreetsideAbstractImage target; 579 580 public NearestImgToTargetComparator(StreetsideAbstractImage target) { 581 this.target = target; 582 } 583 /* (non-Javadoc) 584 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 585 */ 586 @Override 587 public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) { 588 return (int) Math.signum( 589 img1.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) - 590 img2.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) 591 ); 592 } 593 } 594 595 private static class NearestCbToTargetComparator implements Comparator<StreetsideAbstractImage> { 596 private final StreetsideAbstractImage target; 597 598 public NearestCbToTargetComparator(StreetsideAbstractImage target) { 599 this.target = target; 600 } 601 /* (non-Javadoc) 602 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 603 */ 604 @Override 605 public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) { 606 return (int) Math.signum( 607 img1.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) - 608 img2.getMovingLatLon().greatCircleDistance(target.getMovingLatLon()) 609 ); 610 } 611 } 612}