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