001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Image; 007import java.io.StringReader; 008import java.util.ArrayList; 009import java.util.Arrays; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.List; 013import java.util.Locale; 014import java.util.Map; 015import java.util.Objects; 016import java.util.Optional; 017import java.util.Set; 018import java.util.TreeSet; 019import java.util.concurrent.ConcurrentHashMap; 020import java.util.concurrent.TimeUnit; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023import java.util.stream.Collectors; 024 025import javax.json.Json; 026import javax.json.JsonObject; 027import javax.json.JsonReader; 028import javax.json.stream.JsonCollectors; 029import javax.swing.ImageIcon; 030 031import org.openstreetmap.gui.jmapviewer.interfaces.Attributed; 032import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 033import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource; 034import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik; 035import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; 036import org.openstreetmap.josm.data.Bounds; 037import org.openstreetmap.josm.data.StructUtils; 038import org.openstreetmap.josm.data.StructUtils.StructEntry; 039import org.openstreetmap.josm.io.Capabilities; 040import org.openstreetmap.josm.io.OsmApi; 041import org.openstreetmap.josm.spi.preferences.Config; 042import org.openstreetmap.josm.spi.preferences.IPreferences; 043import org.openstreetmap.josm.tools.CheckParameterUtil; 044import org.openstreetmap.josm.tools.ImageProvider; 045import org.openstreetmap.josm.tools.LanguageInfo; 046import org.openstreetmap.josm.tools.Logging; 047import org.openstreetmap.josm.tools.MultiMap; 048import org.openstreetmap.josm.tools.Utils; 049 050/** 051 * Class that stores info about an image background layer. 052 * 053 * @author Frederik Ramm 054 */ 055public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed { 056 057 /** 058 * Type of imagery entry. 059 */ 060 public enum ImageryType { 061 /** A WMS (Web Map Service) entry. **/ 062 WMS("wms"), 063 /** A TMS (Tile Map Service) entry. **/ 064 TMS("tms"), 065 /** TMS entry for Microsoft Bing. */ 066 BING("bing"), 067 /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/ 068 SCANEX("scanex"), 069 /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/ 070 WMS_ENDPOINT("wms_endpoint"), 071 /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/ 072 WMTS("wmts"); 073 074 075 private final String typeString; 076 077 ImageryType(String typeString) { 078 this.typeString = typeString; 079 } 080 081 /** 082 * Returns the unique string identifying this type. 083 * @return the unique string identifying this type 084 * @since 6690 085 */ 086 public final String getTypeString() { 087 return typeString; 088 } 089 090 /** 091 * Returns the imagery type from the given type string. 092 * @param s The type string 093 * @return the imagery type matching the given type string 094 */ 095 public static ImageryType fromString(String s) { 096 for (ImageryType type : ImageryType.values()) { 097 if (type.getTypeString().equals(s)) { 098 return type; 099 } 100 } 101 return null; 102 } 103 } 104 105 /** 106 * Category of imagery entry. 107 * @since 13792 108 */ 109 public enum ImageryCategory { 110 /** A aerial or satellite photo. **/ 111 PHOTO("photo", tr("Aerial or satellite photo")), 112 /** A map. **/ 113 MAP("map", tr("Map")), 114 /** A historic or otherwise outdated map. */ 115 HISTORICMAP("historicmap", tr("Historic or otherwise outdated map")), 116 /** A map based on OSM data. **/ 117 OSMBASEDMAP("osmbasedmap", tr("Map based on OSM data")), 118 /** A historic or otherwise outdated aerial or satellite photo. **/ 119 HISTORICPHOTO("historicphoto", tr("Historic or otherwise outdated aerial or satellite photo")), 120 /** Any other type of imagery **/ 121 OTHER("other", tr("Imagery not matching any other category")); 122 123 private final String category; 124 private final String description; 125 126 ImageryCategory(String category, String description) { 127 this.category = category; 128 this.description = description; 129 } 130 131 /** 132 * Returns the unique string identifying this category. 133 * @return the unique string identifying this category 134 */ 135 public final String getCategoryString() { 136 return category; 137 } 138 139 /** 140 * Returns the description of this category. 141 * @return the description of this category 142 */ 143 public final String getDescription() { 144 return description; 145 } 146 147 /** 148 * Returns the imagery category from the given category string. 149 * @param s The category string 150 * @return the imagery category matching the given category string 151 */ 152 public static ImageryCategory fromString(String s) { 153 for (ImageryCategory category : ImageryCategory.values()) { 154 if (category.getCategoryString().equals(s)) { 155 return category; 156 } 157 } 158 return null; 159 } 160 } 161 162 /** 163 * Multi-polygon bounds for imagery backgrounds. 164 * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location. 165 */ 166 public static class ImageryBounds extends Bounds { 167 168 /** 169 * Constructs a new {@code ImageryBounds} from string. 170 * @param asString The string containing the list of shapes defining this bounds 171 * @param separator The shape separator in the given string, usually a comma 172 */ 173 public ImageryBounds(String asString, String separator) { 174 super(asString, separator); 175 } 176 177 private List<Shape> shapes = new ArrayList<>(); 178 179 /** 180 * Adds a new shape to this bounds. 181 * @param shape The shape to add 182 */ 183 public final void addShape(Shape shape) { 184 this.shapes.add(shape); 185 } 186 187 /** 188 * Sets the list of shapes defining this bounds. 189 * @param shapes The list of shapes defining this bounds. 190 */ 191 public final void setShapes(List<Shape> shapes) { 192 this.shapes = shapes; 193 } 194 195 /** 196 * Returns the list of shapes defining this bounds. 197 * @return The list of shapes defining this bounds 198 */ 199 public final List<Shape> getShapes() { 200 return shapes; 201 } 202 203 @Override 204 public int hashCode() { 205 return Objects.hash(super.hashCode(), shapes); 206 } 207 208 @Override 209 public boolean equals(Object o) { 210 if (this == o) return true; 211 if (o == null || getClass() != o.getClass()) return false; 212 if (!super.equals(o)) return false; 213 ImageryBounds that = (ImageryBounds) o; 214 return Objects.equals(shapes, that.shapes); 215 } 216 } 217 218 /** original name of the imagery entry in case of translation call, for multiple languages English when possible */ 219 private String origName; 220 /** (original) language of the translated name entry */ 221 private String langName; 222 /** whether this is a entry activated by default or not */ 223 private boolean defaultEntry; 224 /** Whether this service requires a explicit EULA acceptance before it can be activated */ 225 private String eulaAcceptanceRequired; 226 /** type of the imagery servics - WMS, TMS, ... */ 227 private ImageryType imageryType = ImageryType.WMS; 228 private double pixelPerDegree; 229 /** maximum zoom level for TMS imagery */ 230 private int defaultMaxZoom; 231 /** minimum zoom level for TMS imagery */ 232 private int defaultMinZoom; 233 /** display bounds of imagery, displayed in prefs and used for automatic imagery selection */ 234 private ImageryBounds bounds; 235 /** projections supported by WMS servers */ 236 private List<String> serverProjections = Collections.emptyList(); 237 /** description of the imagery entry, should contain notes what type of data it is */ 238 private String description; 239 /** language of the description entry */ 240 private String langDescription; 241 /** Text of a text attribution displayed when using the imagery */ 242 private String attributionText; 243 /** Link to a reference stating the permission for OSM usage */ 244 private String permissionReferenceURL; 245 /** Link behind the text attribution displayed when using the imagery */ 246 private String attributionLinkURL; 247 /** Image of a graphical attribution displayed when using the imagery */ 248 private String attributionImage; 249 /** Link behind the graphical attribution displayed when using the imagery */ 250 private String attributionImageURL; 251 /** Text with usage terms displayed when using the imagery */ 252 private String termsOfUseText; 253 /** Link behind the text with usage terms displayed when using the imagery */ 254 private String termsOfUseURL; 255 /** country code of the imagery (for country specific imagery) */ 256 private String countryCode = ""; 257 /** 258 * creation date of the imagery (in the form YYYY-MM-DD;YYYY-MM-DD, where 259 * DD and MM as well as a second date are optional) 260 * @since 11570 261 */ 262 private String date; 263 /** 264 * marked as best in other editors 265 * @since 11575 266 */ 267 private boolean bestMarked; 268 /** 269 * marked as overlay 270 * @since 13536 271 */ 272 private boolean overlay; 273 /** 274 * list of old IDs, only for loading, not handled anywhere else 275 * @since 13536 276 */ 277 private Collection<String> oldIds; 278 /** mirrors of different type for this entry */ 279 private List<ImageryInfo> mirrors; 280 /** icon used in menu */ 281 private String icon; 282 /** is the geo reference correct - don't offer offset handling */ 283 private boolean isGeoreferenceValid; 284 /** which layers should be activated by default on layer addition. **/ 285 private List<DefaultLayer> defaultLayers = new ArrayList<>(); 286 /** HTTP headers **/ 287 private Map<String, String> customHttpHeaders = new ConcurrentHashMap<>(); 288 /** Should this map be transparent **/ 289 private boolean transparent = true; 290 private int minimumTileExpire = (int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get()); 291 /** category of the imagery */ 292 private ImageryCategory category; 293 /** category of the imagery (input string, not saved, copied or used otherwise except for error checks) */ 294 private String categoryOriginalString; 295 /** when adding a field, also adapt the: 296 * {@link #ImageryPreferenceEntry ImageryPreferenceEntry object} 297 * {@link #ImageryPreferenceEntry#ImageryPreferenceEntry(ImageryInfo) ImageryPreferenceEntry constructor} 298 * {@link #ImageryInfo(ImageryPreferenceEntry) ImageryInfo constructor} 299 * {@link #ImageryInfo(ImageryInfo) ImageryInfo constructor} 300 * {@link #equalsPref(ImageryPreferenceEntry) equalsPref method} 301 **/ 302 303 /** 304 * Auxiliary class to save an {@link ImageryInfo} object in the preferences. 305 */ 306 public static class ImageryPreferenceEntry { 307 @StructEntry String name; 308 @StructEntry String d; 309 @StructEntry String id; 310 @StructEntry String type; 311 @StructEntry String url; 312 @StructEntry double pixel_per_eastnorth; 313 @StructEntry String eula; 314 @StructEntry String attribution_text; 315 @StructEntry String attribution_url; 316 @StructEntry String permission_reference_url; 317 @StructEntry String logo_image; 318 @StructEntry String logo_url; 319 @StructEntry String terms_of_use_text; 320 @StructEntry String terms_of_use_url; 321 @StructEntry String country_code = ""; 322 @StructEntry String date; 323 @StructEntry int max_zoom; 324 @StructEntry int min_zoom; 325 @StructEntry String cookies; 326 @StructEntry String bounds; 327 @StructEntry String shapes; 328 @StructEntry String projections; 329 @StructEntry String icon; 330 @StructEntry String description; 331 @StructEntry MultiMap<String, String> noTileHeaders; 332 @StructEntry MultiMap<String, String> noTileChecksums; 333 @StructEntry int tileSize = -1; 334 @StructEntry Map<String, String> metadataHeaders; 335 @StructEntry boolean valid_georeference; 336 @StructEntry boolean bestMarked; 337 @StructEntry boolean modTileFeatures; 338 @StructEntry boolean overlay; 339 @StructEntry String default_layers; 340 @StructEntry Map<String, String> customHttpHeaders; 341 @StructEntry boolean transparent; 342 @StructEntry int minimumTileExpire; 343 @StructEntry String category; 344 345 /** 346 * Constructs a new empty WMS {@code ImageryPreferenceEntry}. 347 */ 348 public ImageryPreferenceEntry() { 349 // Do nothing 350 } 351 352 /** 353 * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}. 354 * @param i The corresponding imagery info 355 */ 356 public ImageryPreferenceEntry(ImageryInfo i) { 357 name = i.name; 358 id = i.id; 359 type = i.imageryType.getTypeString(); 360 url = i.url; 361 pixel_per_eastnorth = i.pixelPerDegree; 362 eula = i.eulaAcceptanceRequired; 363 attribution_text = i.attributionText; 364 attribution_url = i.attributionLinkURL; 365 permission_reference_url = i.permissionReferenceURL; 366 date = i.date; 367 bestMarked = i.bestMarked; 368 overlay = i.overlay; 369 logo_image = i.attributionImage; 370 logo_url = i.attributionImageURL; 371 terms_of_use_text = i.termsOfUseText; 372 terms_of_use_url = i.termsOfUseURL; 373 country_code = i.countryCode; 374 max_zoom = i.defaultMaxZoom; 375 min_zoom = i.defaultMinZoom; 376 cookies = i.cookies; 377 icon = i.icon; 378 description = i.description; 379 category = i.category != null ? i.category.getCategoryString() : null; 380 if (i.bounds != null) { 381 bounds = i.bounds.encodeAsString(","); 382 StringBuilder shapesString = new StringBuilder(); 383 for (Shape s : i.bounds.getShapes()) { 384 if (shapesString.length() > 0) { 385 shapesString.append(';'); 386 } 387 shapesString.append(s.encodeAsString(",")); 388 } 389 if (shapesString.length() > 0) { 390 shapes = shapesString.toString(); 391 } 392 } 393 if (!i.serverProjections.isEmpty()) { 394 projections = i.serverProjections.stream().collect(Collectors.joining(",")); 395 } 396 if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) { 397 noTileHeaders = new MultiMap<>(i.noTileHeaders); 398 } 399 400 if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) { 401 noTileChecksums = new MultiMap<>(i.noTileChecksums); 402 } 403 404 if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) { 405 metadataHeaders = i.metadataHeaders; 406 } 407 408 tileSize = i.getTileSize(); 409 410 valid_georeference = i.isGeoreferenceValid(); 411 modTileFeatures = i.isModTileFeatures(); 412 if (!i.defaultLayers.isEmpty()) { 413 default_layers = i.defaultLayers.stream().map(DefaultLayer::toJson).collect(JsonCollectors.toJsonArray()).toString(); 414 } 415 customHttpHeaders = i.customHttpHeaders; 416 transparent = i.isTransparent(); 417 minimumTileExpire = i.minimumTileExpire; 418 } 419 420 @Override 421 public String toString() { 422 StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name); 423 if (id != null) { 424 s.append(" id=").append(id); 425 } 426 s.append(']'); 427 return s.toString(); 428 } 429 } 430 431 /** 432 * Constructs a new WMS {@code ImageryInfo}. 433 */ 434 public ImageryInfo() { 435 super(); 436 } 437 438 /** 439 * Constructs a new WMS {@code ImageryInfo} with a given name. 440 * @param name The entry name 441 */ 442 public ImageryInfo(String name) { 443 super(name); 444 } 445 446 /** 447 * Constructs a new WMS {@code ImageryInfo} with given name and extended URL. 448 * @param name The entry name 449 * @param url The entry extended URL 450 */ 451 public ImageryInfo(String name, String url) { 452 this(name); 453 setExtendedUrl(url); 454 } 455 456 /** 457 * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs. 458 * @param name The entry name 459 * @param url The entry URL 460 * @param eulaAcceptanceRequired The EULA URL 461 */ 462 public ImageryInfo(String name, String url, String eulaAcceptanceRequired) { 463 this(name); 464 setExtendedUrl(url); 465 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 466 } 467 468 /** 469 * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs. 470 * @param name The entry name 471 * @param url The entry URL 472 * @param type The entry imagery type. If null, WMS will be used as default 473 * @param eulaAcceptanceRequired The EULA URL 474 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work 475 * @throws IllegalArgumentException if type refers to an unknown imagery type 476 */ 477 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) { 478 this(name); 479 setExtendedUrl(url); 480 ImageryType t = ImageryType.fromString(type); 481 this.cookies = cookies; 482 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 483 if (t != null) { 484 this.imageryType = t; 485 } else if (type != null && !type.isEmpty()) { 486 throw new IllegalArgumentException("unknown type: "+type); 487 } 488 } 489 490 /** 491 * Constructs a new {@code ImageryInfo} with given name, url, id, extended and EULA URLs. 492 * @param name The entry name 493 * @param url The entry URL 494 * @param type The entry imagery type. If null, WMS will be used as default 495 * @param eulaAcceptanceRequired The EULA URL 496 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work 497 * @param id tile id 498 * @throws IllegalArgumentException if type refers to an unknown imagery type 499 */ 500 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) { 501 this(name, url, type, eulaAcceptanceRequired, cookies); 502 setId(id); 503 } 504 505 /** 506 * Constructs a new {@code ImageryInfo} from an imagery preference entry. 507 * @param e The imagery preference entry 508 */ 509 public ImageryInfo(ImageryPreferenceEntry e) { 510 super(e.name, e.url, e.id); 511 CheckParameterUtil.ensureParameterNotNull(e.name, "name"); 512 CheckParameterUtil.ensureParameterNotNull(e.url, "url"); 513 description = e.description; 514 cookies = e.cookies; 515 eulaAcceptanceRequired = e.eula; 516 imageryType = ImageryType.fromString(e.type); 517 if (imageryType == null) throw new IllegalArgumentException("unknown type"); 518 pixelPerDegree = e.pixel_per_eastnorth; 519 defaultMaxZoom = e.max_zoom; 520 defaultMinZoom = e.min_zoom; 521 if (e.bounds != null) { 522 bounds = new ImageryBounds(e.bounds, ","); 523 if (e.shapes != null) { 524 try { 525 for (String s : e.shapes.split(";")) { 526 bounds.addShape(new Shape(s, ",")); 527 } 528 } catch (IllegalArgumentException ex) { 529 Logging.warn(ex); 530 } 531 } 532 } 533 if (e.projections != null && !e.projections.isEmpty()) { 534 // split generates null element on empty string which gives one element Array[null] 535 serverProjections = Arrays.asList(e.projections.split(",")); 536 } 537 attributionText = e.attribution_text; 538 attributionLinkURL = e.attribution_url; 539 permissionReferenceURL = e.permission_reference_url; 540 attributionImage = e.logo_image; 541 attributionImageURL = e.logo_url; 542 date = e.date; 543 bestMarked = e.bestMarked; 544 overlay = e.overlay; 545 termsOfUseText = e.terms_of_use_text; 546 termsOfUseURL = e.terms_of_use_url; 547 countryCode = e.country_code; 548 icon = e.icon; 549 if (e.noTileHeaders != null) { 550 noTileHeaders = e.noTileHeaders.toMap(); 551 } 552 if (e.noTileChecksums != null) { 553 noTileChecksums = e.noTileChecksums.toMap(); 554 } 555 setTileSize(e.tileSize); 556 metadataHeaders = e.metadataHeaders; 557 isGeoreferenceValid = e.valid_georeference; 558 modTileFeatures = e.modTileFeatures; 559 if (e.default_layers != null) { 560 try (JsonReader jsonReader = Json.createReader(new StringReader(e.default_layers))) { 561 defaultLayers = jsonReader. 562 readArray(). 563 stream(). 564 map(x -> DefaultLayer.fromJson((JsonObject) x, imageryType)). 565 collect(Collectors.toList()); 566 } 567 } 568 customHttpHeaders = e.customHttpHeaders; 569 transparent = e.transparent; 570 minimumTileExpire = e.minimumTileExpire; 571 category = ImageryCategory.fromString(e.category); 572 } 573 574 /** 575 * Constructs a new {@code ImageryInfo} from an existing one. 576 * @param i The other imagery info 577 */ 578 public ImageryInfo(ImageryInfo i) { 579 super(i.name, i.url, i.id); 580 this.noTileHeaders = i.noTileHeaders; 581 this.noTileChecksums = i.noTileChecksums; 582 this.minZoom = i.minZoom; 583 this.maxZoom = i.maxZoom; 584 this.cookies = i.cookies; 585 this.tileSize = i.tileSize; 586 this.metadataHeaders = i.metadataHeaders; 587 this.modTileFeatures = i.modTileFeatures; 588 589 this.origName = i.origName; 590 this.langName = i.langName; 591 this.defaultEntry = i.defaultEntry; 592 this.eulaAcceptanceRequired = null; 593 this.imageryType = i.imageryType; 594 this.pixelPerDegree = i.pixelPerDegree; 595 this.defaultMaxZoom = i.defaultMaxZoom; 596 this.defaultMinZoom = i.defaultMinZoom; 597 this.bounds = i.bounds; 598 this.serverProjections = i.serverProjections; 599 this.description = i.description; 600 this.langDescription = i.langDescription; 601 this.attributionText = i.attributionText; 602 this.permissionReferenceURL = i.permissionReferenceURL; 603 this.attributionLinkURL = i.attributionLinkURL; 604 this.attributionImage = i.attributionImage; 605 this.attributionImageURL = i.attributionImageURL; 606 this.termsOfUseText = i.termsOfUseText; 607 this.termsOfUseURL = i.termsOfUseURL; 608 this.countryCode = i.countryCode; 609 this.date = i.date; 610 this.bestMarked = i.bestMarked; 611 this.overlay = i.overlay; 612 // do not copy field {@code mirrors} 613 this.icon = i.icon; 614 this.isGeoreferenceValid = i.isGeoreferenceValid; 615 this.defaultLayers = i.defaultLayers; 616 this.customHttpHeaders = i.customHttpHeaders; 617 this.transparent = i.transparent; 618 this.minimumTileExpire = i.minimumTileExpire; 619 this.category = i.category; 620 } 621 622 @Override 623 public int hashCode() { 624 return Objects.hash(url, imageryType); 625 } 626 627 /** 628 * Check if this object equals another ImageryInfo with respect to the properties 629 * that get written to the preference file. 630 * 631 * The field {@link #pixelPerDegree} is ignored. 632 * 633 * @param other the ImageryInfo object to compare to 634 * @return true if they are equal 635 */ 636 public boolean equalsPref(ImageryInfo other) { 637 if (other == null) { 638 return false; 639 } 640 641 // CHECKSTYLE.OFF: BooleanExpressionComplexity 642 return 643 Objects.equals(this.name, other.name) && 644 Objects.equals(this.id, other.id) && 645 Objects.equals(this.url, other.url) && 646 Objects.equals(this.modTileFeatures, other.modTileFeatures) && 647 Objects.equals(this.bestMarked, other.bestMarked) && 648 Objects.equals(this.overlay, other.overlay) && 649 Objects.equals(this.isGeoreferenceValid, other.isGeoreferenceValid) && 650 Objects.equals(this.cookies, other.cookies) && 651 Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) && 652 Objects.equals(this.imageryType, other.imageryType) && 653 Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) && 654 Objects.equals(this.defaultMinZoom, other.defaultMinZoom) && 655 Objects.equals(this.bounds, other.bounds) && 656 Objects.equals(this.serverProjections, other.serverProjections) && 657 Objects.equals(this.attributionText, other.attributionText) && 658 Objects.equals(this.attributionLinkURL, other.attributionLinkURL) && 659 Objects.equals(this.permissionReferenceURL, other.permissionReferenceURL) && 660 Objects.equals(this.attributionImageURL, other.attributionImageURL) && 661 Objects.equals(this.attributionImage, other.attributionImage) && 662 Objects.equals(this.termsOfUseText, other.termsOfUseText) && 663 Objects.equals(this.termsOfUseURL, other.termsOfUseURL) && 664 Objects.equals(this.countryCode, other.countryCode) && 665 Objects.equals(this.date, other.date) && 666 Objects.equals(this.icon, other.icon) && 667 Objects.equals(this.description, other.description) && 668 Objects.equals(this.noTileHeaders, other.noTileHeaders) && 669 Objects.equals(this.noTileChecksums, other.noTileChecksums) && 670 Objects.equals(this.metadataHeaders, other.metadataHeaders) && 671 Objects.equals(this.defaultLayers, other.defaultLayers) && 672 Objects.equals(this.customHttpHeaders, other.customHttpHeaders) && 673 Objects.equals(this.transparent, other.transparent) && 674 Objects.equals(this.minimumTileExpire, other.minimumTileExpire) && 675 Objects.equals(this.category, other.category); 676 // CHECKSTYLE.ON: BooleanExpressionComplexity 677 } 678 679 @Override 680 public boolean equals(Object o) { 681 if (this == o) return true; 682 if (o == null || getClass() != o.getClass()) return false; 683 ImageryInfo that = (ImageryInfo) o; 684 return imageryType == that.imageryType && Objects.equals(url, that.url); 685 } 686 687 @Override 688 public String toString() { 689 return "ImageryInfo{" + 690 "name='" + name + '\'' + 691 ", countryCode='" + countryCode + '\'' + 692 ", url='" + url + '\'' + 693 ", imageryType=" + imageryType + 694 '}'; 695 } 696 697 @Override 698 public int compareTo(ImageryInfo in) { 699 int i = countryCode.compareTo(in.countryCode); 700 if (i == 0) { 701 i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH)); 702 } 703 if (i == 0) { 704 i = url.compareTo(in.url); 705 } 706 if (i == 0) { 707 i = Double.compare(pixelPerDegree, in.pixelPerDegree); 708 } 709 return i; 710 } 711 712 /** 713 * Determines if URL is equal to given imagery info. 714 * @param in imagery info 715 * @return {@code true} if URL is equal to given imagery info 716 */ 717 public boolean equalsBaseValues(ImageryInfo in) { 718 return url.equals(in.url); 719 } 720 721 /** 722 * Sets the pixel per degree value. 723 * @param ppd The ppd value 724 * @see #getPixelPerDegree() 725 */ 726 public void setPixelPerDegree(double ppd) { 727 this.pixelPerDegree = ppd; 728 } 729 730 /** 731 * Sets the maximum zoom level. 732 * @param defaultMaxZoom The maximum zoom level 733 */ 734 public void setDefaultMaxZoom(int defaultMaxZoom) { 735 this.defaultMaxZoom = defaultMaxZoom; 736 } 737 738 /** 739 * Sets the minimum zoom level. 740 * @param defaultMinZoom The minimum zoom level 741 */ 742 public void setDefaultMinZoom(int defaultMinZoom) { 743 this.defaultMinZoom = defaultMinZoom; 744 } 745 746 /** 747 * Sets the imagery polygonial bounds. 748 * @param b The imagery bounds (non-rectangular) 749 */ 750 public void setBounds(ImageryBounds b) { 751 this.bounds = b; 752 } 753 754 /** 755 * Returns the imagery polygonial bounds. 756 * @return The imagery bounds (non-rectangular) 757 */ 758 public ImageryBounds getBounds() { 759 return bounds; 760 } 761 762 @Override 763 public boolean requiresAttribution() { 764 return attributionText != null || attributionLinkURL != null || attributionImage != null 765 || termsOfUseText != null || termsOfUseURL != null; 766 } 767 768 @Override 769 public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) { 770 return attributionText; 771 } 772 773 @Override 774 public String getAttributionLinkURL() { 775 return attributionLinkURL; 776 } 777 778 /** 779 * Return the permission reference URL. 780 * @return The url 781 * @see #setPermissionReferenceURL 782 * @since 11975 783 */ 784 public String getPermissionReferenceURL() { 785 return permissionReferenceURL; 786 } 787 788 @Override 789 public Image getAttributionImage() { 790 ImageIcon i = ImageProvider.getIfAvailable(attributionImage); 791 if (i != null) { 792 return i.getImage(); 793 } 794 return null; 795 } 796 797 /** 798 * Return the raw attribution logo information (an URL to the image). 799 * @return The url text 800 * @since 12257 801 */ 802 public String getAttributionImageRaw() { 803 return attributionImage; 804 } 805 806 @Override 807 public String getAttributionImageURL() { 808 return attributionImageURL; 809 } 810 811 @Override 812 public String getTermsOfUseText() { 813 return termsOfUseText; 814 } 815 816 @Override 817 public String getTermsOfUseURL() { 818 return termsOfUseURL; 819 } 820 821 /** 822 * Set the attribution text 823 * @param text The text 824 * @see #getAttributionText(int, ICoordinate, ICoordinate) 825 */ 826 public void setAttributionText(String text) { 827 attributionText = text; 828 } 829 830 /** 831 * Set the attribution image 832 * @param url The url of the image. 833 * @see #getAttributionImageURL() 834 */ 835 public void setAttributionImageURL(String url) { 836 attributionImageURL = url; 837 } 838 839 /** 840 * Set the image for the attribution 841 * @param res The image resource 842 * @see #getAttributionImage() 843 */ 844 public void setAttributionImage(String res) { 845 attributionImage = res; 846 } 847 848 /** 849 * Sets the URL the attribution should link to. 850 * @param url The url. 851 * @see #getAttributionLinkURL() 852 */ 853 public void setAttributionLinkURL(String url) { 854 attributionLinkURL = url; 855 } 856 857 /** 858 * Sets the permission reference URL. 859 * @param url The url. 860 * @see #getPermissionReferenceURL() 861 * @since 11975 862 */ 863 public void setPermissionReferenceURL(String url) { 864 permissionReferenceURL = url; 865 } 866 867 /** 868 * Sets the text to display to the user as terms of use. 869 * @param text The text 870 * @see #getTermsOfUseText() 871 */ 872 public void setTermsOfUseText(String text) { 873 termsOfUseText = text; 874 } 875 876 /** 877 * Sets a url that links to the terms of use text. 878 * @param text The url. 879 * @see #getTermsOfUseURL() 880 */ 881 public void setTermsOfUseURL(String text) { 882 termsOfUseURL = text; 883 } 884 885 /** 886 * Sets the extended URL of this entry. 887 * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info 888 */ 889 public void setExtendedUrl(String url) { 890 CheckParameterUtil.ensureParameterNotNull(url); 891 892 // Default imagery type is WMS 893 this.url = url; 894 this.imageryType = ImageryType.WMS; 895 896 defaultMaxZoom = 0; 897 defaultMinZoom = 0; 898 for (ImageryType type : ImageryType.values()) { 899 Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+)[,-])?(\\d+)\\])?:(.*)").matcher(url); 900 if (m.matches()) { 901 this.url = m.group(3); 902 this.imageryType = type; 903 if (m.group(2) != null) { 904 defaultMaxZoom = Integer.parseInt(m.group(2)); 905 } 906 if (m.group(1) != null) { 907 defaultMinZoom = Integer.parseInt(m.group(1)); 908 } 909 break; 910 } 911 } 912 913 if (serverProjections.isEmpty()) { 914 serverProjections = new ArrayList<>(); 915 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH)); 916 if (m.matches()) { 917 for (String p : m.group(1).split(",")) { 918 serverProjections.add(p); 919 } 920 } 921 } 922 } 923 924 /** 925 * Returns the entry name. 926 * @return The entry name 927 * @since 6968 928 */ 929 public String getOriginalName() { 930 return this.origName != null ? this.origName : this.name; 931 } 932 933 /** 934 * Sets the entry name and handle translation. 935 * @param language The used language 936 * @param name The entry name 937 * @since 8091 938 */ 939 public void setName(String language, String name) { 940 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language); 941 if (LanguageInfo.isBetterLanguage(langName, language)) { 942 this.name = isdefault ? tr(name) : name; 943 this.langName = language; 944 } 945 if (origName == null || isdefault) { 946 this.origName = name; 947 } 948 } 949 950 /** 951 * Store the id of this info to the preferences and clear it afterwards. 952 */ 953 public void clearId() { 954 if (this.id != null) { 955 Collection<String> newAddedIds = new TreeSet<>(Config.getPref().getList("imagery.layers.addedIds")); 956 newAddedIds.add(this.id); 957 Config.getPref().putList("imagery.layers.addedIds", new ArrayList<>(newAddedIds)); 958 } 959 setId(null); 960 } 961 962 /** 963 * Determines if this entry is enabled by default. 964 * @return {@code true} if this entry is enabled by default, {@code false} otherwise 965 */ 966 public boolean isDefaultEntry() { 967 return defaultEntry; 968 } 969 970 /** 971 * Sets the default state of this entry. 972 * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise 973 */ 974 public void setDefaultEntry(boolean defaultEntry) { 975 this.defaultEntry = defaultEntry; 976 } 977 978 /** 979 * Gets the pixel per degree value 980 * @return The ppd value. 981 */ 982 public double getPixelPerDegree() { 983 return this.pixelPerDegree; 984 } 985 986 /** 987 * Returns the maximum zoom level. 988 * @return The maximum zoom level 989 */ 990 @Override 991 public int getMaxZoom() { 992 return this.defaultMaxZoom; 993 } 994 995 /** 996 * Returns the minimum zoom level. 997 * @return The minimum zoom level 998 */ 999 @Override 1000 public int getMinZoom() { 1001 return this.defaultMinZoom; 1002 } 1003 1004 /** 1005 * Returns the description text when existing. 1006 * @return The description 1007 * @since 8065 1008 */ 1009 public String getDescription() { 1010 return this.description; 1011 } 1012 1013 /** 1014 * Sets the description text when existing. 1015 * @param language The used language 1016 * @param description the imagery description text 1017 * @since 8091 1018 */ 1019 public void setDescription(String language, String description) { 1020 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language); 1021 if (LanguageInfo.isBetterLanguage(langDescription, language)) { 1022 this.description = isdefault ? tr(description) : description; 1023 this.langDescription = language; 1024 } 1025 } 1026 1027 /** 1028 * Return the sorted list of activated Imagery IDs. 1029 * @return sorted list of activated Imagery IDs 1030 * @since 13536 1031 */ 1032 public static Collection<String> getActiveIds() { 1033 ArrayList<String> ids = new ArrayList<>(); 1034 IPreferences pref = Config.getPref(); 1035 if (pref != null) { 1036 List<ImageryPreferenceEntry> entries = StructUtils.getListOfStructs( 1037 pref, "imagery.entries", null, ImageryPreferenceEntry.class); 1038 if (entries != null) { 1039 for (ImageryPreferenceEntry prefEntry : entries) { 1040 if (prefEntry.id != null && !prefEntry.id.isEmpty()) 1041 ids.add(prefEntry.id); 1042 } 1043 Collections.sort(ids); 1044 } 1045 } 1046 return ids; 1047 } 1048 1049 /** 1050 * Returns a tool tip text for display. 1051 * @return The text 1052 * @since 8065 1053 */ 1054 public String getToolTipText() { 1055 StringBuilder res = new StringBuilder(getName()); 1056 boolean html = false; 1057 String dateStr = getDate(); 1058 if (dateStr != null && !dateStr.isEmpty()) { 1059 res.append("<br>").append(tr("Date of imagery: {0}", dateStr)); 1060 html = true; 1061 } 1062 if (category != null && category.getDescription() != null) { 1063 res.append("<br>").append(tr("Imagery category: {0}", category.getDescription())); 1064 html = true; 1065 } 1066 if (bestMarked) { 1067 res.append("<br>").append(tr("This imagery is marked as best in this region in other editors.")); 1068 html = true; 1069 } 1070 if (overlay) { 1071 res.append("<br>").append(tr("This imagery is an overlay.")); 1072 html = true; 1073 } 1074 String desc = getDescription(); 1075 if (desc != null && !desc.isEmpty()) { 1076 res.append("<br>").append(Utils.escapeReservedCharactersHTML(desc)); 1077 html = true; 1078 } 1079 if (html) { 1080 res.insert(0, "<html>").append("</html>"); 1081 } 1082 return res.toString(); 1083 } 1084 1085 /** 1086 * Returns the EULA acceptance URL, if any. 1087 * @return The URL to an EULA text that has to be accepted before use, or {@code null} 1088 */ 1089 public String getEulaAcceptanceRequired() { 1090 return eulaAcceptanceRequired; 1091 } 1092 1093 /** 1094 * Sets the EULA acceptance URL. 1095 * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use 1096 */ 1097 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) { 1098 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 1099 } 1100 1101 /** 1102 * Returns the ISO 3166-1-alpha-2 country code. 1103 * @return The country code (2 letters) 1104 */ 1105 public String getCountryCode() { 1106 return countryCode; 1107 } 1108 1109 /** 1110 * Sets the ISO 3166-1-alpha-2 country code. 1111 * @param countryCode The country code (2 letters) 1112 */ 1113 public void setCountryCode(String countryCode) { 1114 this.countryCode = countryCode; 1115 } 1116 1117 /** 1118 * Returns the date information. 1119 * @return The date (in the form YYYY-MM-DD;YYYY-MM-DD, where 1120 * DD and MM as well as a second date are optional) 1121 * @since 11570 1122 */ 1123 public String getDate() { 1124 return date; 1125 } 1126 1127 /** 1128 * Sets the date information. 1129 * @param date The date information 1130 * @since 11570 1131 */ 1132 public void setDate(String date) { 1133 this.date = date; 1134 } 1135 1136 /** 1137 * Returns the entry icon. 1138 * @return The entry icon 1139 */ 1140 public String getIcon() { 1141 return icon; 1142 } 1143 1144 /** 1145 * Sets the entry icon. 1146 * @param icon The entry icon 1147 */ 1148 public void setIcon(String icon) { 1149 this.icon = icon; 1150 } 1151 1152 /** 1153 * Get the projections supported by the server. Only relevant for 1154 * WMS-type ImageryInfo at the moment. 1155 * @return null, if no projections have been specified; the list 1156 * of supported projections otherwise. 1157 */ 1158 public List<String> getServerProjections() { 1159 return Collections.unmodifiableList(serverProjections); 1160 } 1161 1162 /** 1163 * Sets the list of collections the server supports 1164 * @param serverProjections The list of supported projections 1165 */ 1166 public void setServerProjections(Collection<String> serverProjections) { 1167 CheckParameterUtil.ensureParameterNotNull(serverProjections, "serverProjections"); 1168 this.serverProjections = new ArrayList<>(serverProjections); 1169 } 1170 1171 /** 1172 * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info. 1173 * @return The extended URL 1174 */ 1175 public String getExtendedUrl() { 1176 return imageryType.getTypeString() + (defaultMaxZoom != 0 1177 ? ('['+(defaultMinZoom != 0 ? (Integer.toString(defaultMinZoom) + ',') : "")+defaultMaxZoom+']') : "") + ':' + url; 1178 } 1179 1180 /** 1181 * Gets a unique toolbar key to store this layer as toolbar item 1182 * @return The kay. 1183 */ 1184 public String getToolbarName() { 1185 String res = name; 1186 if (pixelPerDegree != 0) { 1187 res += "#PPD="+pixelPerDegree; 1188 } 1189 return res; 1190 } 1191 1192 /** 1193 * Gets the name that should be displayed in the menu to add this imagery layer. 1194 * @return The text. 1195 */ 1196 public String getMenuName() { 1197 String res = name; 1198 if (pixelPerDegree != 0) { 1199 res += " ("+pixelPerDegree+')'; 1200 } 1201 return res; 1202 } 1203 1204 /** 1205 * Determines if this entry requires attribution. 1206 * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise 1207 */ 1208 public boolean hasAttribution() { 1209 return attributionText != null; 1210 } 1211 1212 /** 1213 * Copies attribution from another {@code ImageryInfo}. 1214 * @param i The other imagery info to get attribution from 1215 */ 1216 public void copyAttribution(ImageryInfo i) { 1217 this.attributionImage = i.attributionImage; 1218 this.attributionImageURL = i.attributionImageURL; 1219 this.attributionText = i.attributionText; 1220 this.attributionLinkURL = i.attributionLinkURL; 1221 this.termsOfUseText = i.termsOfUseText; 1222 this.termsOfUseURL = i.termsOfUseURL; 1223 } 1224 1225 /** 1226 * Applies the attribution from this object to a tile source. 1227 * @param s The tile source 1228 */ 1229 public void setAttribution(AbstractTileSource s) { 1230 if (attributionText != null) { 1231 if ("osm".equals(attributionText)) { 1232 s.setAttributionText(new Mapnik().getAttributionText(0, null, null)); 1233 } else { 1234 s.setAttributionText(attributionText); 1235 } 1236 } 1237 if (attributionLinkURL != null) { 1238 if ("osm".equals(attributionLinkURL)) { 1239 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL()); 1240 } else { 1241 s.setAttributionLinkURL(attributionLinkURL); 1242 } 1243 } 1244 if (attributionImage != null) { 1245 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage); 1246 if (i != null) { 1247 s.setAttributionImage(i.getImage()); 1248 } 1249 } 1250 if (attributionImageURL != null) { 1251 s.setAttributionImageURL(attributionImageURL); 1252 } 1253 if (termsOfUseText != null) { 1254 s.setTermsOfUseText(termsOfUseText); 1255 } 1256 if (termsOfUseURL != null) { 1257 if ("osm".equals(termsOfUseURL)) { 1258 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL()); 1259 } else { 1260 s.setTermsOfUseURL(termsOfUseURL); 1261 } 1262 } 1263 } 1264 1265 /** 1266 * Returns the imagery type. 1267 * @return The imagery type 1268 */ 1269 public ImageryType getImageryType() { 1270 return imageryType; 1271 } 1272 1273 /** 1274 * Sets the imagery type. 1275 * @param imageryType The imagery type 1276 */ 1277 public void setImageryType(ImageryType imageryType) { 1278 this.imageryType = imageryType; 1279 } 1280 1281 /** 1282 * Returns the imagery category. 1283 * @return The imagery category 1284 * @since 13792 1285 */ 1286 public ImageryCategory getImageryCategory() { 1287 return category; 1288 } 1289 1290 /** 1291 * Sets the imagery category. 1292 * @param category The imagery category 1293 * @since 13792 1294 */ 1295 public void setImageryCategory(ImageryCategory category) { 1296 this.category = category; 1297 } 1298 1299 /** 1300 * Returns the imagery category original string (don't use except for error checks). 1301 * @return The imagery category original string 1302 * @since 13792 1303 */ 1304 public String getImageryCategoryOriginalString() { 1305 return categoryOriginalString; 1306 } 1307 1308 /** 1309 * Sets the imagery category original string (don't use except for error checks). 1310 * @param categoryOriginalString The imagery category original string 1311 * @since 13792 1312 */ 1313 public void setImageryCategoryOriginalString(String categoryOriginalString) { 1314 this.categoryOriginalString = categoryOriginalString; 1315 } 1316 1317 /** 1318 * Returns true if this layer's URL is matched by one of the regular 1319 * expressions kept by the current OsmApi instance. 1320 * @return {@code true} is this entry is blacklisted, {@code false} otherwise 1321 */ 1322 public boolean isBlacklisted() { 1323 Capabilities capabilities = OsmApi.getOsmApi().getCapabilities(); 1324 return capabilities != null && capabilities.isOnImageryBlacklist(this.url); 1325 } 1326 1327 /** 1328 * Sets the map of <header name, header value> that if any of this header 1329 * will be returned, then this tile will be treated as "no tile at this zoom level" 1330 * 1331 * @param noTileHeaders Map of <header name, header value> which will be treated as "no tile at this zoom level" 1332 * @since 9613 1333 */ 1334 public void setNoTileHeaders(MultiMap<String, String> noTileHeaders) { 1335 if (noTileHeaders == null || noTileHeaders.isEmpty()) { 1336 this.noTileHeaders = null; 1337 } else { 1338 this.noTileHeaders = noTileHeaders.toMap(); 1339 } 1340 } 1341 1342 @Override 1343 public Map<String, Set<String>> getNoTileHeaders() { 1344 return noTileHeaders; 1345 } 1346 1347 /** 1348 * Sets the map of <checksum type, checksum value> that if any tile with that checksum 1349 * will be returned, then this tile will be treated as "no tile at this zoom level" 1350 * 1351 * @param noTileChecksums Map of <checksum type, checksum value> which will be treated as "no tile at this zoom level" 1352 * @since 9613 1353 */ 1354 public void setNoTileChecksums(MultiMap<String, String> noTileChecksums) { 1355 if (noTileChecksums == null || noTileChecksums.isEmpty()) { 1356 this.noTileChecksums = null; 1357 } else { 1358 this.noTileChecksums = noTileChecksums.toMap(); 1359 } 1360 } 1361 1362 @Override 1363 public Map<String, Set<String>> getNoTileChecksums() { 1364 return noTileChecksums; 1365 } 1366 1367 /** 1368 * Returns the map of <header name, metadata key> indicating, which HTTP headers should 1369 * be moved to metadata 1370 * 1371 * @param metadataHeaders map of <header name, metadata key> indicating, which HTTP headers should be moved to metadata 1372 * @since 8418 1373 */ 1374 public void setMetadataHeaders(Map<String, String> metadataHeaders) { 1375 if (metadataHeaders == null || metadataHeaders.isEmpty()) { 1376 this.metadataHeaders = null; 1377 } else { 1378 this.metadataHeaders = metadataHeaders; 1379 } 1380 } 1381 1382 /** 1383 * Gets the flag if the georeference is valid. 1384 * @return <code>true</code> if it is valid. 1385 */ 1386 public boolean isGeoreferenceValid() { 1387 return isGeoreferenceValid; 1388 } 1389 1390 /** 1391 * Sets an indicator that the georeference is valid 1392 * @param isGeoreferenceValid <code>true</code> if it is marked as valid. 1393 */ 1394 public void setGeoreferenceValid(boolean isGeoreferenceValid) { 1395 this.isGeoreferenceValid = isGeoreferenceValid; 1396 } 1397 1398 /** 1399 * Returns the status of "best" marked status in other editors. 1400 * @return <code>true</code> if it is marked as best. 1401 * @since 11575 1402 */ 1403 public boolean isBestMarked() { 1404 return bestMarked; 1405 } 1406 1407 /** 1408 * Returns the overlay indication. 1409 * @return <code>true</code> if it is an overlay. 1410 * @since 13536 1411 */ 1412 public boolean isOverlay() { 1413 return overlay; 1414 } 1415 1416 /** 1417 * Sets an indicator that in other editors it is marked as best imagery 1418 * @param bestMarked <code>true</code> if it is marked as best in other editors. 1419 * @since 11575 1420 */ 1421 public void setBestMarked(boolean bestMarked) { 1422 this.bestMarked = bestMarked; 1423 } 1424 1425 /** 1426 * Sets overlay indication 1427 * @param overlay <code>true</code> if it is an overlay. 1428 * @since 13536 1429 */ 1430 public void setOverlay(boolean overlay) { 1431 this.overlay = overlay; 1432 } 1433 1434 /** 1435 * Adds an old Id. 1436 * 1437 * @param id the Id to be added 1438 * @since 13536 1439 */ 1440 public void addOldId(String id) { 1441 if (oldIds == null) { 1442 oldIds = new ArrayList<>(); 1443 } 1444 oldIds.add(id); 1445 } 1446 1447 /** 1448 * Get old Ids. 1449 * 1450 * @return collection of ids 1451 * @since 13536 1452 */ 1453 public Collection<String> getOldIds() { 1454 return oldIds; 1455 } 1456 1457 /** 1458 * Adds a mirror entry. Mirror entries are completed with the data from the master entry 1459 * and only describe another method to access identical data. 1460 * 1461 * @param entry the mirror to be added 1462 * @since 9658 1463 */ 1464 public void addMirror(ImageryInfo entry) { 1465 if (mirrors == null) { 1466 mirrors = new ArrayList<>(); 1467 } 1468 mirrors.add(entry); 1469 } 1470 1471 /** 1472 * Returns the mirror entries. Entries are completed with master entry data. 1473 * 1474 * @return the list of mirrors 1475 * @since 9658 1476 */ 1477 public List<ImageryInfo> getMirrors() { 1478 List<ImageryInfo> l = new ArrayList<>(); 1479 if (mirrors != null) { 1480 int num = 1; 1481 for (ImageryInfo i : mirrors) { 1482 ImageryInfo n = new ImageryInfo(this); 1483 if (i.defaultMaxZoom != 0) { 1484 n.defaultMaxZoom = i.defaultMaxZoom; 1485 } 1486 if (i.defaultMinZoom != 0) { 1487 n.defaultMinZoom = i.defaultMinZoom; 1488 } 1489 n.setServerProjections(i.getServerProjections()); 1490 n.url = i.url; 1491 n.imageryType = i.imageryType; 1492 if (i.getTileSize() != 0) { 1493 n.setTileSize(i.getTileSize()); 1494 } 1495 if (n.id != null) { 1496 n.id = n.id + "_mirror"+num; 1497 } 1498 if (num > 1) { 1499 n.name = tr("{0} mirror server {1}", n.name, num); 1500 if (n.origName != null) { 1501 n.origName += " mirror server " + num; 1502 } 1503 } else { 1504 n.name = tr("{0} mirror server", n.name); 1505 if (n.origName != null) { 1506 n.origName += " mirror server"; 1507 } 1508 } 1509 l.add(n); 1510 ++num; 1511 } 1512 } 1513 return l; 1514 } 1515 1516 /** 1517 * Returns default layers that should be shown for this Imagery (if at all supported by imagery provider) 1518 * If no layer is set to default and there is more than one imagery available, then user will be asked to choose the layer 1519 * to work on 1520 * @return Collection of the layer names 1521 */ 1522 public List<DefaultLayer> getDefaultLayers() { 1523 return defaultLayers; 1524 } 1525 1526 /** 1527 * Sets the default layers that user will work with 1528 * @param layers set the list of default layers 1529 */ 1530 public void setDefaultLayers(List<DefaultLayer> layers) { 1531 this.defaultLayers = layers; 1532 } 1533 1534 /** 1535 * Returns custom HTTP headers that should be sent with request towards imagery provider 1536 * @return headers 1537 */ 1538 public Map<String, String> getCustomHttpHeaders() { 1539 if (customHttpHeaders == null) { 1540 return Collections.emptyMap(); 1541 } 1542 return customHttpHeaders; 1543 } 1544 1545 /** 1546 * Sets custom HTTP headers that should be sent with request towards imagery provider 1547 * @param customHttpHeaders http headers 1548 */ 1549 public void setCustomHttpHeaders(Map<String, String> customHttpHeaders) { 1550 this.customHttpHeaders = customHttpHeaders; 1551 } 1552 1553 /** 1554 * Determines if this imagery should be transparent. 1555 * @return should this imagery be transparent 1556 */ 1557 public boolean isTransparent() { 1558 return transparent; 1559 } 1560 1561 /** 1562 * Sets whether imagery should be transparent. 1563 * @param transparent set to true if imagery should be transparent 1564 */ 1565 public void setTransparent(boolean transparent) { 1566 this.transparent = transparent; 1567 } 1568 1569 /** 1570 * Returns minimum tile expiration in seconds. 1571 * @return minimum tile expiration in seconds 1572 */ 1573 public int getMinimumTileExpire() { 1574 return minimumTileExpire; 1575 } 1576 1577 /** 1578 * Sets minimum tile expiration in seconds. 1579 * @param minimumTileExpire minimum tile expiration in seconds 1580 */ 1581 public void setMinimumTileExpire(int minimumTileExpire) { 1582 this.minimumTileExpire = minimumTileExpire; 1583 } 1584 1585 /** 1586 * Get a string representation of this imagery info suitable for the {@code source} changeset tag. 1587 * @return English name, if known 1588 * @since 13890 1589 */ 1590 public String getSourceName() { 1591 if (ImageryType.BING.equals(getImageryType())) { 1592 return "Bing"; 1593 } else { 1594 if (id != null) { 1595 // Retrieve english name, unfortunately not saved in preferences 1596 Optional<ImageryInfo> infoEn = ImageryLayerInfo.allDefaultLayers.stream().filter(x -> id.equals(x.getId())).findAny(); 1597 if (infoEn.isPresent()) { 1598 return infoEn.get().getOriginalName(); 1599 } 1600 } 1601 return getOriginalName(); 1602 } 1603 } 1604}