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