source: josm/trunk/src/org/openstreetmap/josm/io/imagery/WMSImagery.java@ 13782

Last change on this file since 13782 was 13760, checked in by Don-vip, 6 years ago

i18n fix

  • Property svn:eol-style set to native
File size: 29.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.imagery;
3
4import static java.nio.charset.StandardCharsets.UTF_8;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.io.File;
8import java.io.IOException;
9import java.io.InputStream;
10import java.net.MalformedURLException;
11import java.net.URL;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.concurrent.ConcurrentHashMap;
20import java.util.function.Function;
21import java.util.regex.Pattern;
22import java.util.stream.Collectors;
23
24import javax.imageio.ImageIO;
25import javax.xml.namespace.QName;
26import javax.xml.stream.XMLStreamException;
27import javax.xml.stream.XMLStreamReader;
28
29import org.openstreetmap.josm.data.Bounds;
30import org.openstreetmap.josm.data.coor.EastNorth;
31import org.openstreetmap.josm.data.imagery.DefaultLayer;
32import org.openstreetmap.josm.data.imagery.GetCapabilitiesParseHelper;
33import org.openstreetmap.josm.data.imagery.ImageryInfo;
34import org.openstreetmap.josm.data.imagery.LayerDetails;
35import org.openstreetmap.josm.data.projection.Projection;
36import org.openstreetmap.josm.data.projection.Projections;
37import org.openstreetmap.josm.io.CachedFile;
38import org.openstreetmap.josm.tools.Logging;
39import org.openstreetmap.josm.tools.Utils;
40
41/**
42 * This class represents the capabilities of a WMS imagery server.
43 */
44public class WMSImagery {
45
46
47 private static final String CAPABILITIES_QUERY_STRING = "SERVICE=WMS&REQUEST=GetCapabilities";
48
49 /**
50 * WMS namespace address
51 */
52 public static final String WMS_NS_URL = "http://www.opengis.net/wms";
53
54 // CHECKSTYLE.OFF: SingleSpaceSeparator
55 // WMS 1.0 - 1.3.0
56 private static final QName CAPABILITITES_ROOT_130 = new QName("WMS_Capabilities", WMS_NS_URL);
57 private static final QName QN_ABSTRACT = new QName(WMS_NS_URL, "Abstract");
58 private static final QName QN_CAPABILITY = new QName(WMS_NS_URL, "Capability");
59 private static final QName QN_CRS = new QName(WMS_NS_URL, "CRS");
60 private static final QName QN_DCPTYPE = new QName(WMS_NS_URL, "DCPType");
61 private static final QName QN_FORMAT = new QName(WMS_NS_URL, "Format");
62 private static final QName QN_GET = new QName(WMS_NS_URL, "Get");
63 private static final QName QN_GETMAP = new QName(WMS_NS_URL, "GetMap");
64 private static final QName QN_HTTP = new QName(WMS_NS_URL, "HTTP");
65 private static final QName QN_LAYER = new QName(WMS_NS_URL, "Layer");
66 private static final QName QN_NAME = new QName(WMS_NS_URL, "Name");
67 private static final QName QN_REQUEST = new QName(WMS_NS_URL, "Request");
68 private static final QName QN_SERVICE = new QName(WMS_NS_URL, "Service");
69 private static final QName QN_STYLE = new QName(WMS_NS_URL, "Style");
70 private static final QName QN_TITLE = new QName(WMS_NS_URL, "Title");
71 private static final QName QN_BOUNDINGBOX = new QName(WMS_NS_URL, "BoundingBox");
72 private static final QName QN_EX_GEOGRAPHIC_BBOX = new QName(WMS_NS_URL, "EX_GeographicBoundingBox");
73 private static final QName QN_WESTBOUNDLONGITUDE = new QName(WMS_NS_URL, "westBoundLongitude");
74 private static final QName QN_EASTBOUNDLONGITUDE = new QName(WMS_NS_URL, "eastBoundLongitude");
75 private static final QName QN_SOUTHBOUNDLATITUDE = new QName(WMS_NS_URL, "southBoundLatitude");
76 private static final QName QN_NORTHBOUNDLATITUDE = new QName(WMS_NS_URL, "northBoundLatitude");
77 private static final QName QN_ONLINE_RESOURCE = new QName(WMS_NS_URL, "OnlineResource");
78
79 // WMS 1.1 - 1.1.1
80 private static final QName CAPABILITIES_ROOT_111 = new QName("WMT_MS_Capabilities");
81 private static final QName QN_SRS = new QName("SRS");
82 private static final QName QN_LATLONBOUNDINGBOX = new QName("LatLonBoundingBox");
83
84 // CHECKSTYLE.ON: SingleSpaceSeparator
85
86 /**
87 * An exception that is thrown if there was an error while getting the capabilities of the WMS server.
88 */
89 public static class WMSGetCapabilitiesException extends Exception {
90 private final String incomingData;
91
92 /**
93 * Constructs a new {@code WMSGetCapabilitiesException}
94 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
95 * @param incomingData the answer from WMS server
96 */
97 public WMSGetCapabilitiesException(Throwable cause, String incomingData) {
98 super(cause);
99 this.incomingData = incomingData;
100 }
101
102 /**
103 * Constructs a new {@code WMSGetCapabilitiesException}
104 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method
105 * @param incomingData the answer from the server
106 * @since 10520
107 */
108 public WMSGetCapabilitiesException(String message, String incomingData) {
109 super(message);
110 this.incomingData = incomingData;
111 }
112
113 /**
114 * The data that caused this exception.
115 * @return The server response to the capabilities request.
116 */
117 public String getIncomingData() {
118 return incomingData;
119 }
120 }
121
122 private Map<String, String> headers = new ConcurrentHashMap<>();
123 private String version = "1.1.1"; // default version
124 private String getMapUrl;
125 private URL capabilitiesUrl;
126 private List<String> formats = new ArrayList<>();
127 private List<LayerDetails> layers = new ArrayList<>();
128
129 private String title;
130
131 /**
132 * Make getCapabilities request towards given URL
133 * @param url service url
134 * @throws IOException when connection error when fetching get capabilities document
135 * @throws WMSGetCapabilitiesException when there are errors when parsing get capabilities document
136 */
137 public WMSImagery(String url) throws IOException, WMSGetCapabilitiesException {
138 this(url, null);
139 }
140
141 /**
142 * Make getCapabilities request towards given URL using headers
143 * @param url service url
144 * @param headers HTTP headers to be sent with request
145 * @throws IOException when connection error when fetching get capabilities document
146 * @throws WMSGetCapabilitiesException when there are errors when parsing get capabilities document
147 */
148 public WMSImagery(String url, Map<String, String> headers) throws IOException, WMSGetCapabilitiesException {
149 if (headers != null) {
150 this.headers.putAll(headers);
151 }
152
153 IOException savedExc = null;
154 String workingAddress = null;
155 url_search:
156 for (String z: new String[]{
157 normalizeUrl(url),
158 url,
159 url + CAPABILITIES_QUERY_STRING,
160 }) {
161 for (String ver: new String[]{"", "&VERSION=1.3.0", "&VERSION=1.1.1"}) {
162 try {
163 attemptGetCapabilities(z + ver);
164 workingAddress = z;
165 calculateChildren();
166 // clear saved exception - we've got something working
167 savedExc = null;
168 break url_search;
169 } catch (IOException e) {
170 savedExc = e;
171 Logging.warn(e);
172 }
173 }
174 }
175
176 if (workingAddress != null) {
177 try {
178 capabilitiesUrl = new URL(workingAddress);
179 } catch (MalformedURLException e) {
180 if (savedExc != null) {
181 savedExc = e;
182 }
183 try {
184 capabilitiesUrl = new File(workingAddress).toURI().toURL();
185 } catch (MalformedURLException e1) { // NOPMD
186 // do nothing, raise original exception
187 Logging.trace(e1);
188 }
189 }
190 }
191
192 if (savedExc != null) {
193 throw savedExc;
194 }
195 }
196
197 private void calculateChildren() {
198 Map<LayerDetails, List<LayerDetails>> layerChildren = layers.stream()
199 .filter(x -> x.getParent() != null) // exclude top-level elements
200 .collect(Collectors.groupingBy(LayerDetails::getParent));
201 for (LayerDetails ld: layers) {
202 if (layerChildren.containsKey(ld)) {
203 ld.setChildren(layerChildren.get(ld));
204 }
205 }
206 // leave only top-most elements in the list
207 layers = layers.stream().filter(x -> x.getParent() == null).collect(Collectors.toCollection(ArrayList::new));
208 }
209
210 /**
211 * Returns the list of top-level layers.
212 * @return the list of top-level layers
213 */
214 public List<LayerDetails> getLayers() {
215 return Collections.unmodifiableList(layers);
216 }
217
218 /**
219 * Returns the list of supported formats.
220 * @return the list of supported formats
221 */
222 public Collection<String> getFormats() {
223 return Collections.unmodifiableList(formats);
224 }
225
226 /**
227 * Gets the preferred format for this imagery layer.
228 * @return The preferred format as mime type.
229 */
230 public String getPreferredFormat() {
231 if (formats.contains("image/png")) {
232 return "image/png";
233 } else if (formats.contains("image/jpeg")) {
234 return "image/jpeg";
235 } else if (formats.isEmpty()) {
236 return null;
237 } else {
238 return formats.get(0);
239 }
240 }
241
242 /**
243 * @return root URL of services in this GetCapabilities
244 */
245 public String buildRootUrl() {
246 if (getMapUrl == null && capabilitiesUrl == null) {
247 return null;
248 }
249 if (getMapUrl != null) {
250 return getMapUrl;
251 }
252
253 URL serviceUrl = capabilitiesUrl;
254 StringBuilder a = new StringBuilder(serviceUrl.getProtocol());
255 a.append("://").append(serviceUrl.getHost());
256 if (serviceUrl.getPort() != -1) {
257 a.append(':').append(serviceUrl.getPort());
258 }
259 a.append(serviceUrl.getPath()).append('?');
260 if (serviceUrl.getQuery() != null) {
261 a.append(serviceUrl.getQuery());
262 if (!serviceUrl.getQuery().isEmpty() && !serviceUrl.getQuery().endsWith("&")) {
263 a.append('&');
264 }
265 }
266 return a.toString();
267 }
268
269 /**
270 * Returns URL for accessing GetMap service. String will contain following parameters:
271 * * {proj} - that needs to be replaced with projection (one of {@link #getServerProjections(List)})
272 * * {width} - that needs to be replaced with width of the tile
273 * * {height} - that needs to be replaces with height of the tile
274 * * {bbox} - that needs to be replaced with area that should be fetched (in {proj} coordinates)
275 *
276 * Format of the response will be calculated using {@link #getPreferredFormat()}
277 *
278 * @param selectedLayers list of DefaultLayer selection of layers to be shown
279 * @param transparent whether returned images should contain transparent pixels (if supported by format)
280 * @return URL template for GetMap service containing
281 */
282 public String buildGetMapUrl(List<DefaultLayer> selectedLayers, boolean transparent) {
283 return buildGetMapUrl(
284 getLayers(selectedLayers),
285 selectedLayers.stream().map(x -> x.getStyle()).collect(Collectors.toList()),
286 transparent);
287 }
288
289 /**
290 * @param selectedLayers selected layers as subset of the tree returned by {@link #getLayers()}
291 * @param selectedStyles selected styles for all selectedLayers
292 * @param transparent whether returned images should contain transparent pixels (if supported by format)
293 * @return URL template for GetMap service
294 * @see #buildGetMapUrl(List, boolean)
295 */
296 public String buildGetMapUrl(List<LayerDetails> selectedLayers, List<String> selectedStyles, boolean transparent) {
297 return buildGetMapUrl(
298 selectedLayers.stream().map(x -> x.getName()).collect(Collectors.toList()),
299 selectedStyles,
300 getPreferredFormat(),
301 transparent);
302 }
303
304 /**
305 * @param selectedLayers selected layers as list of strings
306 * @param selectedStyles selected styles of layers as list of strings
307 * @param format format of the response - one of {@link #getFormats()}
308 * @param transparent whether returned images should contain transparent pixels (if supported by format)
309 * @return URL template for GetMap service
310 * @see #buildGetMapUrl(List, boolean)
311 */
312 public String buildGetMapUrl(List<String> selectedLayers,
313 Collection<String> selectedStyles,
314 String format,
315 boolean transparent) {
316
317 Utils.ensure(selectedStyles == null || selectedLayers.size() == selectedStyles.size(),
318 tr("Styles size {0} does not match layers size {1}"),
319 selectedStyles == null ? 0 : selectedStyles.size(),
320 selectedLayers.size());
321
322 return buildRootUrl() + "FORMAT=" + format + ((imageFormatHasTransparency(format) && transparent) ? "&TRANSPARENT=TRUE" : "")
323 + "&VERSION=" + this.version + "&SERVICE=WMS&REQUEST=GetMap&LAYERS="
324 + selectedLayers.stream().collect(Collectors.joining(","))
325 + "&STYLES="
326 + (selectedStyles != null ? Utils.join(",", selectedStyles) : "")
327 + "&"
328 + (belowWMS130() ? "SRS" : "CRS")
329 + "={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}";
330 }
331
332 private boolean tagEquals(QName a, QName b) {
333 boolean ret = a.equals(b);
334 if (ret) {
335 return ret;
336 }
337
338 if (belowWMS130()) {
339 return a.getLocalPart().equals(b.getLocalPart());
340 }
341
342 return false;
343 }
344
345 private void attemptGetCapabilities(String url) throws IOException, WMSGetCapabilitiesException {
346 Logging.debug("Trying WMS getcapabilities with url {0}", url);
347 try (CachedFile cf = new CachedFile(url); InputStream in = cf.setHttpHeaders(headers).
348 setMaxAge(7 * CachedFile.DAYS).
349 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince).
350 getInputStream()) {
351
352 try {
353 XMLStreamReader reader = GetCapabilitiesParseHelper.getReader(in);
354 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
355 if (event == XMLStreamReader.START_ELEMENT) {
356 if (tagEquals(CAPABILITIES_ROOT_111, reader.getName())) {
357 // version 1.1.1
358 this.version = reader.getAttributeValue(null, "version");
359 if (this.version == null) {
360 this.version = "1.1.1";
361 }
362 }
363 if (tagEquals(CAPABILITITES_ROOT_130, reader.getName())) {
364 this.version = reader.getAttributeValue(WMS_NS_URL, "version");
365 }
366 if (tagEquals(QN_SERVICE, reader.getName())) {
367 parseService(reader);
368 }
369
370 if (tagEquals(QN_CAPABILITY, reader.getName())) {
371 parseCapability(reader);
372 }
373 }
374 }
375 } catch (XMLStreamException e) {
376 String content = new String(cf.getByteContent(), UTF_8);
377 cf.clear(); // if there is a problem with parsing of the file, remove it from the cache
378 throw new WMSGetCapabilitiesException(e, content);
379 }
380 }
381 }
382
383 private void parseService(XMLStreamReader reader) throws XMLStreamException {
384 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_TITLE)) {
385 this.title = reader.getElementText();
386 // CHECKSTYLE.OFF: EmptyBlock
387 for (int event = reader.getEventType();
388 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_SERVICE, reader.getName()));
389 event = reader.next()) {
390 // empty loop, just move reader to the end of Service tag, if moveReaderToTag return false, it's already done
391 }
392 // CHECKSTYLE.ON: EmptyBlock
393 }
394 }
395
396 private void parseCapability(XMLStreamReader reader) throws XMLStreamException {
397 for (int event = reader.getEventType();
398 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_CAPABILITY, reader.getName()));
399 event = reader.next()) {
400
401 if (event == XMLStreamReader.START_ELEMENT) {
402 if (tagEquals(QN_REQUEST, reader.getName())) {
403 parseRequest(reader);
404 }
405 if (tagEquals(QN_LAYER, reader.getName())) {
406 parseLayer(reader, null);
407 }
408 }
409 }
410 }
411
412 private void parseRequest(XMLStreamReader reader) throws XMLStreamException {
413 String mode = "";
414 String getMapUrl = "";
415 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_GETMAP)) {
416 for (int event = reader.getEventType();
417 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_GETMAP, reader.getName()));
418 event = reader.next()) {
419
420 if (event == XMLStreamReader.START_ELEMENT) {
421 if (tagEquals(QN_FORMAT, reader.getName())) {
422 String value = reader.getElementText();
423 if (isImageFormatSupportedWarn(value) && !this.formats.contains(value)) {
424 this.formats.add(value);
425 }
426 }
427 if (tagEquals(QN_DCPTYPE, reader.getName()) && GetCapabilitiesParseHelper.moveReaderToTag(reader,
428 this::tagEquals, QN_HTTP, QN_GET)) {
429 mode = reader.getName().getLocalPart();
430 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_ONLINE_RESOURCE)) {
431 getMapUrl = reader.getAttributeValue(GetCapabilitiesParseHelper.XLINK_NS_URL, "href");
432 }
433 // TODO should we handle also POST?
434 if ("GET".equalsIgnoreCase(mode) && getMapUrl != null && !"".equals(getMapUrl)) {
435 this.getMapUrl = getMapUrl;
436 }
437 }
438 }
439 }
440 }
441 }
442
443 private void parseLayer(XMLStreamReader reader, LayerDetails parentLayer) throws XMLStreamException {
444 LayerDetails ret = new LayerDetails(parentLayer);
445 for (int event = reader.next(); // start with advancing reader by one element to get the contents of the layer
446 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_LAYER, reader.getName()));
447 event = reader.next()) {
448
449 if (event == XMLStreamReader.START_ELEMENT) {
450 if (tagEquals(QN_NAME, reader.getName())) {
451 ret.setName(reader.getElementText());
452 }
453 if (tagEquals(QN_ABSTRACT, reader.getName())) {
454 ret.setAbstract(GetCapabilitiesParseHelper.getElementTextWithSubtags(reader));
455 }
456 if (tagEquals(QN_TITLE, reader.getName())) {
457 ret.setTitle(reader.getElementText());
458 }
459 if (tagEquals(QN_CRS, reader.getName())) {
460 ret.addCrs(reader.getElementText());
461 }
462 if (tagEquals(QN_SRS, reader.getName()) && belowWMS130()) {
463 ret.addCrs(reader.getElementText());
464 }
465 if (tagEquals(QN_STYLE, reader.getName())) {
466 parseAndAddStyle(reader, ret);
467 }
468 if (tagEquals(QN_LAYER, reader.getName())) {
469
470 parseLayer(reader, ret);
471 }
472 if (tagEquals(QN_EX_GEOGRAPHIC_BBOX, reader.getName())) {
473 if (ret.getBounds() == null) {
474 Bounds bbox = parseExGeographic(reader);
475 ret.setBounds(bbox);
476 }
477
478 }
479 if (tagEquals(QN_BOUNDINGBOX, reader.getName())) {
480 Projection conv;
481 if (belowWMS130()) {
482 conv = Projections.getProjectionByCode(reader.getAttributeValue(WMS_NS_URL, "SRS"));
483 } else {
484 conv = Projections.getProjectionByCode(reader.getAttributeValue(WMS_NS_URL, "CRS"));
485 }
486 if (ret.getBounds() == null && conv != null) {
487 Bounds bbox = parseBoundingBox(reader, conv);
488 ret.setBounds(bbox);
489 }
490 }
491 if (tagEquals(QN_LATLONBOUNDINGBOX, reader.getName()) && belowWMS130()) {
492 if (ret.getBounds() == null) {
493 Bounds bbox = parseBoundingBox(reader, null);
494 ret.setBounds(bbox);
495 }
496 }
497 }
498 }
499 this.layers.add(ret);
500 }
501
502 /**
503 * @return if this service operates at protocol level below 1.3.0
504 */
505 public boolean belowWMS130() {
506 return this.version.equals("1.1.1") || this.version.equals("1.1") || this.version.equals("1.0");
507 }
508
509 private void parseAndAddStyle(XMLStreamReader reader, LayerDetails ld) throws XMLStreamException {
510 String name = null;
511 String title = null;
512 for (int event = reader.getEventType();
513 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_STYLE, reader.getName()));
514 event = reader.next()) {
515 if (event == XMLStreamReader.START_ELEMENT) {
516 if (tagEquals(QN_NAME, reader.getName())) {
517 name = reader.getElementText();
518 }
519 if (tagEquals(QN_TITLE, reader.getName())) {
520 title = reader.getElementText();
521 }
522 }
523 }
524 if (name == null) {
525 name = "";
526 }
527 ld.addStyle(name, title);
528 }
529
530 private Bounds parseExGeographic(XMLStreamReader reader) throws XMLStreamException {
531 String minx = null, maxx = null, maxy = null, miny = null;
532
533 for (int event = reader.getEventType();
534 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_EX_GEOGRAPHIC_BBOX, reader.getName()));
535 event = reader.next()) {
536 if (event == XMLStreamReader.START_ELEMENT) {
537 if (tagEquals(QN_WESTBOUNDLONGITUDE, reader.getName())) {
538 minx = reader.getElementText();
539 }
540
541 if (tagEquals(QN_EASTBOUNDLONGITUDE, reader.getName())) {
542 maxx = reader.getElementText();
543 }
544
545 if (tagEquals(QN_SOUTHBOUNDLATITUDE, reader.getName())) {
546 miny = reader.getElementText();
547 }
548
549 if (tagEquals(QN_NORTHBOUNDLATITUDE, reader.getName())) {
550 maxy = reader.getElementText();
551 }
552 }
553 }
554 return parseBBox(null, miny, minx, maxy, maxx);
555 }
556
557 private Bounds parseBoundingBox(XMLStreamReader reader, Projection conv) {
558 Function<String, String> attrGetter = tag -> belowWMS130() ?
559 reader.getAttributeValue(null, tag)
560 : reader.getAttributeValue(WMS_NS_URL, tag);
561
562 return parseBBox(
563 conv,
564 attrGetter.apply("miny"),
565 attrGetter.apply("minx"),
566 attrGetter.apply("maxy"),
567 attrGetter.apply("maxx")
568 );
569 }
570
571 private Bounds parseBBox(Projection conv, String miny, String minx, String maxy, String maxx) {
572 if (miny == null || minx == null || maxy == null || maxx == null) {
573 return null;
574 }
575 if (conv != null) {
576 return new Bounds(
577 conv.eastNorth2latlon(new EastNorth(getDecimalDegree(minx), getDecimalDegree(miny))),
578 conv.eastNorth2latlon(new EastNorth(getDecimalDegree(maxx), getDecimalDegree(maxy)))
579 );
580 }
581 return new Bounds(
582 getDecimalDegree(miny),
583 getDecimalDegree(minx),
584 getDecimalDegree(maxy),
585 getDecimalDegree(maxx)
586 );
587 }
588
589 private static double getDecimalDegree(String value) {
590 // Some real-world WMS servers use a comma instead of a dot as decimal separator (seen in Polish WMS server)
591 return Double.parseDouble(value.replace(',', '.'));
592 }
593
594 private String normalizeUrl(String serviceUrlStr) throws MalformedURLException {
595 URL getCapabilitiesUrl = null;
596 String ret = null;
597
598 if (!Pattern.compile(".*GetCapabilities.*", Pattern.CASE_INSENSITIVE).matcher(serviceUrlStr).matches()) {
599 // If the url doesn't already have GetCapabilities, add it in
600 getCapabilitiesUrl = new URL(serviceUrlStr);
601 ret = serviceUrlStr;
602 if (getCapabilitiesUrl.getQuery() == null) {
603 ret = serviceUrlStr + '?' + CAPABILITIES_QUERY_STRING;
604 } else if (!getCapabilitiesUrl.getQuery().isEmpty() && !getCapabilitiesUrl.getQuery().endsWith("&")) {
605 ret = serviceUrlStr + '&' + CAPABILITIES_QUERY_STRING;
606 } else {
607 ret = serviceUrlStr + CAPABILITIES_QUERY_STRING;
608 }
609 } else {
610 // Otherwise assume it's a good URL and let the subsequent error
611 // handling systems deal with problems
612 ret = serviceUrlStr;
613 }
614 return ret;
615 }
616
617 private static boolean isImageFormatSupportedWarn(String format) {
618 boolean isFormatSupported = isImageFormatSupported(format);
619 if (!isFormatSupported) {
620 Logging.info("Skipping unsupported image format {0}", format);
621 }
622 return isFormatSupported;
623 }
624
625 static boolean isImageFormatSupported(final String format) {
626 return ImageIO.getImageReadersByMIMEType(format).hasNext()
627 // handles image/tiff image/tiff8 image/geotiff image/geotiff8
628 || isImageFormatSupported(format, "tiff", "geotiff")
629 || isImageFormatSupported(format, "png")
630 || isImageFormatSupported(format, "svg")
631 || isImageFormatSupported(format, "bmp");
632 }
633
634 static boolean isImageFormatSupported(String format, String... mimeFormats) {
635 for (String mime : mimeFormats) {
636 if (format.startsWith("image/" + mime)) {
637 return ImageIO.getImageReadersBySuffix(mimeFormats[0]).hasNext();
638 }
639 }
640 return false;
641 }
642
643 static boolean imageFormatHasTransparency(final String format) {
644 return format != null && (format.startsWith("image/png") || format.startsWith("image/gif")
645 || format.startsWith("image/svg") || format.startsWith("image/tiff"));
646 }
647
648 /**
649 * Creates ImageryInfo object from this GetCapabilities document
650 *
651 * @param name name of imagery layer
652 * @param selectedLayers layers which are to be used by this imagery layer
653 * @param selectedStyles styles that should be used for selectedLayers
654 * @param transparent if layer should be transparent
655 * @return ImageryInfo object
656 */
657 public ImageryInfo toImageryInfo(String name, List<LayerDetails> selectedLayers, List<String> selectedStyles, boolean transparent) {
658 ImageryInfo i = new ImageryInfo(name, buildGetMapUrl(selectedLayers, selectedStyles, transparent));
659 if (selectedLayers != null && !selectedLayers.isEmpty()) {
660 i.setServerProjections(getServerProjections(selectedLayers));
661 }
662 return i;
663 }
664
665 /**
666 * Returns projections that server supports for provided list of layers. This will be intersection of projections
667 * defined for each layer
668 *
669 * @param selectedLayers list of layers
670 * @return projection code
671 */
672 public Collection<String> getServerProjections(List<LayerDetails> selectedLayers) {
673 if (selectedLayers.isEmpty()) {
674 return Collections.emptyList();
675 }
676 Set<String> proj = new HashSet<>(selectedLayers.get(0).getCrs());
677
678 // set intersect with all layers
679 for (LayerDetails ld: selectedLayers) {
680 proj.retainAll(ld.getCrs());
681 }
682 return proj;
683 }
684
685
686 /**
687 * @param defaultLayers default layers that should select layer object
688 * @return collection of LayerDetails specified by DefaultLayers
689 */
690 public List<LayerDetails> getLayers(List<DefaultLayer> defaultLayers) {
691 Collection<String> layerNames = defaultLayers.stream().map(x -> x.getLayerName()).collect(Collectors.toList());
692 return layers.stream()
693 .flatMap(LayerDetails::flattened)
694 .filter(x -> layerNames.contains(x.getName()))
695 .collect(Collectors.toList());
696 }
697
698 /**
699 * @return title of this service
700 */
701 public String getTitle() {
702 return title;
703 }
704}
Note: See TracBrowser for help on using the repository browser.