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