001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.vector; 003 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.HashSet; 009import java.util.LinkedHashSet; 010import java.util.List; 011import java.util.Map; 012import java.util.Objects; 013import java.util.Optional; 014import java.util.Set; 015import java.util.concurrent.ConcurrentHashMap; 016import java.util.concurrent.locks.Lock; 017import java.util.concurrent.locks.ReentrantReadWriteLock; 018import java.util.function.Function; 019import java.util.function.Predicate; 020import java.util.function.Supplier; 021import java.util.stream.Collectors; 022import java.util.stream.Stream; 023 024import org.openstreetmap.josm.data.DataSource; 025import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile; 026import org.openstreetmap.josm.data.osm.BBox; 027import org.openstreetmap.josm.data.osm.DataSelectionListener; 028import org.openstreetmap.josm.data.osm.DownloadPolicy; 029import org.openstreetmap.josm.data.osm.HighlightUpdateListener; 030import org.openstreetmap.josm.data.osm.IPrimitive; 031import org.openstreetmap.josm.data.osm.OsmData; 032import org.openstreetmap.josm.data.osm.PrimitiveId; 033import org.openstreetmap.josm.data.osm.Storage; 034import org.openstreetmap.josm.data.osm.UploadPolicy; 035import org.openstreetmap.josm.data.osm.WaySegment; 036import org.openstreetmap.josm.data.osm.event.IDataSelectionEventSource; 037import org.openstreetmap.josm.data.osm.event.IDataSelectionListener; 038import org.openstreetmap.josm.data.osm.event.IDataSelectionListener.SelectionAddEvent; 039import org.openstreetmap.josm.data.osm.event.IDataSelectionListener.SelectionChangeEvent; 040import org.openstreetmap.josm.data.osm.event.IDataSelectionListener.SelectionRemoveEvent; 041import org.openstreetmap.josm.data.osm.event.IDataSelectionListener.SelectionReplaceEvent; 042import org.openstreetmap.josm.data.osm.event.IDataSelectionListener.SelectionToggleEvent; 043import org.openstreetmap.josm.gui.mappaint.ElemStyles; 044import org.openstreetmap.josm.tools.ListenerList; 045import org.openstreetmap.josm.tools.Logging; 046import org.openstreetmap.josm.tools.SubclassFilteredCollection; 047 048/** 049 * A data class for Vector Data 050 * 051 * @author Taylor Smock 052 * @since 17862 053 */ 054public class VectorDataSet implements OsmData<VectorPrimitive, VectorNode, VectorWay, VectorRelation>, 055 IDataSelectionEventSource<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> { 056 // Note: In Java 8, computeIfAbsent is blocking for both pre-existing and new values. In Java 9, it is only blocking 057 // for new values (perf increase). See JDK-8161372 for more info. 058 private final Map<Integer, Storage<MVTTile>> dataStoreMap = new ConcurrentHashMap<>(); 059 // This is for "custom" data 060 private final VectorDataStore customDataStore = new VectorDataStore(); 061 // Both of these listener lists are useless, since they expect OsmPrimitives at this time 062 private final ListenerList<HighlightUpdateListener> highlightUpdateListenerListenerList = ListenerList.create(); 063 private final ListenerList<DataSelectionListener> dataSelectionListenerListenerList = ListenerList.create(); 064 private boolean lock = true; 065 private String name; 066 private short mappaintCacheIdx = 1; 067 068 private final Object selectionLock = new Object(); 069 /** 070 * The current selected primitives. This is always a unmodifiable set. 071 * 072 * The set should be ordered in the order in which the primitives have been added to the selection. 073 */ 074 private Set<PrimitiveId> currentSelectedPrimitives = Collections.emptySet(); 075 076 private final ListenerList<IDataSelectionListener<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet>> listeners = 077 ListenerList.create(); 078 079 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 080 081 /** 082 * The distance to consider nodes duplicates -- mostly a memory saving measure. 083 * 0.000_000_1 ~1.2 cm (+- 5.57 mm) 084 * Descriptions from <a href="https://xkcd.com/2170/">https://xkcd.com/2170/</a> 085 * Notes on <a href="https://wiki.openstreetmap.org/wiki/Node">https://wiki.openstreetmap.org/wiki/Node</a> indicate 086 * that IEEE 32-bit floats should not be used at high longitude (0.000_01 precision) 087 */ 088 protected static final float DUPE_NODE_DISTANCE = 0.000_000_1f; 089 090 /** 091 * The current zoom we are getting/adding to 092 */ 093 private int zoom; 094 /** 095 * Default to normal download policy 096 */ 097 private DownloadPolicy downloadPolicy = DownloadPolicy.NORMAL; 098 /** 099 * Default to a blocked upload policy 100 */ 101 private UploadPolicy uploadPolicy = UploadPolicy.BLOCKED; 102 /** 103 * The paint style for this layer 104 */ 105 private ElemStyles styles; 106 private final Collection<PrimitiveId> highlighted = new HashSet<>(); 107 108 @Override 109 public Collection<DataSource> getDataSources() { 110 // TODO 111 return Collections.emptyList(); 112 } 113 114 @Override 115 public void lock() { 116 this.lock = true; 117 } 118 119 @Override 120 public void unlock() { 121 this.lock = false; 122 } 123 124 @Override 125 public boolean isLocked() { 126 return this.lock; 127 } 128 129 @Override 130 public String getVersion() { 131 return "8"; // TODO get this dynamically. Not critical, as this is currently the _only_ version. 132 } 133 134 @Override 135 public String getName() { 136 return this.name; 137 } 138 139 @Override 140 public void setName(String name) { 141 this.name = name; 142 } 143 144 /** 145 * Add a primitive to the custom data store 146 * @param primitive the primitive to add 147 */ 148 @Override 149 public void addPrimitive(VectorPrimitive primitive) { 150 tryWrite(this.readWriteLock, () -> { 151 this.customDataStore.addPrimitive(primitive); 152 primitive.setDataSet(this); 153 }); 154 } 155 156 /** 157 * Remove a primitive from the custom data store 158 * @param primitive The primitive to add to the custom data store 159 */ 160 public void removePrimitive(VectorPrimitive primitive) { 161 this.customDataStore.removePrimitive(primitive); 162 primitive.setDataSet(null); 163 } 164 165 @Override 166 public void clear() { 167 synchronized (this.dataStoreMap) { 168 this.dataStoreMap.clear(); 169 } 170 } 171 172 @Override 173 public List<VectorNode> searchNodes(BBox bbox) { 174 return tryRead(this.readWriteLock, () -> { 175 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 176 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 177 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 178 .flatMap(store -> store.searchNodes(bbox).stream()).collect(Collectors.toList()); 179 }).orElseGet(Collections::emptyList); 180 } 181 182 @Override 183 public boolean containsNode(VectorNode vectorNode) { 184 return tryRead(this.readWriteLock, () -> { 185 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 186 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 187 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 188 .anyMatch(store -> store.containsNode(vectorNode)); 189 }).orElse(Boolean.FALSE); 190 } 191 192 @Override 193 public List<VectorWay> searchWays(BBox bbox) { 194 return tryRead(this.readWriteLock, () -> { 195 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 196 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 197 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 198 .flatMap(store -> store.searchWays(bbox).stream()).collect(Collectors.toList()); 199 }).orElseGet(Collections::emptyList); 200 } 201 202 @Override 203 public boolean containsWay(VectorWay vectorWay) { 204 return tryRead(this.readWriteLock, () -> { 205 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 206 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 207 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 208 .anyMatch(store -> store.containsWay(vectorWay)); 209 }).orElse(Boolean.FALSE); 210 } 211 212 @Override 213 public List<VectorRelation> searchRelations(BBox bbox) { 214 return tryRead(this.readWriteLock, () -> { 215 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 216 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 217 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 218 .flatMap(store -> store.searchRelations(bbox).stream()).collect(Collectors.toList()); 219 }).orElseGet(Collections::emptyList); 220 } 221 222 @Override 223 public boolean containsRelation(VectorRelation vectorRelation) { 224 return tryRead(this.readWriteLock, () -> { 225 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 226 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 227 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getStore) 228 .anyMatch(store -> store.containsRelation(vectorRelation)); 229 }).orElse(Boolean.FALSE); 230 } 231 232 /** 233 * Get a primitive for an id 234 * @param primitiveId type and uniqueId of the primitive. Might be < 0 for newly created primitives 235 * @return The primitive for the id. Please note that since this is vector data, there may be more primitives with this id. 236 * Please use {@link #getPrimitivesById(PrimitiveId...)} to get all primitives for that {@link PrimitiveId}. 237 */ 238 @Override 239 public VectorPrimitive getPrimitiveById(PrimitiveId primitiveId) { 240 return this.getPrimitivesById(primitiveId).findFirst().orElse(null); 241 } 242 243 /** 244 * Get all primitives for ids 245 * @param primitiveIds The ids to search for 246 * @return The primitives for the ids (note: as this is vector data, a {@link PrimitiveId} may have multiple associated primitives) 247 */ 248 public Stream<VectorPrimitive> getPrimitivesById(PrimitiveId... primitiveIds) { 249 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 250 return Stream.concat(dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(), 251 Stream.of(this.customDataStore)).map(VectorDataStore::getPrimitivesMap) 252 .flatMap(m -> Stream.of(primitiveIds).map(m::get)).filter(Objects::nonNull); 253 } 254 255 @Override 256 public <T extends VectorPrimitive> Collection<T> getPrimitives(Predicate<? super VectorPrimitive> predicate) { 257 Collection<VectorPrimitive> primitives = tryRead(this.readWriteLock, () -> { 258 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 259 final Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 260 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)) 261 .map(VectorDataStore::getAllPrimitives).flatMap(Collection::stream).distinct().collect(Collectors.toList()); 262 263 }).orElseGet(Collections::emptyList); 264 return new SubclassFilteredCollection<>(primitives, predicate); 265 } 266 267 @Override 268 public Collection<VectorNode> getNodes() { 269 return this.getPrimitives(VectorNode.class::isInstance); 270 } 271 272 @Override 273 public Collection<VectorWay> getWays() { 274 return this.getPrimitives(VectorWay.class::isInstance); 275 } 276 277 @Override 278 public Collection<VectorRelation> getRelations() { 279 return this.getPrimitives(VectorRelation.class::isInstance); 280 } 281 282 @Override 283 public DownloadPolicy getDownloadPolicy() { 284 return this.downloadPolicy; 285 } 286 287 @Override 288 public void setDownloadPolicy(DownloadPolicy downloadPolicy) { 289 this.downloadPolicy = downloadPolicy; 290 } 291 292 @Override 293 public UploadPolicy getUploadPolicy() { 294 return this.uploadPolicy; 295 } 296 297 @Override 298 public void setUploadPolicy(UploadPolicy uploadPolicy) { 299 this.uploadPolicy = uploadPolicy; 300 } 301 302 /** 303 * Get the current Read/Write lock. 304 * This changes based off of zoom level. Please do not use this in a finally block 305 * @return The current read/write lock 306 */ 307 @Override 308 public Lock getReadLock() { 309 return this.readWriteLock.readLock(); 310 } 311 312 @Override 313 public Collection<WaySegment> getHighlightedVirtualNodes() { 314 // TODO? This requires a change to WaySegment so that it isn't Way/Node specific 315 return Collections.emptyList(); 316 } 317 318 @Override 319 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) { 320 // TODO? This requires a change to WaySegment so that it isn't Way/Node specific 321 } 322 323 @Override 324 public Collection<WaySegment> getHighlightedWaySegments() { 325 // TODO? This requires a change to WaySegment so that it isn't Way/Node specific 326 return Collections.emptyList(); 327 } 328 329 @Override 330 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) { 331 // TODO? This requires a change to WaySegment so that it isn't Way/Node specific 332 } 333 334 /** 335 * Mark some primitives as highlighted. 336 * API is *highly likely* to change, as the inherited methods are modified to accept primitives other than OSM primitives. 337 * @param primitives The primitives to highlight 338 */ 339 public void setHighlighted(Collection<PrimitiveId> primitives) { 340 this.highlighted.clear(); 341 this.highlighted.addAll(primitives); 342 // The highlight event updates are very OSM specific, and require a DataSet. 343 this.highlightUpdateListenerListenerList.fireEvent(event -> event.highlightUpdated(null)); 344 } 345 346 /** 347 * Get the highlighted objects 348 * @return The highlighted objects 349 */ 350 public Collection<PrimitiveId> getHighlighted() { 351 return Collections.unmodifiableCollection(this.highlighted); 352 } 353 354 @Override 355 public void addHighlightUpdateListener(HighlightUpdateListener listener) { 356 this.highlightUpdateListenerListenerList.addListener(listener); 357 } 358 359 @Override 360 public void removeHighlightUpdateListener(HighlightUpdateListener listener) { 361 this.highlightUpdateListenerListenerList.removeListener(listener); 362 } 363 364 @Override 365 public Collection<VectorPrimitive> getAllSelected() { 366 return tryRead(this.readWriteLock, () -> { 367 final Storage<MVTTile> dataStore = this.getBestZoomDataStore().orElse(null); 368 Stream<VectorDataStore> dataStoreStream = dataStore != null ? dataStore.stream().map(MVTTile::getData) : Stream.empty(); 369 return Stream.concat(dataStoreStream, Stream.of(this.customDataStore)).map(VectorDataStore::getPrimitivesMap) 370 .flatMap(dataMap -> { 371 // Synchronize on dataMap to avoid concurrent modification errors 372 synchronized (dataMap) { 373 return this.currentSelectedPrimitives.stream().map(dataMap::get).filter(Objects::nonNull); 374 } 375 }).collect(Collectors.toList()); 376 }).orElseGet(Collections::emptyList); 377 } 378 379 /** 380 * Get the best zoom datastore 381 * @return A datastore with data, or {@code null} if no good datastore exists. 382 */ 383 private Optional<Storage<MVTTile>> getBestZoomDataStore() { 384 final int currentZoom = this.zoom; 385 if (this.dataStoreMap.containsKey(currentZoom)) { 386 return Optional.of(this.dataStoreMap.get(currentZoom)); 387 } 388 // Check up to two zooms higher (may cause perf hit) 389 for (int tZoom = currentZoom + 1; tZoom < currentZoom + 3; tZoom++) { 390 if (this.dataStoreMap.containsKey(tZoom)) { 391 return Optional.of(this.dataStoreMap.get(tZoom)); 392 } 393 } 394 // Return *any* lower zoom data (shouldn't cause a perf hit...) 395 for (int tZoom = currentZoom - 1; tZoom >= 0; tZoom--) { 396 if (this.dataStoreMap.containsKey(tZoom)) { 397 return Optional.of(this.dataStoreMap.get(tZoom)); 398 } 399 } 400 // Check higher level zooms. May cause perf issues if selected datastore has a lot of data. 401 for (int tZoom = currentZoom + 3; tZoom < 34; tZoom++) { 402 if (this.dataStoreMap.containsKey(tZoom)) { 403 return Optional.of(this.dataStoreMap.get(tZoom)); 404 } 405 } 406 return Optional.empty(); 407 } 408 409 @Override 410 public boolean selectionEmpty() { 411 return this.currentSelectedPrimitives.isEmpty(); 412 } 413 414 @Override 415 public boolean isSelected(VectorPrimitive osm) { 416 return this.currentSelectedPrimitives.contains(osm.getPrimitiveId()); 417 } 418 419 @Override 420 public void toggleSelected(Collection<? extends PrimitiveId> osm) { 421 this.toggleSelectedImpl(osm.stream()); 422 } 423 424 @Override 425 public void toggleSelected(PrimitiveId... osm) { 426 this.toggleSelectedImpl(Stream.of(osm)); 427 } 428 429 private void toggleSelectedImpl(Stream<? extends PrimitiveId> osm) { 430 this.doSelectionChange(old -> new SelectionToggleEvent<>(this, old, 431 osm.flatMap(this::getPrimitivesById).filter(Objects::nonNull))); 432 } 433 434 @Override 435 public void setSelected(Collection<? extends PrimitiveId> selection) { 436 this.setSelectedImpl(selection.stream()); 437 } 438 439 @Override 440 public void setSelected(PrimitiveId... osm) { 441 this.setSelectedImpl(Stream.of(osm)); 442 } 443 444 private void setSelectedImpl(Stream<? extends PrimitiveId> osm) { 445 this.doSelectionChange(old -> new SelectionReplaceEvent<>(this, old, 446 osm.filter(Objects::nonNull).flatMap(this::getPrimitivesById).filter(Objects::nonNull))); 447 } 448 449 @Override 450 public void addSelected(Collection<? extends PrimitiveId> selection) { 451 this.addSelectedImpl(selection.stream()); 452 } 453 454 @Override 455 public void addSelected(PrimitiveId... osm) { 456 this.addSelectedImpl(Stream.of(osm)); 457 } 458 459 private void addSelectedImpl(Stream<? extends PrimitiveId> osm) { 460 this.doSelectionChange(old -> new SelectionAddEvent<>(this, old, 461 osm.flatMap(this::getPrimitivesById).filter(Objects::nonNull))); 462 } 463 464 @Override 465 public void clearSelection(PrimitiveId... osm) { 466 this.clearSelectionImpl(Stream.of(osm)); 467 } 468 469 @Override 470 public void clearSelection(Collection<? extends PrimitiveId> list) { 471 this.clearSelectionImpl(list.stream()); 472 } 473 474 @Override 475 public void clearSelection() { 476 this.clearSelectionImpl(new ArrayList<>(this.currentSelectedPrimitives).stream()); 477 } 478 479 private void clearSelectionImpl(Stream<? extends PrimitiveId> osm) { 480 this.doSelectionChange(old -> new SelectionRemoveEvent<>(this, old, 481 osm.flatMap(this::getPrimitivesById).filter(Objects::nonNull))); 482 } 483 484 /** 485 * Do a selection change. 486 * <p> 487 * This is the only method that changes the current selection state. 488 * @param command A generator that generates the {@link SelectionChangeEvent} 489 * for the given base set of currently selected primitives. 490 * @return true iff the command did change the selection. 491 */ 492 private boolean doSelectionChange(final Function<Set<VectorPrimitive>, 493 SelectionChangeEvent<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet>> command) { 494 synchronized (this.selectionLock) { 495 SelectionChangeEvent<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> event = 496 command.apply(currentSelectedPrimitives.stream().map(this::getPrimitiveById).collect(Collectors.toSet())); 497 if (event.isNop()) { 498 return false; 499 } 500 this.currentSelectedPrimitives = event.getSelection().stream().map(IPrimitive::getPrimitiveId) 501 .collect(Collectors.toCollection(LinkedHashSet::new)); 502 this.listeners.fireEvent(l -> l.selectionChanged(event)); 503 return true; 504 } 505 } 506 507 @Override 508 public void addSelectionListener(DataSelectionListener listener) { 509 this.dataSelectionListenerListenerList.addListener(listener); 510 } 511 512 @Override 513 public void removeSelectionListener(DataSelectionListener listener) { 514 this.dataSelectionListenerListenerList.removeListener(listener); 515 } 516 517 public short getMappaintCacheIndex() { 518 return this.mappaintCacheIdx; 519 } 520 521 @Override 522 public void clearMappaintCache() { 523 this.mappaintCacheIdx++; 524 } 525 526 public void setZoom(int zoom) { 527 if (zoom == this.zoom) { 528 return; // Do nothing -- zoom isn't actually changing 529 } 530 this.zoom = zoom; 531 this.clearMappaintCache(); 532 final int[] nearestZoom = {-1, -1, -1, -1}; 533 nearestZoom[0] = zoom; 534 // Create a new list to avoid concurrent modification issues 535 synchronized (this.dataStoreMap) { 536 final int[] keys = new ArrayList<>(this.dataStoreMap.keySet()).stream().filter(Objects::nonNull) 537 .mapToInt(Integer::intValue).sorted().toArray(); 538 final int index; 539 if (this.dataStoreMap.containsKey(zoom)) { 540 index = Arrays.binarySearch(keys, zoom); 541 } else { 542 // (-(insertion point) - 1) = return -> insertion point = -(return + 1) 543 index = -(Arrays.binarySearch(keys, zoom) + 1); 544 } 545 if (index > 0) { 546 nearestZoom[1] = keys[index - 1]; 547 } 548 if (index < keys.length - 2) { 549 nearestZoom[2] = keys[index + 1]; 550 } 551 552 // TODO cleanup zooms for memory 553 } 554 } 555 556 public int getZoom() { 557 return this.zoom; 558 } 559 560 /** 561 * Add tile data to this dataset 562 * @param tile The tile to add 563 */ 564 public void addTileData(MVTTile tile) { 565 tryWrite(this.readWriteLock, () -> { 566 final int currentZoom = tile.getZoom(); 567 // computeIfAbsent should be thread safe (ConcurrentHashMap indicates it is, anyway) 568 final Storage<MVTTile> dataStore = this.dataStoreMap.computeIfAbsent(currentZoom, tZoom -> new Storage<>()); 569 tile.getData().getAllPrimitives().forEach(primitive -> primitive.setDataSet(this)); 570 dataStore.add(tile); 571 }); 572 } 573 574 /** 575 * Try to read something (here to avoid boilerplate) 576 * 577 * @param lock The lock 578 * @param supplier The reading function 579 * @param <T> The return type 580 * @return The optional return 581 */ 582 private static <T> Optional<T> tryRead(ReentrantReadWriteLock lock, Supplier<T> supplier) { 583 try { 584 lock.readLock().lockInterruptibly(); 585 return Optional.ofNullable(supplier.get()); 586 } catch (InterruptedException e) { 587 Logging.error(e); 588 Thread.currentThread().interrupt(); 589 } finally { 590 lock.readLock().unlock(); 591 } 592 return Optional.empty(); 593 } 594 595 /** 596 * Try to write something (here to avoid boilerplate) 597 * @param lock lock 598 * @param runnable The writing function 599 */ 600 private static void tryWrite(ReentrantReadWriteLock lock, Runnable runnable) { 601 try { 602 lock.writeLock().lockInterruptibly(); 603 runnable.run(); 604 } catch (InterruptedException e) { 605 Logging.error(e); 606 Thread.currentThread().interrupt(); 607 } finally { 608 if (lock.isWriteLockedByCurrentThread()) { 609 lock.writeLock().unlock(); 610 } 611 } 612 } 613 614 /** 615 * Get the styles for this layer 616 * 617 * @return The styles 618 */ 619 public ElemStyles getStyles() { 620 return this.styles; 621 } 622 623 /** 624 * Set the styles for this layer 625 * @param styles The styles to set for this layer 626 */ 627 public void setStyles(Collection<ElemStyles> styles) { 628 if (styles.size() == 1) { 629 this.styles = styles.iterator().next(); 630 } else if (!styles.isEmpty()) { 631 this.styles = new ElemStyles(styles.stream().flatMap(style -> style.getStyleSources().stream()).collect(Collectors.toList())); 632 } else { 633 this.styles = null; 634 } 635 } 636 637 /** 638 * Mark some layers as invisible 639 * @param invisibleLayers The layer to not show 640 */ 641 public void setInvisibleLayers(Collection<String> invisibleLayers) { 642 String[] currentInvisibleLayers = invisibleLayers.stream().filter(Objects::nonNull).toArray(String[]::new); 643 List<String> temporaryList = Arrays.asList(currentInvisibleLayers); 644 this.dataStoreMap.values().stream().flatMap(Collection::stream).map(MVTTile::getData) 645 .forEach(dataStore -> dataStore.getAllPrimitives().parallelStream() 646 .forEach(primitive -> primitive.setVisible(!temporaryList.contains(primitive.getLayer())))); 647 } 648 649 @Override 650 public boolean addSelectionListener(IDataSelectionListener<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> listener) { 651 if (!this.listeners.containsListener(listener)) { 652 this.listeners.addListener(listener); 653 } 654 return this.listeners.containsListener(listener); 655 } 656 657 @Override 658 public boolean removeSelectionListener( 659 IDataSelectionListener<VectorPrimitive, VectorNode, VectorWay, VectorRelation, VectorDataSet> listener) { 660 if (this.listeners.containsListener(listener)) { 661 this.listeners.removeListener(listener); 662 } 663 return this.listeners.containsListener(listener); 664 } 665}