001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.awt.Image; 005import java.util.ArrayList; 006import java.util.Arrays; 007import java.util.Collection; 008import java.util.Collections; 009import java.util.List; 010import java.util.regex.Matcher; 011import java.util.regex.Pattern; 012 013import javax.swing.ImageIcon; 014 015import org.openstreetmap.gui.jmapviewer.Coordinate; 016import org.openstreetmap.gui.jmapviewer.interfaces.Attributed; 017import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource; 018import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik; 019import org.openstreetmap.josm.Main; 020import org.openstreetmap.josm.data.Bounds; 021import org.openstreetmap.josm.data.Preferences.pref; 022import org.openstreetmap.josm.io.OsmApi; 023import org.openstreetmap.josm.tools.CheckParameterUtil; 024import org.openstreetmap.josm.tools.ImageProvider; 025 026/** 027 * Class that stores info about an image background layer. 028 * 029 * @author Frederik Ramm <frederik@remote.org> 030 */ 031public class ImageryInfo implements Comparable<ImageryInfo>, Attributed { 032 033 public enum ImageryType { 034 WMS("wms"), 035 TMS("tms"), 036 HTML("html"), 037 BING("bing"), 038 SCANEX("scanex"), 039 WMS_ENDPOINT("wms_endpoint"); 040 041 private String urlString; 042 043 ImageryType(String urlString) { 044 this.urlString = urlString; 045 } 046 047 public String getUrlString() { 048 return urlString; 049 } 050 051 public static ImageryType fromUrlString(String s) { 052 for (ImageryType type : ImageryType.values()) { 053 if (type.getUrlString().equals(s)) { 054 return type; 055 } 056 } 057 return null; 058 } 059 } 060 061 public static class ImageryBounds extends Bounds { 062 public ImageryBounds(String asString, String separator) { 063 super(asString, separator); 064 } 065 066 private List<Shape> shapes = new ArrayList<Shape>(); 067 068 public void addShape(Shape shape) { 069 this.shapes.add(shape); 070 } 071 072 public void setShapes(List<Shape> shapes) { 073 this.shapes = shapes; 074 } 075 076 public List<Shape> getShapes() { 077 return shapes; 078 } 079 080 @Override 081 public int hashCode() { 082 final int prime = 31; 083 int result = super.hashCode(); 084 result = prime * result + ((shapes == null) ? 0 : shapes.hashCode()); 085 return result; 086 } 087 088 @Override 089 public boolean equals(Object obj) { 090 if (this == obj) 091 return true; 092 if (!super.equals(obj)) 093 return false; 094 if (getClass() != obj.getClass()) 095 return false; 096 ImageryBounds other = (ImageryBounds) obj; 097 if (shapes == null) { 098 if (other.shapes != null) 099 return false; 100 } else if (!shapes.equals(other.shapes)) 101 return false; 102 return true; 103 } 104 } 105 106 private String name; 107 private String url = null; 108 private boolean defaultEntry = false; 109 private String cookies = null; 110 private String eulaAcceptanceRequired= null; 111 private ImageryType imageryType = ImageryType.WMS; 112 private double pixelPerDegree = 0.0; 113 private int defaultMaxZoom = 0; 114 private int defaultMinZoom = 0; 115 private ImageryBounds bounds = null; 116 private List<String> serverProjections; 117 private String attributionText; 118 private String attributionLinkURL; 119 private String attributionImage; 120 private String attributionImageURL; 121 private String termsOfUseText; 122 private String termsOfUseURL; 123 private String countryCode = ""; 124 private String icon; 125 // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor 126 127 /** auxiliary class to save an ImageryInfo object in the preferences */ 128 public static class ImageryPreferenceEntry { 129 @pref String name; 130 @pref String type; 131 @pref String url; 132 @pref double pixel_per_eastnorth; 133 @pref String eula; 134 @pref String attribution_text; 135 @pref String attribution_url; 136 @pref String logo_image; 137 @pref String logo_url; 138 @pref String terms_of_use_text; 139 @pref String terms_of_use_url; 140 @pref String country_code = ""; 141 @pref int max_zoom; 142 @pref int min_zoom; 143 @pref String cookies; 144 @pref String bounds; 145 @pref String shapes; 146 @pref String projections; 147 @pref String icon; 148 149 /** 150 * Constructs a new {@code ImageryPreferenceEntry}. 151 */ 152 public ImageryPreferenceEntry() { 153 } 154 155 public ImageryPreferenceEntry(ImageryInfo i) { 156 name = i.name; 157 type = i.imageryType.getUrlString(); 158 url = i.url; 159 pixel_per_eastnorth = i.pixelPerDegree; 160 eula = i.eulaAcceptanceRequired; 161 attribution_text = i.attributionText; 162 attribution_url = i.attributionLinkURL; 163 logo_image = i.attributionImage; 164 logo_url = i.attributionImageURL; 165 terms_of_use_text = i.termsOfUseText; 166 terms_of_use_url = i.termsOfUseURL; 167 country_code = i.countryCode; 168 max_zoom = i.defaultMaxZoom; 169 min_zoom = i.defaultMinZoom; 170 cookies = i.cookies; 171 icon = i.icon; 172 if (i.bounds != null) { 173 bounds = i.bounds.encodeAsString(","); 174 StringBuilder shapesString = new StringBuilder(); 175 for (Shape s : i.bounds.getShapes()) { 176 if (shapesString.length() > 0) { 177 shapesString.append(";"); 178 } 179 shapesString.append(s.encodeAsString(",")); 180 } 181 if (shapesString.length() > 0) { 182 shapes = shapesString.toString(); 183 } 184 } 185 if (i.serverProjections != null && !i.serverProjections.isEmpty()) { 186 StringBuilder val = new StringBuilder(); 187 for (String p : i.serverProjections) { 188 if (val.length() > 0) { 189 val.append(","); 190 } 191 val.append(p); 192 } 193 projections = val.toString(); 194 } 195 } 196 197 @Override 198 public String toString() { 199 return "ImageryPreferenceEntry [name=" + name + "]"; 200 } 201 } 202 203 /** 204 * Constructs a new {@code ImageryInfo}. 205 */ 206 public ImageryInfo() { 207 } 208 209 public ImageryInfo(String name) { 210 this.name=name; 211 } 212 213 public ImageryInfo(String name, String url) { 214 this.name=name; 215 setExtendedUrl(url); 216 } 217 218 public ImageryInfo(String name, String url, String eulaAcceptanceRequired) { 219 this.name=name; 220 setExtendedUrl(url); 221 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 222 } 223 224 public ImageryInfo(String name, String url, String eulaAcceptanceRequired, String cookies) { 225 this.name=name; 226 setExtendedUrl(url); 227 this.cookies=cookies; 228 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 229 } 230 231 public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) { 232 this.name=name; 233 setExtendedUrl(url); 234 ImageryType t = ImageryType.fromUrlString(type); 235 this.cookies=cookies; 236 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 237 if (t != null) { 238 this.imageryType = t; 239 } 240 } 241 242 public ImageryInfo(String name, String url, String cookies, double pixelPerDegree) { 243 this.name=name; 244 setExtendedUrl(url); 245 this.cookies=cookies; 246 this.pixelPerDegree=pixelPerDegree; 247 } 248 249 public ImageryInfo(ImageryPreferenceEntry e) { 250 CheckParameterUtil.ensureParameterNotNull(e.name, "name"); 251 CheckParameterUtil.ensureParameterNotNull(e.url, "url"); 252 name = e.name; 253 url = e.url; 254 cookies = e.cookies; 255 eulaAcceptanceRequired = e.eula; 256 imageryType = ImageryType.fromUrlString(e.type); 257 if (imageryType == null) throw new IllegalArgumentException("unknown type"); 258 pixelPerDegree = e.pixel_per_eastnorth; 259 defaultMaxZoom = e.max_zoom; 260 defaultMinZoom = e.min_zoom; 261 if (e.bounds != null) { 262 bounds = new ImageryBounds(e.bounds, ","); 263 if (e.shapes != null) { 264 try { 265 for (String s : e.shapes.split(";")) { 266 bounds.addShape(new Shape(s, ",")); 267 } 268 } catch (IllegalArgumentException ex) { 269 Main.warn(ex); 270 } 271 } 272 } 273 if (e.projections != null) { 274 serverProjections = Arrays.asList(e.projections.split(",")); 275 } 276 attributionText = e.attribution_text; 277 attributionLinkURL = e.attribution_url; 278 attributionImage = e.logo_image; 279 attributionImageURL = e.logo_url; 280 termsOfUseText = e.terms_of_use_text; 281 termsOfUseURL = e.terms_of_use_url; 282 countryCode = e.country_code; 283 icon = e.icon; 284 } 285 286 public ImageryInfo(ImageryInfo i) { 287 this.name = i.name; 288 this.url = i.url; 289 this.defaultEntry = i.defaultEntry; 290 this.cookies = i.cookies; 291 this.eulaAcceptanceRequired = null; 292 this.imageryType = i.imageryType; 293 this.pixelPerDegree = i.pixelPerDegree; 294 this.defaultMaxZoom = i.defaultMaxZoom; 295 this.defaultMinZoom = i.defaultMinZoom; 296 this.bounds = i.bounds; 297 this.serverProjections = i.serverProjections; 298 this.attributionText = i.attributionText; 299 this.attributionLinkURL = i.attributionLinkURL; 300 this.attributionImage = i.attributionImage; 301 this.attributionImageURL = i.attributionImageURL; 302 this.termsOfUseText = i.termsOfUseText; 303 this.termsOfUseURL = i.termsOfUseURL; 304 this.countryCode = i.countryCode; 305 this.icon = i.icon; 306 } 307 308 @Override 309 public boolean equals(Object o) { 310 if (this == o) return true; 311 if (o == null || getClass() != o.getClass()) return false; 312 313 ImageryInfo that = (ImageryInfo) o; 314 315 if (imageryType != that.imageryType) return false; 316 if (url != null ? !url.equals(that.url) : that.url != null) return false; 317 if (name != null ? !name.equals(that.name) : that.name != null) return false; 318 319 return true; 320 } 321 322 @Override 323 public int hashCode() { 324 int result = url != null ? url.hashCode() : 0; 325 result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0); 326 return result; 327 } 328 329 @Override 330 public String toString() { 331 return "ImageryInfo{" + 332 "name='" + name + '\'' + 333 ", countryCode='" + countryCode + '\'' + 334 ", url='" + url + '\'' + 335 ", imageryType=" + imageryType + 336 '}'; 337 } 338 339 @Override 340 public int compareTo(ImageryInfo in) 341 { 342 int i = countryCode.compareTo(in.countryCode); 343 if (i == 0) { 344 i = name.compareTo(in.name); 345 } 346 if (i == 0) { 347 i = url.compareTo(in.url); 348 } 349 if (i == 0) { 350 i = Double.compare(pixelPerDegree, in.pixelPerDegree); 351 } 352 return i; 353 } 354 355 public boolean equalsBaseValues(ImageryInfo in) 356 { 357 return url.equals(in.url); 358 } 359 360 public void setPixelPerDegree(double ppd) { 361 this.pixelPerDegree = ppd; 362 } 363 364 public void setDefaultMaxZoom(int defaultMaxZoom) { 365 this.defaultMaxZoom = defaultMaxZoom; 366 } 367 368 public void setDefaultMinZoom(int defaultMinZoom) { 369 this.defaultMinZoom = defaultMinZoom; 370 } 371 372 public void setBounds(ImageryBounds b) { 373 this.bounds = b; 374 } 375 376 public ImageryBounds getBounds() { 377 return bounds; 378 } 379 380 @Override 381 public boolean requiresAttribution() { 382 return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null; 383 } 384 385 @Override 386 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) { 387 return attributionText; 388 } 389 390 @Override 391 public String getAttributionLinkURL() { 392 return attributionLinkURL; 393 } 394 395 @Override 396 public Image getAttributionImage() { 397 ImageIcon i = ImageProvider.getIfAvailable(attributionImage); 398 if (i != null) { 399 return i.getImage(); 400 } 401 return null; 402 } 403 404 @Override 405 public String getAttributionImageURL() { 406 return attributionImageURL; 407 } 408 409 @Override 410 public String getTermsOfUseText() { 411 return termsOfUseText; 412 } 413 414 @Override 415 public String getTermsOfUseURL() { 416 return termsOfUseURL; 417 } 418 419 public void setAttributionText(String text) { 420 attributionText = text; 421 } 422 423 public void setAttributionImageURL(String text) { 424 attributionImageURL = text; 425 } 426 427 public void setAttributionImage(String text) { 428 attributionImage = text; 429 } 430 431 public void setAttributionLinkURL(String text) { 432 attributionLinkURL = text; 433 } 434 435 public void setTermsOfUseText(String text) { 436 termsOfUseText = text; 437 } 438 439 public void setTermsOfUseURL(String text) { 440 termsOfUseURL = text; 441 } 442 443 public void setExtendedUrl(String url) { 444 CheckParameterUtil.ensureParameterNotNull(url); 445 446 // Default imagery type is WMS 447 this.url = url; 448 this.imageryType = ImageryType.WMS; 449 450 defaultMaxZoom = 0; 451 defaultMinZoom = 0; 452 for (ImageryType type : ImageryType.values()) { 453 Matcher m = Pattern.compile(type.getUrlString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url); 454 if (m.matches()) { 455 this.url = m.group(3); 456 this.imageryType = type; 457 if (m.group(2) != null) { 458 defaultMaxZoom = Integer.valueOf(m.group(2)); 459 } 460 if (m.group(1) != null) { 461 defaultMinZoom = Integer.valueOf(m.group(1)); 462 } 463 break; 464 } 465 } 466 467 if (serverProjections == null || serverProjections.isEmpty()) { 468 try { 469 serverProjections = new ArrayList<String>(); 470 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase()); 471 if(m.matches()) { 472 for(String p : m.group(1).split(",")) 473 serverProjections.add(p); 474 } 475 } catch (Exception e) { 476 Main.warn(e); 477 } 478 } 479 } 480 481 public String getName() { 482 return this.name; 483 } 484 485 public void setName(String name) { 486 this.name = name; 487 } 488 489 public String getUrl() { 490 return this.url; 491 } 492 493 public void setUrl(String url) { 494 this.url = url; 495 } 496 497 public boolean isDefaultEntry() { 498 return defaultEntry; 499 } 500 501 public void setDefaultEntry(boolean defaultEntry) { 502 this.defaultEntry = defaultEntry; 503 } 504 505 public String getCookies() { 506 return this.cookies; 507 } 508 509 public double getPixelPerDegree() { 510 return this.pixelPerDegree; 511 } 512 513 public int getMaxZoom() { 514 return this.defaultMaxZoom; 515 } 516 517 public int getMinZoom() { 518 return this.defaultMinZoom; 519 } 520 521 public String getEulaAcceptanceRequired() { 522 return eulaAcceptanceRequired; 523 } 524 525 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) { 526 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 527 } 528 529 public String getCountryCode() { 530 return countryCode; 531 } 532 533 public void setCountryCode(String countryCode) { 534 this.countryCode = countryCode; 535 } 536 537 public String getIcon() { 538 return icon; 539 } 540 541 public void setIcon(String icon) { 542 this.icon = icon; 543 } 544 545 /** 546 * Get the projections supported by the server. Only relevant for 547 * WMS-type ImageryInfo at the moment. 548 * @return null, if no projections have been specified; the list 549 * of supported projections otherwise. 550 */ 551 public List<String> getServerProjections() { 552 if (serverProjections == null) 553 return Collections.emptyList(); 554 return Collections.unmodifiableList(serverProjections); 555 } 556 557 public void setServerProjections(Collection<String> serverProjections) { 558 this.serverProjections = new ArrayList<String>(serverProjections); 559 } 560 561 public String getExtendedUrl() { 562 return imageryType.getUrlString() + (defaultMaxZoom != 0 563 ? "["+(defaultMinZoom != 0 ? defaultMinZoom+",":"")+defaultMaxZoom+"]" : "") + ":" + url; 564 } 565 566 public String getToolbarName() 567 { 568 String res = name; 569 if(pixelPerDegree != 0.0) { 570 res += "#PPD="+pixelPerDegree; 571 } 572 return res; 573 } 574 575 public String getMenuName() 576 { 577 String res = name; 578 if(pixelPerDegree != 0.0) { 579 res += " ("+pixelPerDegree+")"; 580 } 581 return res; 582 } 583 584 public boolean hasAttribution() 585 { 586 return attributionText != null; 587 } 588 589 public void copyAttribution(ImageryInfo i) 590 { 591 this.attributionImage = i.attributionImage; 592 this.attributionImageURL = i.attributionImageURL; 593 this.attributionText = i.attributionText; 594 this.attributionLinkURL = i.attributionLinkURL; 595 this.termsOfUseText = i.termsOfUseText; 596 this.termsOfUseURL = i.termsOfUseURL; 597 } 598 599 /** 600 * Applies the attribution from this object to a TMSTileSource. 601 */ 602 public void setAttribution(AbstractTileSource s) { 603 if (attributionText != null) { 604 if (attributionText.equals("osm")) { 605 s.setAttributionText(new Mapnik().getAttributionText(0, null, null)); 606 } else { 607 s.setAttributionText(attributionText); 608 } 609 } 610 if (attributionLinkURL != null) { 611 if (attributionLinkURL.equals("osm")) { 612 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL()); 613 } else { 614 s.setAttributionLinkURL(attributionLinkURL); 615 } 616 } 617 if (attributionImage != null) { 618 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage); 619 if (i != null) { 620 s.setAttributionImage(i.getImage()); 621 } 622 } 623 if (attributionImageURL != null) { 624 s.setAttributionImageURL(attributionImageURL); 625 } 626 if (termsOfUseText != null) { 627 s.setTermsOfUseText(termsOfUseText); 628 } 629 if (termsOfUseURL != null) { 630 if (termsOfUseURL.equals("osm")) { 631 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL()); 632 } else { 633 s.setTermsOfUseURL(termsOfUseURL); 634 } 635 } 636 } 637 638 public ImageryType getImageryType() { 639 return imageryType; 640 } 641 642 public void setImageryType(ImageryType imageryType) { 643 this.imageryType = imageryType; 644 } 645 646 /** 647 * Returns true if this layer's URL is matched by one of the regular 648 * expressions kept by the current OsmApi instance. 649 */ 650 public boolean isBlacklisted() { 651 return OsmApi.getOsmApi().getCapabilities().isOnImageryBlacklist(this.url); 652 } 653}