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 if (name != null ? !name.equals(that.name) : that.name != null) return false; 279 280 return true; 281 } 282 283 @Override 284 public int hashCode() { 285 int result = url != null ? url.hashCode() : 0; 286 result = 31 * result + (imageryType != null ? imageryType.hashCode() : 0); 287 return result; 288 } 289 290 @Override 291 public String toString() { 292 return "ImageryInfo{" + 293 "name='" + name + '\'' + 294 ", countryCode='" + countryCode + '\'' + 295 ", url='" + url + '\'' + 296 ", imageryType=" + imageryType + 297 '}'; 298 } 299 300 @Override 301 public int compareTo(ImageryInfo in) 302 { 303 int i = countryCode.compareTo(in.countryCode); 304 if (i == 0) { 305 i = name.compareTo(in.name); 306 } 307 if (i == 0) { 308 i = url.compareTo(in.url); 309 } 310 if (i == 0) { 311 i = Double.compare(pixelPerDegree, in.pixelPerDegree); 312 } 313 return i; 314 } 315 316 public boolean equalsBaseValues(ImageryInfo in) 317 { 318 return url.equals(in.url); 319 } 320 321 public void setPixelPerDegree(double ppd) { 322 this.pixelPerDegree = ppd; 323 } 324 325 public void setDefaultMaxZoom(int defaultMaxZoom) { 326 this.defaultMaxZoom = defaultMaxZoom; 327 } 328 329 public void setDefaultMinZoom(int defaultMinZoom) { 330 this.defaultMinZoom = defaultMinZoom; 331 } 332 333 public void setBounds(ImageryBounds b) { 334 this.bounds = b; 335 } 336 337 public ImageryBounds getBounds() { 338 return bounds; 339 } 340 341 @Override 342 public boolean requiresAttribution() { 343 return attributionText != null || attributionImage != null || termsOfUseText != null || termsOfUseURL != null; 344 } 345 346 @Override 347 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) { 348 return attributionText; 349 } 350 351 @Override 352 public String getAttributionLinkURL() { 353 return attributionLinkURL; 354 } 355 356 @Override 357 public Image getAttributionImage() { 358 ImageIcon i = ImageProvider.getIfAvailable(attributionImage); 359 if (i != null) { 360 return i.getImage(); 361 } 362 return null; 363 } 364 365 @Override 366 public String getAttributionImageURL() { 367 return attributionImageURL; 368 } 369 370 @Override 371 public String getTermsOfUseText() { 372 return termsOfUseText; 373 } 374 375 @Override 376 public String getTermsOfUseURL() { 377 return termsOfUseURL; 378 } 379 380 public void setAttributionText(String text) { 381 attributionText = text; 382 } 383 384 public void setAttributionImageURL(String text) { 385 attributionImageURL = text; 386 } 387 388 public void setAttributionImage(String text) { 389 attributionImage = text; 390 } 391 392 public void setAttributionLinkURL(String text) { 393 attributionLinkURL = text; 394 } 395 396 public void setTermsOfUseText(String text) { 397 termsOfUseText = text; 398 } 399 400 public void setTermsOfUseURL(String text) { 401 termsOfUseURL = text; 402 } 403 404 public void setExtendedUrl(String url) { 405 CheckParameterUtil.ensureParameterNotNull(url); 406 407 // Default imagery type is WMS 408 this.url = url; 409 this.imageryType = ImageryType.WMS; 410 411 defaultMaxZoom = 0; 412 defaultMinZoom = 0; 413 for (ImageryType type : ImageryType.values()) { 414 Matcher m = Pattern.compile(type.getUrlString()+"(?:\\[(?:(\\d+),)?(\\d+)\\])?:(.*)").matcher(url); 415 if(m.matches()) { 416 this.url = m.group(3); 417 this.imageryType = type; 418 if(m.group(2) != null) { 419 defaultMaxZoom = Integer.valueOf(m.group(2)); 420 } 421 if(m.group(1) != null) { 422 defaultMinZoom = Integer.valueOf(m.group(1)); 423 } 424 break; 425 } 426 } 427 428 if(serverProjections == null || serverProjections.isEmpty()) { 429 try { 430 serverProjections = new ArrayList<String>(); 431 Matcher m = Pattern.compile(".*\\{PROJ\\(([^)}]+)\\)\\}.*").matcher(url.toUpperCase()); 432 if(m.matches()) { 433 for(String p : m.group(1).split(",")) 434 serverProjections.add(p); 435 } 436 } catch(Exception e) { 437 } 438 } 439 } 440 441 public String getName() { 442 return this.name; 443 } 444 445 public void setName(String name) { 446 this.name = name; 447 } 448 449 public String getUrl() { 450 return this.url; 451 } 452 453 public void setUrl(String url) { 454 this.url = url; 455 } 456 457 public boolean isDefaultEntry() { 458 return defaultEntry; 459 } 460 461 public void setDefaultEntry(boolean defaultEntry) { 462 this.defaultEntry = defaultEntry; 463 } 464 465 public String getCookies() { 466 return this.cookies; 467 } 468 469 public double getPixelPerDegree() { 470 return this.pixelPerDegree; 471 } 472 473 public int getMaxZoom() { 474 return this.defaultMaxZoom; 475 } 476 477 public int getMinZoom() { 478 return this.defaultMinZoom; 479 } 480 481 public String getEulaAcceptanceRequired() { 482 return eulaAcceptanceRequired; 483 } 484 485 public void setEulaAcceptanceRequired(String eulaAcceptanceRequired) { 486 this.eulaAcceptanceRequired = eulaAcceptanceRequired; 487 } 488 489 public String getCountryCode() { 490 return countryCode; 491 } 492 493 public void setCountryCode(String countryCode) { 494 this.countryCode = countryCode; 495 } 496 497 public String getIcon() { 498 return icon; 499 } 500 501 public void setIcon(String icon) { 502 this.icon = icon; 503 } 504 505 /** 506 * Get the projections supported by the server. Only relevant for 507 * WMS-type ImageryInfo at the moment. 508 * @return null, if no projections have been specified; the list 509 * of supported projections otherwise. 510 */ 511 public List<String> getServerProjections() { 512 if (serverProjections == null) 513 return Collections.emptyList(); 514 return Collections.unmodifiableList(serverProjections); 515 } 516 517 public void setServerProjections(Collection<String> serverProjections) { 518 this.serverProjections = new ArrayList<String>(serverProjections); 519 } 520 521 public String getExtendedUrl() { 522 return imageryType.getUrlString() + (defaultMaxZoom != 0 523 ? "["+(defaultMinZoom != 0 ? defaultMinZoom+",":"")+defaultMaxZoom+"]" : "") + ":" + url; 524 } 525 526 public String getToolbarName() 527 { 528 String res = name; 529 if(pixelPerDegree != 0.0) { 530 res += "#PPD="+pixelPerDegree; 531 } 532 return res; 533 } 534 535 public String getMenuName() 536 { 537 String res = name; 538 if(pixelPerDegree != 0.0) { 539 res += " ("+pixelPerDegree+")"; 540 } 541 return res; 542 } 543 544 public boolean hasAttribution() 545 { 546 return attributionText != null; 547 } 548 549 public void copyAttribution(ImageryInfo i) 550 { 551 this.attributionImage = i.attributionImage; 552 this.attributionImageURL = i.attributionImageURL; 553 this.attributionText = i.attributionText; 554 this.attributionLinkURL = i.attributionLinkURL; 555 this.termsOfUseText = i.termsOfUseText; 556 this.termsOfUseURL = i.termsOfUseURL; 557 } 558 559 /** 560 * Applies the attribution from this object to a TMSTileSource. 561 */ 562 public void setAttribution(AbstractTileSource s) { 563 if (attributionText != null) { 564 if (attributionText.equals("osm")) { 565 s.setAttributionText(new Mapnik().getAttributionText(0, null, null)); 566 } else { 567 s.setAttributionText(attributionText); 568 } 569 } 570 if (attributionLinkURL != null) { 571 if (attributionLinkURL.equals("osm")) { 572 s.setAttributionLinkURL(new Mapnik().getAttributionLinkURL()); 573 } else { 574 s.setAttributionLinkURL(attributionLinkURL); 575 } 576 } 577 if (attributionImage != null) { 578 ImageIcon i = ImageProvider.getIfAvailable(null, attributionImage); 579 if (i != null) { 580 s.setAttributionImage(i.getImage()); 581 } 582 } 583 if (attributionImageURL != null) { 584 s.setAttributionImageURL(attributionImageURL); 585 } 586 if (termsOfUseText != null) { 587 s.setTermsOfUseText(termsOfUseText); 588 } 589 if (termsOfUseURL != null) { 590 if (termsOfUseURL.equals("osm")) { 591 s.setTermsOfUseURL(new Mapnik().getTermsOfUseURL()); 592 } else { 593 s.setTermsOfUseURL(termsOfUseURL); 594 } 595 } 596 } 597 598 public ImageryType getImageryType() { 599 return imageryType; 600 } 601 602 public void setImageryType(ImageryType imageryType) { 603 this.imageryType = imageryType; 604 } 605 606 /** 607 * Returns true if this layer's URL is matched by one of the regular 608 * expressions kept by the current OsmApi instance. 609 */ 610 public boolean isBlacklisted() { 611 return OsmApi.getOsmApi().getCapabilities().isOnImageryBlacklist(this.url); 612 } 613 }