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