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