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 /** 304 * Constructs a new {@code ImageryInfo} with given name, url, extended and EULA URLs. 305 * @param name The entry name 306 * @param url The entry URL 307 * @param type The entry imagery type. If null, WMS will be used as default 308 * @param eulaAcceptanceRequired The EULA URL 309 * @throws IllegalArgumentException if type refers to an unknown imagery type 310 */ 311 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) { 312 this.name=name; 313 setExtendedUrl(url); 314 ImageryType t = ImageryType.fromString(type); 315 this.cookies=cookies; 316 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 317 if (t != null) { 318 this.imageryType = t; 319 } else if (type != null && !type.trim().isEmpty()) { 320 throw new IllegalArgumentException("unknown type: "+type); 321 } 322 } 323 324 /** 325 * Constructs a new {@code ImageryInfo} from an imagery preference entry. 326 * @param e The imagery preference entry 327 */ 328 public ImageryInfo(ImageryPreferenceEntry e) { 329 CheckParameterUtil.ensureParameterNotNull(e.name, "name"); 330 CheckParameterUtil.ensureParameterNotNull(e.url, "url"); 331 name = e.name; 332 id = e.id; 333 url = e.url; 334 cookies = e.cookies; 335 eulaAcceptanceRequired = e.eula; 336 imageryType = ImageryType.fromString(e.type); 337 if (imageryType == null) throw new IllegalArgumentException("unknown type"); 338 pixelPerDegree = e.pixel_per_eastnorth; 339 defaultMaxZoom = e.max_zoom; 340 defaultMinZoom = e.min_zoom; 341 if (e.bounds != null) { 342 bounds = new ImageryBounds(e.bounds, ","); 343 if (e.shapes != null) { 344 try { 345 for (String s : e.shapes.split(";")) { 346 bounds.addShape(new Shape(s, ",")); 347 } 348 } catch (IllegalArgumentException ex) { 349 Main.warn(ex); 350 } 351 } 352 } 353 if (e.projections != null) { 354 serverProjections = Arrays.asList(e.projections.split(",")); 355 } 356 attributionText = e.attribution_text; 357 attributionLinkURL = e.attribution_url; 358 attributionImage = e.logo_image; 359 attributionImageURL = e.logo_url; 360 termsOfUseText = e.terms_of_use_text; 361 termsOfUseURL = e.terms_of_use_url; 362 countryCode = e.country_code; 363 icon = e.icon; 364 } 365 366 /** 367 * Constructs a new {@code ImageryInfo} from an existing one. 368 * @param i The other imagery info 369 */ 370 public ImageryInfo(ImageryInfo i) { 371 this.name = i.name; 372 this.id = i.id; 373 this.url = i.url; 374 this.defaultEntry = i.defaultEntry; 375 this.cookies = i.cookies; 376 this.eulaAcceptanceRequired = null; 377 this.imageryType = i.imageryType; 378 this.pixelPerDegree = i.pixelPerDegree; 379 this.defaultMaxZoom = i.defaultMaxZoom; 380 this.defaultMinZoom = i.defaultMinZoom; 381 this.bounds = i.bounds; 382 this.serverProjections = i.serverProjections; 383 this.attributionText = i.attributionText; 384 this.attributionLinkURL = i.attributionLinkURL; 385 this.attributionImage = i.attributionImage; 386 this.attributionImageURL = i.attributionImageURL; 387 this.termsOfUseText = i.termsOfUseText; 388 this.termsOfUseURL = i.termsOfUseURL; 389 this.countryCode = i.countryCode; 390 this.icon = i.icon; 391 } 392 393 @Override 394 public boolean equals(Object o) { 395 if (this == o) return true; 396 if (o == null || getClass() != o.getClass()) return false; 397 398 ImageryInfo that = (ImageryInfo) o; 399 400 if (imageryType != that.imageryType) return false; 401 if (url != null ? !url.equals(that.url) : that.url != null) return false; 402 if (name != null ? !name.equals(that.name) : that.name != null) return false; 403 404 return true; 405 } 406 407 /** 408 * Check if this object equals another ImageryInfo with respect to the properties 409 * that get written to the preference file. 410 * 411 * The field {@link #pixelPerDegree} is ignored. 412 * 413 * @param other the ImageryInfo object to compare to 414 * @return true if they are equal 415 */ 416 public boolean equalsPref(ImageryInfo other) { 417 if (other == null) { 418 return false; 419 } 420 if (!Objects.equals(this.name, other.name)) { 421 return false; 422 } 423 if (!Objects.equals(this.id, other.id)) { 424 return false; 425 } 426 if (!Objects.equals(this.url, other.url)) { 427 return false; 428 } 429 if (!Objects.equals(this.cookies, other.cookies)) { 430 return false; 431 } 432 if (!Objects.equals(this.eulaAcceptanceRequired, other.eulaAcceptanceRequired)) { 433 return false; 434 } 435 if (this.imageryType != other.imageryType) { 436 return false; 437 } 438 if (this.defaultMaxZoom != other.defaultMaxZoom) { 439 return false; 440 } 441 if (this.defaultMinZoom != other.defaultMinZoom) { 442 return false; 443 } 444 if (!Objects.equals(this.bounds, other.bounds)) { 445 return false; 446 } 447 if (!Objects.equals(this.serverProjections, other.serverProjections)) { 448 return false; 449 } 450 if (!Objects.equals(this.attributionText, other.attributionText)) { 451 return false; 452 } 453 if (!Objects.equals(this.attributionLinkURL, other.attributionLinkURL)) { 454 return false; 455 } 456 if (!Objects.equals(this.attributionImage, other.attributionImage)) { 457 return false; 458 } 459 if (!Objects.equals(this.attributionImageURL, other.attributionImageURL)) { 460 return false; 461 } 462 if (!Objects.equals(this.termsOfUseText, other.termsOfUseText)) { 463 return false; 464 } 465 if (!Objects.equals(this.termsOfUseURL, other.termsOfUseURL)) { 466 return false; 467 } 468 if (!Objects.equals(this.countryCode, other.countryCode)) { 469 return false; 470 } 471 if (!Objects.equals(this.icon, other.icon)) { 472 return false; 473 } 474 return true; 475 } 476 477 478 @Override 479 public int hashCode() { 480 int result = url != null ? url.hashCode() : 0; 481 result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0); 482 return result; 483 } 484 485 @Override 486 public String toString() { 487 return "ImageryInfo{" + 488 "name='" + name + '\'' + 489 ", countryCode='" + countryCode + '\'' + 490 ", url='" + url + '\'' + 491 ", imageryType=" + imageryType + 492 '}'; 493 } 494 495 @Override 496 public int compareTo(ImageryInfo in) { 497 int i = countryCode.compareTo(in.countryCode); 498 if (i == 0) { 499 i = name.toLowerCase().compareTo(in.name.toLowerCase()); 500 } 501 if (i == 0) { 502 i = url.compareTo(in.url); 503 } 504 if (i == 0) { 505 i = Double.compare(pixelPerDegree, in.pixelPerDegree); 506 } 507 return i; 508 } 509 510 public boolean equalsBaseValues(ImageryInfo in) { 511 return url.equals(in.url); 512 } 513 514 public void setPixelPerDegree(double ppd) { 515 this.pixelPerDegree = ppd; 516 } 517 518 /** 519 * Sets the maximum zoom level. 520 * @param defaultMaxZoom The maximum zoom level 521 */ 522 public void setDefaultMaxZoom(int defaultMaxZoom) { 523 this.defaultMaxZoom = defaultMaxZoom; 524 } 525 526 /** 527 * Sets the minimum zoom level. 528 * @param defaultMinZoom The minimum zoom level 529 */ 530 public void setDefaultMinZoom(int defaultMinZoom) { 531 this.defaultMinZoom = defaultMinZoom; 532 } 533 534 /** 535 * Sets the imagery polygonial bounds. 536 * @param b The imagery bounds (non-rectangular) 537 */ 538 public void setBounds(ImageryBounds b) { 539 this.bounds = b; 540 } 541 542 /** 543 * Returns the imagery polygonial bounds. 544 * @return The imagery bounds (non-rectangular) 545 */ 546 public ImageryBounds getBounds() { 547 return bounds; 548 } 549 550 @Override 551 public boolean requiresAttribution() { 552 return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null; 553 } 554 555 @Override 556 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) { 557 return attributionText; 558 } 559 560 @Override 561 public String getAttributionLinkURL() { 562 return attributionLinkURL; 563 } 564 565 @Override 566 public Image getAttributionImage() { 567 ImageIcon i = ImageProvider.getIfAvailable(attributionImage); 568 if (i != null) { 569 return i.getImage(); 570 } 571 return null; 572 } 573 574 @Override 575 public String getAttributionImageURL() { 576 return attributionImageURL; 577 } 578 579 @Override 580 public String getTermsOfUseText() { 581 return termsOfUseText; 582 } 583 584 @Override 585 public String getTermsOfUseURL() { 586 return termsOfUseURL; 587 } 588 589 public void setAttributionText(String text) { 590 attributionText = text; 591 } 592 593 public void setAttributionImageURL(String text) { 594 attributionImageURL = text; 595 } 596 597 public void setAttributionImage(String text) { 598 attributionImage = text; 599 } 600 601 public void setAttributionLinkURL(String text) { 602 attributionLinkURL = text; 603 } 604 605 public void setTermsOfUseText(String text) { 606 termsOfUseText = text; 607 } 608 609 public void setTermsOfUseURL(String text) { 610 termsOfUseURL = text; 611 } 612 613 /** 614 * Sets the extended URL of this entry. 615 * @param url Entry extended URL containing in addition of service URL, its type and min/max zoom info 616 */ 617 public void setExtendedUrl(String url) { 618 CheckParameterUtil.ensureParameterNotNull(url); 619 620 // Default imagery type is WMS 621 this.url = url; 622 this.imageryType = ImageryType.WMS; 623 624 defaultMaxZoom = 0; 625 defaultMinZoom = 0; 626 for (ImageryType type : ImageryType.values()) { 627 Matcher m = Pattern.compile(type.getTypeString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url); 628 if (m.matches()) { 629 this.url = m.group(3); 630 this.imageryType = type; 631 if (m.group(2) != null) { 632 defaultMaxZoom = Integer.valueOf(m.group(2)); 633 } 634 if (m.group(1) != null) { 635 defaultMinZoom = Integer.valueOf(m.group(1)); 636 } 637 break; 638 } 639 } 640 641 if (serverProjections == null || serverProjections.isEmpty()) { 642 try { 643 serverProjections = new ArrayList<>(); 644 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase()); 645 if(m.matches()) { 646 for(String p : m.group(1).split(",")) 647 serverProjections.add(p); 648 } 649 } catch (Exception e) { 650 Main.warn(e); 651 } 652 } 653 } 654 655 /** 656 * Returns the entry name. 657 * @return The entry name 658 */ 659 public String getName() { 660 return this.name; 661 } 662 663 /** 664 * Returns the entry name. 665 * @return The entry name 666 * @since 6968 667 */ 668 public String getOriginalName() { 669 return this.origName != null ? this.origName : this.name; 670 } 671 672 /** 673 * Sets the entry name. 674 * @param name The entry name 675 */ 676 public void setName(String name) { 677 this.name = name; 678 } 679 680 /** 681 * Sets the entry name and translates it. 682 * @param name The entry name 683 * @since 6968 684 */ 685 public void setTranslatedName(String name) { 686 this.name = tr(name); 687 this.origName = name; 688 } 689 690 /** 691 * Gets the entry id. 692 * 693 * Id can be null. This gets the configured id as is. Due to a user error, 694 * this may not be unique. Use {@link ImageryLayerInfo#getUniqueId} to ensure 695 * a unique value. 696 * @return the id 697 */ 698 public String getId() { 699 return this.id; 700 } 701 702 /** 703 * Sets the entry id. 704 * @param id the entry id 705 */ 706 public void setId(String id) { 707 this.id = id; 708 } 709 710 public void clearId() { 711 if (this.id != null) { 712 Collection<String> newAddedIds = new TreeSet<>(Main.pref.getCollection("imagery.layers.addedIds")); 713 newAddedIds.add(this.id); 714 Main.pref.putCollection("imagery.layers.addedIds", newAddedIds); 715 } 716 this.id = null; 717 } 718 719 /** 720 * Returns the entry URL. 721 * @return The entry URL 722 */ 723 public String getUrl() { 724 return this.url; 725 } 726 727 /** 728 * Sets the entry URL. 729 * @param url The entry URL 730 */ 731 public void setUrl(String url) { 732 this.url = url; 733 } 734 735 /** 736 * Determines if this entry is enabled by default. 737 * @return {@code true} if this entry is enabled by default, {@code false} otherwise 738 */ 739 public boolean isDefaultEntry() { 740 return defaultEntry; 741 } 742 743 /** 744 * Sets the default state of this entry. 745 * @param defaultEntry {@code true} if this entry has to be enabled by default, {@code false} otherwise 746 */ 747 public void setDefaultEntry(boolean defaultEntry) { 748 this.defaultEntry = defaultEntry; 749 } 750 751 public String getCookies() { 752 return this.cookies; 753 } 754 755 public double getPixelPerDegree() { 756 return this.pixelPerDegree; 757 } 758 759 /** 760 * Returns the maximum zoom level. 761 * @return The maximum zoom level 762 */ 763 public int getMaxZoom() { 764 return this.defaultMaxZoom; 765 } 766 767 /** 768 * Returns the minimum zoom level. 769 * @return The minimum zoom level 770 */ 771 public int getMinZoom() { 772 return this.defaultMinZoom; 773 } 774 775 /** 776 * Returns the EULA acceptance URL, if any. 777 * @return The URL to an EULA text that has to be accepted before use, or {@code null} 778 */ 779 public String getEulaAcceptanceRequired() { 780 return eulaAcceptanceRequired; 781 } 782 783 /** 784 * Sets the EULA acceptance URL. 785 * @param eulaAcceptanceRequired The URL to an EULA text that has to be accepted before use 786 */ 787 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) { 788 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 789 } 790 791 /** 792 * Returns the ISO 3166-1-alpha-2 country code. 793 * @return The country code (2 letters) 794 */ 795 public String getCountryCode() { 796 return countryCode; 797 } 798 799 /** 800 * Sets the ISO 3166-1-alpha-2 country code. 801 * @param countryCode The country code (2 letters) 802 */ 803 public void setCountryCode(String countryCode) { 804 this.countryCode = countryCode; 805 } 806 807 /** 808 * Returns the entry icon. 809 * @return The entry icon 810 */ 811 public String getIcon() { 812 return icon; 813 } 814 815 /** 816 * Sets the entry icon. 817 * @param icon The entry icon 818 */ 819 public void setIcon(String icon) { 820 this.icon = icon; 821 } 822 823 /** 824 * Get the projections supported by the server. Only relevant for 825 * WMS-type ImageryInfo at the moment. 826 * @return null, if no projections have been specified; the list 827 * of supported projections otherwise. 828 */ 829 public List<String> getServerProjections() { 830 if (serverProjections == null) 831 return Collections.emptyList(); 832 return Collections.unmodifiableList(serverProjections); 833 } 834 835 public void setServerProjections(Collection<String> serverProjections) { 836 this.serverProjections = new ArrayList<>(serverProjections); 837 } 838 839 /** 840 * Returns the extended URL, containing in addition of service URL, its type and min/max zoom info. 841 * @return The extended URL 842 */ 843 public String getExtendedUrl() { 844 return imageryType.getTypeString() + (defaultMaxZoom != 0 845 ? "["+(defaultMinZoom != 0 ? defaultMinZoom+",":"")+defaultMaxZoom+"]" : "") + ":" + url; 846 } 847 848 public String getToolbarName() { 849 String res = name; 850 if(pixelPerDegree != 0.0) { 851 res += "#PPD="+pixelPerDegree; 852 } 853 return res; 854 } 855 856 public String getMenuName() { 857 String res = name; 858 if(pixelPerDegree != 0.0) { 859 res += " ("+pixelPerDegree+")"; 860 } 861 return res; 862 } 863 864 /** 865 * Determines if this entry requires attribution. 866 * @return {@code true} if some attribution text has to be displayed, {@code false} otherwise 867 */ 868 public boolean hasAttribution() { 869 return attributionText != null; 870 } 871 872 /** 873 * Copies attribution from another {@code ImageryInfo}. 874 * @param i The other imagery info to get attribution from 875 */ 876 public void copyAttribution(ImageryInfo i) { 877 this.attributionImage = i.attributionImage; 878 this.attributionImageURL = i.attributionImageURL; 879 this.attributionText = i.attributionText; 880 this.attributionLinkURL = i.attributionLinkURL; 881 this.termsOfUseText = i.termsOfUseText; 882 this.termsOfUseURL = i.termsOfUseURL; 883 } 884 885 /** 886 * Applies the attribution from this object to a tile source. 887 * @param s The tile source 888 */ 889 public void setAttribution(AbstractTileSource s) { 890 if (attributionText != null) { 891 if ("osm".equals(attributionText)) { 892 s.setAttributionText(new Mapnik().getAttributionText(0, null, null)); 893 } else { 894 s.setAttributionText(attributionText); 895 } 896 } 897 if (attributionLinkURL != null) { 898 if ("osm".equals(attributionLinkURL)) { 899 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL()); 900 } else { 901 s.setAttributionLinkURL(attributionLinkURL); 902 } 903 } 904 if (attributionImage != null) { 905 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage); 906 if (i != null) { 907 s.setAttributionImage(i.getImage()); 908 } 909 } 910 if (attributionImageURL != null) { 911 s.setAttributionImageURL(attributionImageURL); 912 } 913 if (termsOfUseText != null) { 914 s.setTermsOfUseText(termsOfUseText); 915 } 916 if (termsOfUseURL != null) { 917 if ("osm".equals(termsOfUseURL)) { 918 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL()); 919 } else { 920 s.setTermsOfUseURL(termsOfUseURL); 921 } 922 } 923 } 924 925 /** 926 * Returns the imagery type. 927 * @return The imagery type 928 */ 929 public ImageryType getImageryType() { 930 return imageryType; 931 } 932 933 /** 934 * Sets the imagery type. 935 * @param imageryType The imagery type 936 */ 937 public void setImageryType(ImageryType imageryType) { 938 this.imageryType = imageryType; 939 } 940 941 /** 942 * Returns true if this layer's URL is matched by one of the regular 943 * expressions kept by the current OsmApi instance. 944 * @return {@code true} is this entry is blacklisted, {@code false} otherwise 945 */ 946 public boolean isBlacklisted() { 947 Capabilities capabilities = OsmApi.getOsmApi().getCapabilities(); 948 return capabilities != null && capabilities.isOnImageryBlacklist(this.url); 949 } 950}