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.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.List; 012import java.util.Locale; 013import java.util.Map; 014import java.util.Objects; 015import java.util.Set; 016import java.util.TreeSet; 017import java.util.regex.Matcher; 018import java.util.regex.Pattern; 019 020import javax.swing.ImageIcon; 021 022import org.openstreetmap.gui.jmapviewer.interfaces.Attributed; 023import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 024import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource; 025import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik; 026import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; 027import org.openstreetmap.josm.Main; 028import org.openstreetmap.josm.data.Bounds; 029import org.openstreetmap.josm.data.Preferences.pref; 030import org.openstreetmap.josm.io.Capabilities; 031import org.openstreetmap.josm.io.OsmApi; 032import org.openstreetmap.josm.tools.CheckParameterUtil; 033import org.openstreetmap.josm.tools.ImageProvider; 034import org.openstreetmap.josm.tools.LanguageInfo; 035import org.openstreetmap.josm.tools.MultiMap; 036 037/** 038 * Class that stores info about an image background layer. 039 * 040 * @author Frederik Ramm 041 */ 042public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed { 043 044 /** 045 * Type of imagery entry. 046 */ 047 public enum ImageryType { 048 /** A WMS (Web Map Service) entry. **/ 049 WMS("wms"), 050 /** A TMS (Tile Map Service) entry. **/ 051 TMS("tms"), 052 /** An HTML proxy (previously used for Yahoo imagery) entry. **/ 053 HTML("html"), 054 /** TMS entry for Microsoft Bing. */ 055 BING("bing"), 056 /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/ 057 SCANEX("scanex"), 058 /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/ 059 WMS_ENDPOINT("wms_endpoint"), 060 /** WMTS stores GetCapabilities URL. Does not store any information about the layer **/ 061 WMTS("wmts"); 062 063 064 private final String typeString; 065 066 ImageryType(String urlString) { 067 this.typeString = urlString; 068 } 069 070 /** 071 * Returns the unique string identifying this type. 072 * @return the unique string identifying this type 073 * @since 6690 074 */ 075 public final String getTypeString() { 076 return typeString; 077 } 078 079 /** 080 * Returns the imagery type from the given type string. 081 * @param s The type string 082 * @return the imagery type matching the given type string 083 */ 084 public static ImageryType fromString(String s) { 085 for (ImageryType type : ImageryType.values()) { 086 if (type.getTypeString().equals(s)) { 087 return type; 088 } 089 } 090 return null; 091 } 092 } 093 094 /** 095 * Multi-polygon bounds for imagery backgrounds. 096 * Used to display imagery coverage in preferences and to determine relevant imagery entries based on edit location. 097 */ 098 public static class ImageryBounds extends Bounds { 099 100 /** 101 * Constructs a new {@code ImageryBounds} from string. 102 * @param asString The string containing the list of shapes defining this bounds 103 * @param separator The shape separator in the given string, usually a comma 104 */ 105 public ImageryBounds(String asString, String separator) { 106 super(asString, separator); 107 } 108 109 private List<Shape> shapes = new ArrayList<>(); 110 111 /** 112 * Adds a new shape to this bounds. 113 * @param shape The shape to add 114 */ 115 public final void addShape(Shape shape) { 116 this.shapes.add(shape); 117 } 118 119 /** 120 * Sets the list of shapes defining this bounds. 121 * @param shapes The list of shapes defining this bounds. 122 */ 123 public final void setShapes(List<Shape> shapes) { 124 this.shapes = shapes; 125 } 126 127 /** 128 * Returns the list of shapes defining this bounds. 129 * @return The list of shapes defining this bounds 130 */ 131 public final List<Shape> getShapes() { 132 return shapes; 133 } 134 135 @Override 136 public int hashCode() { 137 return Objects.hash(super.hashCode(), shapes); 138 } 139 140 @Override 141 public boolean equals(Object o) { 142 if (this == o) return true; 143 if (o == null || getClass() != o.getClass()) return false; 144 if (!super.equals(o)) return false; 145 ImageryBounds that = (ImageryBounds) o; 146 return Objects.equals(shapes, that.shapes); 147 } 148 } 149 150 /** original name of the imagery entry in case of translation call, for multiple languages English when possible */ 151 private String origName; 152 /** (original) language of the translated name entry */ 153 private String langName; 154 /** whether this is a entry activated by default or not */ 155 private boolean defaultEntry; 156 /** The data part of HTTP cookies header in case the service requires cookies to work */ 157 private String cookies; 158 /** Whether this service requires a explicit EULA acceptance before it can be activated */ 159 private String eulaAcceptanceRequired; 160 /** type of the imagery servics - WMS, TMS, ... */ 161 private ImageryType imageryType = ImageryType.WMS; 162 private double pixelPerDegree; 163 /** maximum zoom level for TMS imagery */ 164 private int defaultMaxZoom; 165 /** minimum zoom level for TMS imagery */ 166 private int defaultMinZoom; 167 /** display bounds of imagery, displayed in prefs and used for automatic imagery selection */ 168 private ImageryBounds bounds; 169 /** projections supported by WMS servers */ 170 private List<String> serverProjections; 171 /** description of the imagery entry, should contain notes what type of data it is */ 172 private String description; 173 /** language of the description entry */ 174 private String langDescription; 175 /** Text of a text attribution displayed when using the imagery */ 176 private String attributionText; 177 /** Link behing the text attribution displayed when using the imagery */ 178 private String attributionLinkURL; 179 /** Image of a graphical attribution displayed when using the imagery */ 180 private String attributionImage; 181 /** Link behind the graphical attribution displayed when using the imagery */ 182 private String attributionImageURL; 183 /** Text with usage terms displayed when using the imagery */ 184 private String termsOfUseText; 185 /** Link behind the text with usage terms displayed when using the imagery */ 186 private String termsOfUseURL; 187 /** country code of the imagery (for country specific imagery) */ 188 private String countryCode = ""; 189 /** mirrors of different type for this entry */ 190 private List<ImageryInfo> mirrors; 191 /** icon used in menu */ 192 private String icon; 193 private boolean isGeoreferenceValid; 194 private boolean isEpsg4326To3857Supported; 195 // when adding a field, also adapt the ImageryInfo(ImageryInfo) 196 // and ImageryInfo(ImageryPreferenceEntry) constructor, equals method, and ImageryPreferenceEntry 197 198 /** 199 * Auxiliary class to save an {@link ImageryInfo} object in the preferences. 200 */ 201 public static class ImageryPreferenceEntry { 202 @pref String name; 203 @pref String id; 204 @pref String type; 205 @pref String url; 206 @pref double pixel_per_eastnorth; 207 @pref String eula; 208 @pref String attribution_text; 209 @pref String attribution_url; 210 @pref String logo_image; 211 @pref String logo_url; 212 @pref String terms_of_use_text; 213 @pref String terms_of_use_url; 214 @pref String country_code = ""; 215 @pref int max_zoom; 216 @pref int min_zoom; 217 @pref String cookies; 218 @pref String bounds; 219 @pref String shapes; 220 @pref String projections; 221 @pref String icon; 222 @pref String description; 223 @pref MultiMap<String, String> noTileHeaders; 224 @pref MultiMap<String, String> noTileChecksums; 225 @pref int tileSize = -1; 226 @pref Map<String, String> metadataHeaders; 227 @pref boolean valid_georeference; 228 @pref boolean supports_epsg_4326_to_3857_conversion; 229 230 /** 231 * Constructs a new empty WMS {@code ImageryPreferenceEntry}. 232 */ 233 public ImageryPreferenceEntry() { 234 } 235 236 /** 237 * Constructs a new {@code ImageryPreferenceEntry} from a given {@code ImageryInfo}. 238 * @param i The corresponding imagery info 239 */ 240 public ImageryPreferenceEntry(ImageryInfo i) { 241 name = i.name; 242 id = i.id; 243 type = i.imageryType.getTypeString(); 244 url = i.url; 245 pixel_per_eastnorth = i.pixelPerDegree; 246 eula = i.eulaAcceptanceRequired; 247 attribution_text = i.attributionText; 248 attribution_url = i.attributionLinkURL; 249 logo_image = i.attributionImage; 250 logo_url = i.attributionImageURL; 251 terms_of_use_text = i.termsOfUseText; 252 terms_of_use_url = i.termsOfUseURL; 253 country_code = i.countryCode; 254 max_zoom = i.defaultMaxZoom; 255 min_zoom = i.defaultMinZoom; 256 cookies = i.cookies; 257 icon = i.icon; 258 description = i.description; 259 if (i.bounds != null) { 260 bounds = i.bounds.encodeAsString(","); 261 StringBuilder shapesString = new StringBuilder(); 262 for (Shape s : i.bounds.getShapes()) { 263 if (shapesString.length() > 0) { 264 shapesString.append(';'); 265 } 266 shapesString.append(s.encodeAsString(",")); 267 } 268 if (shapesString.length() > 0) { 269 shapes = shapesString.toString(); 270 } 271 } 272 if (i.serverProjections != null && !i.serverProjections.isEmpty()) { 273 StringBuilder val = new StringBuilder(); 274 for (String p : i.serverProjections) { 275 if (val.length() > 0) { 276 val.append(','); 277 } 278 val.append(p); 279 } 280 projections = val.toString(); 281 } 282 if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) { 283 noTileHeaders = new MultiMap<>(i.noTileHeaders); 284 } 285 286 if (i.noTileChecksums != null && !i.noTileChecksums.isEmpty()) { 287 noTileChecksums = new MultiMap<>(i.noTileChecksums); 288 } 289 290 if (i.metadataHeaders != null && !i.metadataHeaders.isEmpty()) { 291 metadataHeaders = i.metadataHeaders; 292 } 293 294 tileSize = i.getTileSize(); 295 296 valid_georeference = i.isGeoreferenceValid(); 297 supports_epsg_4326_to_3857_conversion = i.isEpsg4326To3857Supported(); 298 299 } 300 301 @Override 302 public String toString() { 303 StringBuilder s = new StringBuilder("ImageryPreferenceEntry [name=").append(name); 304 if (id != null) { 305 s.append(" id=").append(id); 306 } 307 s.append(']'); 308 return s.toString(); 309 } 310 } 311 312 /** 313 * Constructs a new WMS {@code ImageryInfo}. 314 */ 315 public ImageryInfo() { 316 super(); 317 } 318 319 /** 320 * Constructs a new WMS {@code ImageryInfo} with a given name. 321 * @param name The entry name 322 */ 323 public ImageryInfo(String name) { 324 super(name); 325 } 326 327 /** 328 * Constructs a new WMS {@code ImageryInfo} with given name and extended URL. 329 * @param name The entry name 330 * @param url The entry extended URL 331 */ 332 public ImageryInfo(String name, String url) { 333 this(name); 334 setExtendedUrl(url); 335 } 336 337 /** 338 * Constructs a new WMS {@code ImageryInfo} with given name, extended and EULA URLs. 339 * @param name The entry name 340 * @param url The entry URL 341 * @param eulaAcceptanceRequired The EULA URL 342 */ 343 public ImageryInfo(String name, String url, String eulaAcceptanceRequired) { 344 this(name); 345 setExtendedUrl(url); 346 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 347 } 348 349 /** 350 * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs. 351 * @param name The entry name 352 * @param url The entry URL 353 * @param type The entry imagery type. If null, WMS will be used as default 354 * @param eulaAcceptanceRequired The EULA URL 355 * @param cookies The data part of HTTP cookies header in case the service requires cookies to work 356 * @throws IllegalArgumentException if type refers to an unknown imagery type 357 */ 358 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) { 359 this(name); 360 setExtendedUrl(url); 361 ImageryType t = ImageryType.fromString(type); 362 this.cookies = cookies; 363 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 364 if (t != null) { 365 this.imageryType = t; 366 } else if (type != null && !type.trim().isEmpty()) { 367 throw new IllegalArgumentException("unknown type: "+type); 368 } 369 } 370 371 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) { 372 this(name, url, type, eulaAcceptanceRequired, cookies); 373 setId(id); 374 } 375 376 /** 377 * Constructs a new {@code ImageryInfo} from an imagery preference entry. 378 * @param e The imagery preference entry 379 */ 380 public ImageryInfo(ImageryPreferenceEntry e) { 381 super(e.name, e.url, e.id); 382 CheckParameterUtil.ensureParameterNotNull(e.name, "name"); 383 CheckParameterUtil.ensureParameterNotNull(e.url, "url"); 384 description = e.description; 385 cookies = e.cookies; 386 eulaAcceptanceRequired = e.eula; 387 imageryType = ImageryType.fromString(e.type); 388 if (imageryType == null) throw new IllegalArgumentException("unknown type"); 389 pixelPerDegree = e.pixel_per_eastnorth; 390 defaultMaxZoom = e.max_zoom; 391 defaultMinZoom = e.min_zoom; 392 if (e.bounds != null) { 393 bounds = new ImageryBounds(e.bounds, ","); 394 if (e.shapes != null) { 395 try { 396 for (String s : e.shapes.split(";")) { 397 bounds.addShape(new Shape(s, ",")); 398 } 399 } catch (IllegalArgumentException ex) { 400 Main.warn(ex); 401 } 402 } 403 } 404 if (e.projections != null) { 405 serverProjections = Arrays.asList(e.projections.split(",")); 406 } 407 attributionText = e.attribution_text; 408 attributionLinkURL = e.attribution_url; 409 attributionImage = e.logo_image; 410 attributionImageURL = e.logo_url; 411 termsOfUseText = e.terms_of_use_text; 412 termsOfUseURL = e.terms_of_use_url; 413 countryCode = e.country_code; 414 icon = e.icon; 415 if (e.noTileHeaders != null) { 416 noTileHeaders = e.noTileHeaders.toMap(); 417 } 418 if (e.noTileChecksums != null) { 419 noTileChecksums = e.noTileChecksums.toMap(); 420 } 421 setTileSize(e.tileSize); 422 metadataHeaders = e.metadataHeaders; 423 isEpsg4326To3857Supported = e.supports_epsg_4326_to_3857_conversion; 424 isGeoreferenceValid = e.valid_georeference; 425 } 426 427 /** 428 * Constructs a new {@code ImageryInfo} from an existing one. 429 * @param i The other imagery info 430 */ 431 public ImageryInfo(ImageryInfo i) { 432 super(i.name, i.url, i.id); 433 this.defaultEntry = i.defaultEntry; 434 this.cookies = i.cookies; 435 this.eulaAcceptanceRequired = null; 436 this.imageryType = i.imageryType; 437 this.pixelPerDegree = i.pixelPerDegree; 438 this.defaultMaxZoom = i.defaultMaxZoom; 439 this.defaultMinZoom = i.defaultMinZoom; 440 this.bounds = i.bounds; 441 this.serverProjections = i.serverProjections; 442 this.attributionText = i.attributionText; 443 this.attributionLinkURL = i.attributionLinkURL; 444 this.attributionImage = i.attributionImage; 445 this.attributionImageURL = i.attributionImageURL; 446 this.termsOfUseText = i.termsOfUseText; 447 this.termsOfUseURL = i.termsOfUseURL; 448 this.countryCode = i.countryCode; 449 this.icon = i.icon; 450 this.description = i.description; 451 this.noTileHeaders = i.noTileHeaders; 452 this.noTileChecksums = i.noTileChecksums; 453 this.metadataHeaders = i.metadataHeaders; 454 this.isEpsg4326To3857Supported = i.isEpsg4326To3857Supported; 455 this.isGeoreferenceValid = i.isGeoreferenceValid; 456 } 457 458 @Override 459 public int hashCode() { 460 return Objects.hash(url, imageryType); 461 } 462 463 /** 464 * Check if this object equals another ImageryInfo with respect to the properties 465 * that get written to the preference file. 466 * 467 * The field {@link #pixelPerDegree} is ignored. 468 * 469 * @param other the ImageryInfo object to compare to 470 * @return true if they are equal 471 */ 472 public boolean equalsPref(ImageryInfo other) { 473 if (other == null) { 474 return false; 475 } 476 477 return 478 Objects.equals(this.name, other.name) && 479 Objects.equals(this.id, other.id) && 480 Objects.equals(this.url, other.url) && 481 Objects.equals(this.cookies, other.cookies) && 482 Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired) && 483 Objects.equals(this.imageryType, other.imageryType) && 484 Objects.equals(this.defaultMaxZoom, other.defaultMaxZoom) && 485 Objects.equals(this.defaultMinZoom, other.defaultMinZoom) && 486 Objects.equals(this.bounds, other.bounds) && 487 Objects.equals(this.serverProjections, other.serverProjections) && 488 Objects.equals(this.attributionText, other.attributionText) && 489 Objects.equals(this.attributionLinkURL, other.attributionLinkURL) && 490 Objects.equals(this.attributionImageURL, other.attributionImageURL) && 491 Objects.equals(this.attributionImage, other.attributionImage) && 492 Objects.equals(this.termsOfUseText, other.termsOfUseText) && 493 Objects.equals(this.termsOfUseURL, other.termsOfUseURL) && 494 Objects.equals(this.countryCode, other.countryCode) && 495 Objects.equals(this.icon, other.icon) && 496 Objects.equals(this.description, other.description) && 497 Objects.equals(this.noTileHeaders, other.noTileHeaders) && 498 Objects.equals(this.noTileChecksums, other.noTileChecksums) && 499 Objects.equals(this.metadataHeaders, other.metadataHeaders); 500 } 501 502 @Override 503 public boolean equals(Object o) { 504 if (this == o) return true; 505 if (o == null || getClass() != o.getClass()) return false; 506 ImageryInfo that = (ImageryInfo) o; 507 return imageryType == that.imageryType && Objects.equals(url, that.url); 508 } 509 510 @Override 511 public String toString() { 512 return "ImageryInfo{" + 513 "name='" + name + '\'' + 514 ", countryCode='" + countryCode + '\'' + 515 ", url='" + url + '\'' + 516 ", imageryType=" + imageryType + 517 '}'; 518 } 519 520 @Override 521 public int compareTo(ImageryInfo in) { 522 int i = countryCode.compareTo(in.countryCode); 523 if (i == 0) { 524 i = name.toLowerCase(Locale.ENGLISH).compareTo(in.name.toLowerCase(Locale.ENGLISH)); 525 } 526 if (i == 0) { 527 i = url.compareTo(in.url); 528 } 529 if (i == 0) { 530 i = Double.compare(pixelPerDegree, in.pixelPerDegree); 531 } 532 return i; 533 } 534 535 public boolean equalsBaseValues(ImageryInfo in) { 536 return url.equals(in.url); 537 } 538 539 public void setPixelPerDegree(double ppd) { 540 this.pixelPerDegree = ppd; 541 } 542 543 /** 544 * Sets the maximum zoom level. 545 * @param defaultMaxZoom The maximum zoom level 546 */ 547 public void setDefaultMaxZoom(int defaultMaxZoom) { 548 this.defaultMaxZoom = defaultMaxZoom; 549 } 550 551 /** 552 * Sets the minimum zoom level. 553 * @param defaultMinZoom The minimum zoom level 554 */ 555 public void setDefaultMinZoom(int defaultMinZoom) { 556 this.defaultMinZoom = defaultMinZoom; 557 } 558 559 /** 560 * Sets the imagery polygonial bounds. 561 * @param b The imagery bounds (non-rectangular) 562 */ 563 public void setBounds(ImageryBounds b) { 564 this.bounds = b; 565 } 566 567 /** 568 * Returns the imagery polygonial bounds. 569 * @return The imagery bounds (non-rectangular) 570 */ 571 public ImageryBounds getBounds() { 572 return bounds; 573 } 574 575 @Override 576 public boolean requiresAttribution() { 577 return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null; 578 } 579 580 @Override 581 public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) { 582 return attributionText; 583 } 584 585 @Override 586 public String getAttributionLinkURL() { 587 return attributionLinkURL; 588 } 589 590 @Override 591 public Image getAttributionImage() { 592 ImageIcon i = ImageProvider.getIfAvailable(attributionImage); 593 if (i != null) { 594 return i.getImage(); 595 } 596 return null; 597 } 598 599 @Override 600 public String getAttributionImageURL() { 601 return attributionImageURL; 602 } 603 604 @Override 605 public String getTermsOfUseText() { 606 return termsOfUseText; 607 } 608 609 @Override 610 public String getTermsOfUseURL() { 611 return termsOfUseURL; 612 } 613 614 public void setAttributionText(String text) { 615 attributionText = text; 616 } 617 618 public void setAttributionImageURL(String text) { 619 attributionImageURL = text; 620 } 621 622 public void setAttributionImage(String text) { 623 attributionImage = text; 624 } 625 626 public void setAttributionLinkURL(String text) { 627 attributionLinkURL = text; 628 } 629 630 public void setTermsOfUseText(String text) { 631 termsOfUseText = text; 632 } 633 634 public void setTermsOfUseURL(String text) { 635 termsOfUseURL = text; 636 } 637 638 /** 639 * Sets the extended URL of this entry. 640 * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info 641 */ 642 public void setExtendedUrl(String url) { 643 CheckParameterUtil.ensureParameterNotNull(url); 644 645 // Default imagery type is WMS 646 this.url = url; 647 this.imageryType = ImageryType.WMS; 648 649 defaultMaxZoom = 0; 650 defaultMinZoom = 0; 651 for (ImageryType type : ImageryType.values()) { 652 Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url); 653 if (m.matches()) { 654 this.url = m.group(3); 655 this.imageryType = type; 656 if (m.group(2) != null) { 657 defaultMaxZoom = Integer.parseInt(m.group(2)); 658 } 659 if (m.group(1) != null) { 660 defaultMinZoom = Integer.parseInt(m.group(1)); 661 } 662 break; 663 } 664 } 665 666 if (serverProjections == null || serverProjections.isEmpty()) { 667 try { 668 serverProjections = new ArrayList<>(); 669 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase(Locale.ENGLISH)); 670 if (m.matches()) { 671 for (String p : m.group(1).split(",")) { 672 serverProjections.add(p); 673 } 674 } 675 } catch (Exception e) { 676 Main.warn(e); 677 } 678 } 679 } 680 681 /** 682 * Returns the entry name. 683 * @return The entry name 684 * @since 6968 685 */ 686 public String getOriginalName() { 687 return this.origName != null ? this.origName : this.name; 688 } 689 690 /** 691 * Sets the entry name and handle translation. 692 * @param language The used language 693 * @param name The entry name 694 * @since 8091 695 */ 696 public void setName(String language, String name) { 697 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language); 698 if (LanguageInfo.isBetterLanguage(langName, language)) { 699 this.name = isdefault ? tr(name) : name; 700 this.langName = language; 701 } 702 if (origName == null || isdefault) { 703 this.origName = name; 704 } 705 } 706 707 public void clearId() { 708 if (this.id != null) { 709 Collection<String> newAddedIds = new TreeSet<>(Main.pref.getCollection("imagery.layers.addedIds")); 710 newAddedIds.add(this.id); 711 Main.pref.putCollection("imagery.layers.addedIds", newAddedIds); 712 } 713 setId(null); 714 } 715 716 /** 717 * Determines if this entry is enabled by default. 718 * @return {@code true} if this entry is enabled by default, {@code false} otherwise 719 */ 720 public boolean isDefaultEntry() { 721 return defaultEntry; 722 } 723 724 /** 725 * Sets the default state of this entry. 726 * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise 727 */ 728 public void setDefaultEntry(boolean defaultEntry) { 729 this.defaultEntry = defaultEntry; 730 } 731 732 /** 733 * Return the data part of HTTP cookies header in case the service requires cookies to work 734 * @return the cookie data part 735 */ 736 @Override 737 public String getCookies() { 738 return this.cookies; 739 } 740 741 public double getPixelPerDegree() { 742 return this.pixelPerDegree; 743 } 744 745 /** 746 * Returns the maximum zoom level. 747 * @return The maximum zoom level 748 */ 749 @Override 750 public int getMaxZoom() { 751 return this.defaultMaxZoom; 752 } 753 754 /** 755 * Returns the minimum zoom level. 756 * @return The minimum zoom level 757 */ 758 @Override 759 public int getMinZoom() { 760 return this.defaultMinZoom; 761 } 762 763 /** 764 * Returns the description text when existing. 765 * @return The description 766 * @since 8065 767 */ 768 public String getDescription() { 769 return this.description; 770 } 771 772 /** 773 * Sets the description text when existing. 774 * @param language The used language 775 * @param description the imagery description text 776 * @since 8091 777 */ 778 public void setDescription(String language, String description) { 779 boolean isdefault = LanguageInfo.getJOSMLocaleCode(null).equals(language); 780 if (LanguageInfo.isBetterLanguage(langDescription, language)) { 781 this.description = isdefault ? tr(description) : description; 782 this.langDescription = language; 783 } 784 } 785 786 /** 787 * Returns a tool tip text for display. 788 * @return The text 789 * @since 8065 790 */ 791 public String getToolTipText() { 792 String desc = getDescription(); 793 if (desc != null && !desc.isEmpty()) { 794 return "<html>" + getName() + "<br>" + desc + "</html>"; 795 } 796 return getName(); 797 } 798 799 /** 800 * Returns the EULA acceptance URL, if any. 801 * @return The URL to an EULA text that has to be accepted before use, or {@code null} 802 */ 803 public String getEulaAcceptanceRequired() { 804 return eulaAcceptanceRequired; 805 } 806 807 /** 808 * Sets the EULA acceptance URL. 809 * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use 810 */ 811 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) { 812 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 813 } 814 815 /** 816 * Returns the ISO 3166-1-alpha-2 country code. 817 * @return The country code (2 letters) 818 */ 819 public String getCountryCode() { 820 return countryCode; 821 } 822 823 /** 824 * Sets the ISO 3166-1-alpha-2 country code. 825 * @param countryCode The country code (2 letters) 826 */ 827 public void setCountryCode(String countryCode) { 828 this.countryCode = countryCode; 829 } 830 831 /** 832 * Returns the entry icon. 833 * @return The entry icon 834 */ 835 public String getIcon() { 836 return icon; 837 } 838 839 /** 840 * Sets the entry icon. 841 * @param icon The entry icon 842 */ 843 public void setIcon(String icon) { 844 this.icon = icon; 845 } 846 847 /** 848 * Get the projections supported by the server. Only relevant for 849 * WMS-type ImageryInfo at the moment. 850 * @return null, if no projections have been specified; the list 851 * of supported projections otherwise. 852 */ 853 public List<String> getServerProjections() { 854 if (serverProjections == null) 855 return Collections.emptyList(); 856 return Collections.unmodifiableList(serverProjections); 857 } 858 859 public void setServerProjections(Collection<String> serverProjections) { 860 this.serverProjections = new ArrayList<>(serverProjections); 861 } 862 863 /** 864 * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info. 865 * @return The extended URL 866 */ 867 public String getExtendedUrl() { 868 return imageryType.getTypeString() + (defaultMaxZoom != 0 869 ? "["+(defaultMinZoom != 0 ? Integer.toString(defaultMinZoom) + ',' : "")+defaultMaxZoom+"]" : "") + ':' + url; 870 } 871 872 public String getToolbarName() { 873 String res = name; 874 if (pixelPerDegree != 0) { 875 res += "#PPD="+pixelPerDegree; 876 } 877 return res; 878 } 879 880 public String getMenuName() { 881 String res = name; 882 if (pixelPerDegree != 0) { 883 res += " ("+pixelPerDegree+')'; 884 } 885 return res; 886 } 887 888 /** 889 * Determines if this entry requires attribution. 890 * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise 891 */ 892 public boolean hasAttribution() { 893 return attributionText != null; 894 } 895 896 /** 897 * Copies attribution from another {@code ImageryInfo}. 898 * @param i The other imagery info to get attribution from 899 */ 900 public void copyAttribution(ImageryInfo i) { 901 this.attributionImage = i.attributionImage; 902 this.attributionImageURL = i.attributionImageURL; 903 this.attributionText = i.attributionText; 904 this.attributionLinkURL = i.attributionLinkURL; 905 this.termsOfUseText = i.termsOfUseText; 906 this.termsOfUseURL = i.termsOfUseURL; 907 } 908 909 /** 910 * Applies the attribution from this object to a tile source. 911 * @param s The tile source 912 */ 913 public void setAttribution(AbstractTileSource s) { 914 if (attributionText != null) { 915 if ("osm".equals(attributionText)) { 916 s.setAttributionText(new Mapnik().getAttributionText(0, null, null)); 917 } else { 918 s.setAttributionText(attributionText); 919 } 920 } 921 if (attributionLinkURL != null) { 922 if ("osm".equals(attributionLinkURL)) { 923 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL()); 924 } else { 925 s.setAttributionLinkURL(attributionLinkURL); 926 } 927 } 928 if (attributionImage != null) { 929 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage); 930 if (i != null) { 931 s.setAttributionImage(i.getImage()); 932 } 933 } 934 if (attributionImageURL != null) { 935 s.setAttributionImageURL(attributionImageURL); 936 } 937 if (termsOfUseText != null) { 938 s.setTermsOfUseText(termsOfUseText); 939 } 940 if (termsOfUseURL != null) { 941 if ("osm".equals(termsOfUseURL)) { 942 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL()); 943 } else { 944 s.setTermsOfUseURL(termsOfUseURL); 945 } 946 } 947 } 948 949 /** 950 * Returns the imagery type. 951 * @return The imagery type 952 */ 953 public ImageryType getImageryType() { 954 return imageryType; 955 } 956 957 /** 958 * Sets the imagery type. 959 * @param imageryType The imagery type 960 */ 961 public void setImageryType(ImageryType imageryType) { 962 this.imageryType = imageryType; 963 } 964 965 /** 966 * Returns true if this layer's URL is matched by one of the regular 967 * expressions kept by the current OsmApi instance. 968 * @return {@code true} is this entry is blacklisted, {@code false} otherwise 969 */ 970 public boolean isBlacklisted() { 971 Capabilities capabilities = OsmApi.getOsmApi().getCapabilities(); 972 return capabilities != null && capabilities.isOnImageryBlacklist(this.url); 973 } 974 975 /** 976 * Sets the map of <header name, header value> that if any of this header 977 * will be returned, then this tile will be treated as "no tile at this zoom level" 978 * 979 * @param noTileHeaders Map of <header name, header value> which will be treated as "no tile at this zoom level" 980 * @since 9613 981 */ 982 public void setNoTileHeaders(MultiMap<String, String> noTileHeaders) { 983 if (noTileHeaders == null) { 984 this.noTileHeaders = null; 985 } else { 986 this.noTileHeaders = noTileHeaders.toMap(); 987 } 988 } 989 990 @Override 991 public Map<String, Set<String>> getNoTileHeaders() { 992 return noTileHeaders; 993 } 994 995 /** 996 * Sets the map of <checksum type, checksum value> that if any tile with that checksum 997 * will be returned, then this tile will be treated as "no tile at this zoom level" 998 * 999 * @param noTileChecksums Map of <checksum type, checksum value> which will be treated as "no tile at this zoom level" 1000 * @since 9613 1001 */ 1002 public void setNoTileChecksums(MultiMap<String, String> noTileChecksums) { 1003 if (noTileChecksums == null) { 1004 this.noTileChecksums = null; 1005 } else { 1006 this.noTileChecksums = noTileChecksums.toMap(); 1007 } 1008 } 1009 1010 @Override 1011 public Map<String, Set<String>> getNoTileChecksums() { 1012 return noTileChecksums; 1013 } 1014 1015 /** 1016 * Returns the map of <header name, metadata key> indicating, which HTTP headers should 1017 * be moved to metadata 1018 * 1019 * @param metadataHeaders map of <header name, metadata key> indicating, which HTTP headers should be moved to metadata 1020 * @since 8418 1021 */ 1022 public void setMetadataHeaders(Map<String, String> metadataHeaders) { 1023 this.metadataHeaders = metadataHeaders; 1024 } 1025 1026 public boolean isEpsg4326To3857Supported() { 1027 return isEpsg4326To3857Supported; 1028 } 1029 1030 public void setEpsg4326To3857Supported(boolean isEpsg4326To3857Supported) { 1031 this.isEpsg4326To3857Supported = isEpsg4326To3857Supported; 1032 } 1033 1034 public boolean isGeoreferenceValid() { 1035 return isGeoreferenceValid; 1036 } 1037 1038 public void setGeoreferenceValid(boolean isGeoreferenceValid) { 1039 this.isGeoreferenceValid = isGeoreferenceValid; 1040 } 1041 1042 /** 1043 * Adds a mirror entry. Mirror entries are completed with the data from the master entry 1044 * and only describe another method to access identical data. 1045 * 1046 * @param entry the mirror to be added 1047 * @since 9658 1048 */ 1049 public void addMirror(ImageryInfo entry) { 1050 if (mirrors == null) { 1051 mirrors = new ArrayList<>(); 1052 } 1053 mirrors.add(entry); 1054 } 1055 1056 /** 1057 * Returns the mirror entries. Entries are completed with master entry data. 1058 * 1059 * @return the list of mirrors 1060 * @since 9658 1061 */ 1062 public List<ImageryInfo> getMirrors() { 1063 List<ImageryInfo> l = new ArrayList<>(); 1064 if (mirrors != null) { 1065 for (ImageryInfo i : mirrors) { 1066 ImageryInfo n = new ImageryInfo(this); 1067 if (i.defaultMaxZoom != 0) { 1068 n.defaultMaxZoom = i.defaultMaxZoom; 1069 } 1070 if (i.defaultMinZoom != 0) { 1071 n.defaultMinZoom = i.defaultMinZoom; 1072 } 1073 if (i.serverProjections != null) { 1074 n.serverProjections = i.serverProjections; 1075 } 1076 n.url = i.url; 1077 n.imageryType = i.imageryType; 1078 if (i.getTileSize() != 0) { 1079 n.setTileSize(i.getTileSize()); 1080 } 1081 l.add(n); 1082 } 1083 } 1084 return l; 1085 } 1086}