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

Last change on this file since 8624 was 8624, checked in by bastiK, 9 years ago

add missing svn:eol-style=native

  • Property svn:eol-style set to native
File size: 14.1 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.awt.Point;
7import java.text.DecimalFormat;
8import java.text.DecimalFormatSymbols;
9import java.text.NumberFormat;
10import java.util.List;
11import java.util.Locale;
12import java.util.Map;
13import java.util.concurrent.ConcurrentHashMap;
14import java.util.regex.Matcher;
15import java.util.regex.Pattern;
16
17import org.openstreetmap.gui.jmapviewer.OsmMercator;
18import org.openstreetmap.gui.jmapviewer.Tile;
19import org.openstreetmap.gui.jmapviewer.TileXY;
20import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
21import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
22import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.data.Bounds;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.projection.Projection;
28import org.openstreetmap.josm.gui.layer.WMSLayer;
29import org.openstreetmap.josm.tools.CheckParameterUtil;
30
31/**
32 * Tile Source handling WMS providers
33 *
34 * @author Wiktor Niesiobędzki
35 * @since 8526
36 */
37public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource {
38 private Map<String, String> headers = new ConcurrentHashMap<>();
39 private final List<String> serverProjections;
40 private EastNorth topLeftCorner;
41 private Bounds worldBounds;
42
43 private static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}";
44 private static final String PATTERN_PROJ = "\\{proj(\\([^})]+\\))?\\}";
45 private static final String PATTERN_BBOX = "\\{bbox\\}";
46 private static final String PATTERN_W = "\\{w\\}";
47 private static final String PATTERN_S = "\\{s\\}";
48 private static final String PATTERN_E = "\\{e\\}";
49 private static final String PATTERN_N = "\\{n\\}";
50 private static final String PATTERN_WIDTH = "\\{width\\}";
51 private static final String PATTERN_HEIGHT = "\\{height\\}";
52
53 private static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));
54
55 private static final String[] ALL_PATTERNS = {
56 PATTERN_HEADER, PATTERN_PROJ, PATTERN_BBOX, PATTERN_W, PATTERN_S, PATTERN_E, PATTERN_N, PATTERN_WIDTH, PATTERN_HEIGHT
57 };
58
59 /**
60 * Creates a tile source based on imagery info
61 * @param info imagery info
62 */
63 public TemplatedWMSTileSource(ImageryInfo info) {
64 super(info);
65 this.serverProjections = info.getServerProjections();
66 handleTemplate();
67 initProjection();
68 }
69
70 /**
71 * Initializes class with current projection in JOSM. This call is needed every time projection changes.
72 */
73 public void initProjection() {
74 initProjection(Main.getProjection());
75 }
76
77 /**
78 * Initializes class with projection in JOSM. This call is needed every time projection changes.
79 * @param proj new projection that shall be used for computations
80 */
81 public void initProjection(Projection proj) {
82 this.worldBounds = getWorldBounds();
83 EastNorth min = proj.latlon2eastNorth(worldBounds.getMin());
84 EastNorth max = proj.latlon2eastNorth(worldBounds.getMax());
85 this.topLeftCorner = new EastNorth(min.east(), max.north());
86 }
87
88 @Override
89 public int getDefaultTileSize() {
90 return WMSLayer.PROP_IMAGE_SIZE.get();
91 }
92
93 // FIXME: remove in September 2015, when ImageryPreferenceEntry.tileSize will be initialized to -1 instead to 256
94 // need to leave it as it is to keep compatiblity between tested and latest JOSM versions
95 @Override
96 public int getTileSize() {
97 return WMSLayer.PROP_IMAGE_SIZE.get();
98 }
99
100 @Override
101 public String getTileUrl(int zoom, int tilex, int tiley) {
102 String myProjCode = Main.getProjection().toCode();
103
104 EastNorth nw = getTileEastNorth(tilex, tiley, zoom);
105 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom);
106
107 double w = nw.getX();
108 double n = nw.getY();
109
110 double s = se.getY();
111 double e = se.getX();
112
113 if (!serverProjections.contains(myProjCode) && serverProjections.contains("EPSG:4326") && "EPSG:3857".equals(myProjCode)) {
114 LatLon swll = Main.getProjection().eastNorth2latlon(new EastNorth(w, s));
115 LatLon nell = Main.getProjection().eastNorth2latlon(new EastNorth(e, n));
116 myProjCode = "EPSG:4326";
117 s = swll.lat();
118 w = swll.lon();
119 n = nell.lat();
120 e = nell.lon();
121 }
122
123 if ("EPSG:4326".equals(myProjCode) && !serverProjections.contains(myProjCode) && serverProjections.contains("CRS:84")) {
124 myProjCode = "CRS:84";
125 }
126
127 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326.
128 //
129 // Background:
130 //
131 // bbox=x_min,y_min,x_max,y_max
132 //
133 // SRS=... is WMS 1.1.1
134 // CRS=... is WMS 1.3.0
135 //
136 // The difference:
137 // For SRS x is east-west and y is north-south
138 // For CRS x and y are as specified by the EPSG
139 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326.
140 // For most other EPSG code there seems to be no difference.
141 // CHECKSTYLE.OFF: LineLength
142 // [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
143 // CHECKSTYLE.ON: LineLength
144 boolean switchLatLon = false;
145 if (baseUrl.toLowerCase().contains("crs=epsg:4326")) {
146 switchLatLon = true;
147 } else if (baseUrl.toLowerCase().contains("crs=")) {
148 // assume WMS 1.3.0
149 switchLatLon = Main.getProjection().switchXY();
150 }
151 String bbox;
152 if (switchLatLon) {
153 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(s), latLonFormat.format(w), latLonFormat.format(n), latLonFormat.format(e));
154 } else {
155 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(w), latLonFormat.format(s), latLonFormat.format(e), latLonFormat.format(n));
156 }
157 return baseUrl.
158 replaceAll(PATTERN_PROJ, myProjCode)
159 .replaceAll(PATTERN_BBOX, bbox)
160 .replaceAll(PATTERN_W, latLonFormat.format(w))
161 .replaceAll(PATTERN_S, latLonFormat.format(s))
162 .replaceAll(PATTERN_E, latLonFormat.format(e))
163 .replaceAll(PATTERN_N, latLonFormat.format(n))
164 .replaceAll(PATTERN_WIDTH, String.valueOf(getTileSize()))
165 .replaceAll(PATTERN_HEIGHT, String.valueOf(getTileSize()))
166 .replace(" ", "%20");
167 }
168
169 @Override
170 public String getTileId(int zoom, int tilex, int tiley) {
171 return getTileUrl(zoom, tilex, tiley);
172 }
173
174 @Override
175 public ICoordinate tileXYToLatLon(Tile tile) {
176 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
177 }
178
179 @Override
180 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
181 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
182 }
183
184 @Override
185 public ICoordinate tileXYToLatLon(int x, int y, int zoom) {
186 return Main.getProjection().eastNorth2latlon(getTileEastNorth(x, y, zoom)).toCoordinate();
187 }
188
189 @Override
190 public TileXY latLonToTileXY(double lat, double lon, int zoom) {
191 Projection proj = Main.getProjection();
192 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon));
193 double scale = getDegreesPerTile(zoom);
194 return new TileXY(
195 (enPoint.east() - topLeftCorner.east()) / scale,
196 (topLeftCorner.north() - enPoint.north()) / scale
197 );
198 }
199
200 @Override
201 public TileXY latLonToTileXY(ICoordinate point, int zoom) {
202 return latLonToTileXY(point.getLat(), point.getLon(), zoom);
203 }
204
205 @Override
206 public int getTileXMax(int zoom) {
207 LatLon bottomRight = new LatLon(worldBounds.getMinLat(), worldBounds.getMaxLon());
208 return latLonToTileXY(bottomRight.toCoordinate(), zoom).getXIndex();
209 }
210
211 @Override
212 public int getTileXMin(int zoom) {
213 return 0;
214 }
215
216 @Override
217 public int getTileYMax(int zoom) {
218 LatLon bottomRight = new LatLon(worldBounds.getMinLat(), worldBounds.getMaxLon());
219 return latLonToTileXY(bottomRight.toCoordinate(), zoom).getYIndex();
220 }
221
222 @Override
223 public int getTileYMin(int zoom) {
224 return 0;
225 }
226
227 @Override
228 public Point latLonToXY(double lat, double lon, int zoom) {
229 double scale = getDegreesPerTile(zoom) / getTileSize();
230 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
231 return new Point(
232 (int) Math.round((point.east() - topLeftCorner.east()) / scale),
233 (int) Math.round((topLeftCorner.north() - point.north()) / scale)
234 );
235 }
236
237 @Override
238 public Point latLonToXY(ICoordinate point, int zoom) {
239 return latLonToXY(point.getLat(), point.getLon(), zoom);
240 }
241
242 @Override
243 public ICoordinate XYToLatLon(Point point, int zoom) {
244 return XYToLatLon(point.x, point.y, zoom);
245 }
246
247 @Override
248 public ICoordinate XYToLatLon(int x, int y, int zoom) {
249 double scale = getDegreesPerTile(zoom) / getTileSize();
250 Projection proj = Main.getProjection();
251 EastNorth ret = new EastNorth(
252 topLeftCorner.east() + x * scale,
253 topLeftCorner.north() - y * scale
254 );
255 return proj.eastNorth2latlon(ret).toCoordinate();
256 }
257
258 @Override
259 public Map<String, String> getHeaders() {
260 return headers;
261 }
262
263 @Override
264 public double lonToTileX(double lon, int zoom) {
265 throw new UnsupportedOperationException("Not implemented");
266 }
267
268 @Override
269 public double tileXToLon(int x, int zoom) {
270 throw new UnsupportedOperationException("Not implemented");
271 }
272
273 @Override
274 public double tileYToLat(int y, int zoom) {
275 throw new UnsupportedOperationException("Not implemented");
276 }
277
278 @Override
279 public double getDistance(double lat1, double lon1, double lat2, double lon2) {
280 throw new UnsupportedOperationException("Not implemented");
281 }
282
283 @Override
284 public int lonToX(double lon, int zoom) {
285 throw new UnsupportedOperationException("Not implemented");
286 }
287
288 @Override
289 public int latToY(double lat, int zoom) {
290 throw new UnsupportedOperationException("Not implemented");
291 }
292
293 @Override
294 public double XToLon(int x, int zoom) {
295 throw new UnsupportedOperationException("Not implemented");
296 }
297
298 @Override
299 public double YToLat(int y, int zoom) {
300 throw new UnsupportedOperationException("Not implemented");
301 }
302
303 @Override
304 public double latToTileY(double lat, int zoom) {
305 throw new UnsupportedOperationException("Not implemented");
306 }
307
308 /**
309 * Checks if url is acceptable by this Tile Source
310 * @param url URL to check
311 */
312 public static void checkUrl(String url) {
313 CheckParameterUtil.ensureParameterNotNull(url, "url");
314 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);
315 while (m.find()) {
316 boolean isSupportedPattern = false;
317 for (String pattern : ALL_PATTERNS) {
318 if (m.group().matches(pattern)) {
319 isSupportedPattern = true;
320 break;
321 }
322 }
323 if (!isSupportedPattern) {
324 throw new IllegalArgumentException(
325 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));
326 }
327 }
328 }
329
330 private void handleTemplate() {
331 // Capturing group pattern on switch values
332 Pattern pattern = Pattern.compile(PATTERN_HEADER);
333 StringBuffer output = new StringBuffer();
334 Matcher matcher = pattern.matcher(this.baseUrl);
335 while (matcher.find()) {
336 headers.put(matcher.group(1), matcher.group(2));
337 matcher.appendReplacement(output, "");
338 }
339 matcher.appendTail(output);
340 this.baseUrl = output.toString();
341 }
342
343 protected EastNorth getTileEastNorth(int x, int y, int z) {
344 double scale = getDegreesPerTile(z);
345 return new EastNorth(
346 topLeftCorner.east() + x * scale,
347 topLeftCorner.north() - y * scale
348 );
349 }
350
351 private double getDegreesPerTile(int zoom) {
352 Projection proj = Main.getProjection();
353 EastNorth min = proj.latlon2eastNorth(worldBounds.getMin());
354 EastNorth max = proj.latlon2eastNorth(worldBounds.getMax());
355
356 int tilesPerZoom = (int) Math.pow(2, zoom - 1);
357 return Math.max(
358 Math.abs(max.getY() - min.getY()) / tilesPerZoom,
359 Math.abs(max.getX() - min.getX()) / tilesPerZoom
360 );
361 }
362
363 /**
364 * returns world bounds, but detect situation, when default bounds are provided (-90, -180, 90, 180), and projection
365 * returns very close values for both min and max X. To work around this problem, cap this projection on north and south
366 * pole, the same way they are capped in Mercator projection, so conversions should work properly
367 */
368 private final static Bounds getWorldBounds() {
369 Projection proj = Main.getProjection();
370 Bounds bounds = proj.getWorldBoundsLatLon();
371 EastNorth min = proj.latlon2eastNorth(bounds.getMin());
372 EastNorth max = proj.latlon2eastNorth(bounds.getMax());
373
374 if (Math.abs(min.getX() - max.getX()) < 1 && bounds.equals(new Bounds(new LatLon(-90, -180), new LatLon(90, 180)))) {
375 return new Bounds(
376 new LatLon(OsmMercator.MIN_LAT, bounds.getMinLon()),
377 new LatLon(OsmMercator.MAX_LAT, bounds.getMaxLon())
378 );
379 }
380 return bounds;
381 }
382}
Note: See TracBrowser for help on using the repository browser.