source: josm/trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java@ 15739

Last change on this file since 15739 was 15739, checked in by simon04, 4 years ago

see #14921 - WMS: support time={time} for Sentinel-2 imagery

  • Property svn:eol-style set to native
File size: 7.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.DecimalFormat;
7import java.text.DecimalFormatSymbols;
8import java.text.NumberFormat;
9import java.util.Arrays;
10import java.util.Locale;
11import java.util.Map;
12import java.util.Set;
13import java.util.TreeSet;
14import java.util.concurrent.ConcurrentHashMap;
15import java.util.regex.Matcher;
16import java.util.regex.Pattern;
17
18import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.projection.Projection;
21import org.openstreetmap.josm.data.projection.ProjectionRegistry;
22import org.openstreetmap.josm.gui.layer.WMSLayer;
23import org.openstreetmap.josm.tools.CheckParameterUtil;
24import org.openstreetmap.josm.tools.Utils;
25
26/**
27 * Tile Source handling WMS providers
28 *
29 * @author Wiktor Niesiobędzki
30 * @since 8526
31 */
32public class TemplatedWMSTileSource extends AbstractWMSTileSource implements TemplatedTileSource {
33 // CHECKSTYLE.OFF: SingleSpaceSeparator
34 private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
35 private static final Pattern PATTERN_PROJ = Pattern.compile("\\{proj\\}");
36 private static final Pattern PATTERN_WKID = Pattern.compile("\\{wkid\\}");
37 private static final Pattern PATTERN_BBOX = Pattern.compile("\\{bbox\\}");
38 private static final Pattern PATTERN_W = Pattern.compile("\\{w\\}");
39 private static final Pattern PATTERN_S = Pattern.compile("\\{s\\}");
40 private static final Pattern PATTERN_E = Pattern.compile("\\{e\\}");
41 private static final Pattern PATTERN_N = Pattern.compile("\\{n\\}");
42 private static final Pattern PATTERN_WIDTH = Pattern.compile("\\{width\\}");
43 private static final Pattern PATTERN_HEIGHT = Pattern.compile("\\{height\\}");
44 private static final Pattern PATTERN_TIME = Pattern.compile("\\{time\\}"); // Sentinel-2
45 private static final Pattern PATTERN_PARAM = Pattern.compile("\\{([^}]+)\\}");
46 // CHECKSTYLE.ON: SingleSpaceSeparator
47
48 private static final NumberFormat LATLON_FORMAT = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));
49
50 private static final Pattern[] ALL_PATTERNS = {
51 PATTERN_HEADER, PATTERN_PROJ, PATTERN_WKID, PATTERN_BBOX,
52 PATTERN_W, PATTERN_S, PATTERN_E, PATTERN_N,
53 PATTERN_WIDTH, PATTERN_HEIGHT, PATTERN_TIME,
54 };
55
56 private final Set<String> serverProjections;
57 private final Map<String, String> headers = new ConcurrentHashMap<>();
58 private final String date;
59 private final boolean switchLatLon;
60
61 /**
62 * Creates a tile source based on imagery info
63 * @param info imagery info
64 * @param tileProjection the tile projection
65 */
66 public TemplatedWMSTileSource(ImageryInfo info, Projection tileProjection) {
67 super(info, tileProjection);
68 this.serverProjections = new TreeSet<>(info.getServerProjections());
69 this.headers.putAll(info.getCustomHttpHeaders());
70 this.date = info.getDate();
71 handleTemplate();
72 initProjection();
73 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326.
74 //
75 // Background:
76 //
77 // bbox=x_min,y_min,x_max,y_max
78 //
79 // SRS=... is WMS 1.1.1
80 // CRS=... is WMS 1.3.0
81 //
82 // The difference:
83 // For SRS x is east-west and y is north-south
84 // For CRS x and y are as specified by the EPSG
85 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326.
86 // For most other EPSG code there seems to be no difference.
87 // CHECKSTYLE.OFF: LineLength
88 // [1] https://www.epsg-registry.org/report.htm?type=selection&entity=urn:ogc:def:crs:EPSG::4326&reportDetail=short&style=urn:uuid:report-style:default-with-code&style_name=OGP%20Default%20With%20Code&title=EPSG:4326
89 // CHECKSTYLE.ON: LineLength
90 if (baseUrl.toLowerCase(Locale.US).contains("crs=epsg:4326")) {
91 switchLatLon = true;
92 } else if (baseUrl.toLowerCase(Locale.US).contains("crs=")) {
93 // assume WMS 1.3.0
94 switchLatLon = ProjectionRegistry.getProjection().switchXY();
95 } else {
96 switchLatLon = false;
97 }
98 }
99
100 @Override
101 public int getDefaultTileSize() {
102 return WMSLayer.PROP_IMAGE_SIZE.get();
103 }
104
105 @Override
106 public String getTileUrl(int zoom, int tilex, int tiley) {
107 String myProjCode = getServerCRS();
108
109 EastNorth nw = getTileEastNorth(tilex, tiley, zoom);
110 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom);
111
112 double w = nw.getX();
113 double n = nw.getY();
114
115 double s = se.getY();
116 double e = se.getX();
117
118 if ("EPSG:4326".equals(myProjCode) && !serverProjections.contains(myProjCode) && serverProjections.contains("CRS:84")) {
119 myProjCode = "CRS:84";
120 }
121
122 // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll
123 StringBuffer url = new StringBuffer(baseUrl.length());
124 Matcher matcher = PATTERN_PARAM.matcher(baseUrl);
125 while (matcher.find()) {
126 String replacement;
127 switch (matcher.group(1)) {
128 case "proj":
129 replacement = myProjCode;
130 break;
131 case "wkid":
132 replacement = myProjCode.startsWith("EPSG:") ? myProjCode.substring(5) : myProjCode;
133 break;
134 case "bbox":
135 replacement = getBbox(zoom, tilex, tiley, switchLatLon);
136 break;
137 case "w":
138 replacement = LATLON_FORMAT.format(w);
139 break;
140 case "s":
141 replacement = LATLON_FORMAT.format(s);
142 break;
143 case "e":
144 replacement = LATLON_FORMAT.format(e);
145 break;
146 case "n":
147 replacement = LATLON_FORMAT.format(n);
148 break;
149 case "width":
150 case "height":
151 replacement = String.valueOf(getTileSize());
152 break;
153 case "time":
154 replacement = Utils.encodeUrl(date);
155 break;
156 default:
157 replacement = '{' + matcher.group(1) + '}';
158 }
159 matcher.appendReplacement(url, replacement);
160 }
161 matcher.appendTail(url);
162 return url.toString().replace(" ", "%20");
163 }
164
165 @Override
166 public String getTileId(int zoom, int tilex, int tiley) {
167 return getTileUrl(zoom, tilex, tiley);
168 }
169
170 @Override
171 public Map<String, String> getHeaders() {
172 return headers;
173 }
174
175 /**
176 * Checks if url is acceptable by this Tile Source
177 * @param url URL to check
178 */
179 public static void checkUrl(String url) {
180 CheckParameterUtil.ensureParameterNotNull(url, "url");
181 Matcher m = PATTERN_PARAM.matcher(url);
182 while (m.find()) {
183 boolean isSupportedPattern = Arrays.stream(ALL_PATTERNS)
184 .anyMatch(pattern -> pattern.matcher(m.group()).matches());
185 if (!isSupportedPattern) {
186 throw new IllegalArgumentException(
187 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));
188 }
189 }
190 }
191
192 private void handleTemplate() {
193 // Capturing group pattern on switch values
194 StringBuffer output = new StringBuffer();
195 Matcher matcher = PATTERN_HEADER.matcher(this.baseUrl);
196 while (matcher.find()) {
197 headers.put(matcher.group(1), matcher.group(2));
198 matcher.appendReplacement(output, "");
199 }
200 matcher.appendTail(output);
201 this.baseUrl = output.toString();
202 }
203}
Note: See TracBrowser for help on using the repository browser.