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