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 }