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

Last change on this file since 13792 was 13733, checked in by wiktorn, 6 years ago

Imagery definition refactor

Extend imagery definitions by:

  • allowing setting default layers for WMS_ENDPOINT and WMTS
  • allowing setting minimum expires time for tile for this imagery
  • allowing setting custom headers that will be sent for all requests

(get map, get capabilities) for this imagery

Additional changes in code:

  • use TileJobOptions to pass miscellaneous options to loaders
  • refactor WMSImagery to use SAX parser

See: #15981, #7953, #16224, #15940, #16249

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