- Timestamp:
- 2016-09-10T21:27:19+02:00 (8 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 2 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java
r10989 r10990 2 2 package org.openstreetmap.josm.data.imagery; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 4 import java.awt.Point; 7 import java.text.DecimalFormat;8 import java.text.DecimalFormatSymbols;9 import java.text.NumberFormat;10 import java.util.Locale;11 import java.util.Map;12 import java.util.Set;13 import java.util.TreeSet;14 import java.util.concurrent.ConcurrentHashMap;15 import java.util.regex.Matcher;16 import java.util.regex.Pattern;17 5 18 6 import org.openstreetmap.gui.jmapviewer.Tile; 19 7 import org.openstreetmap.gui.jmapviewer.TileXY; 20 8 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 21 import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;22 9 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; 10 import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; 23 11 import org.openstreetmap.josm.Main; 24 12 import org.openstreetmap.josm.data.Bounds; … … 27 15 import org.openstreetmap.josm.data.coor.LatLon; 28 16 import org.openstreetmap.josm.data.projection.Projection; 29 import org.openstreetmap.josm.gui.layer.WMSLayer;30 import org.openstreetmap.josm.tools.CheckParameterUtil;31 17 32 18 /** 33 * Tile Source handling WMS providers 19 * Base class for different WMS tile sources those based on URL templates and those based on WMS endpoints 20 * @author Wiktor Niesiobędzki 21 * @since 10990 34 22 * 35 * @author Wiktor Niesiobędzki36 * @since 852637 23 */ 38 public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource { 39 private final Map<String, String> headers = new ConcurrentHashMap<>(); 40 private final Set<String> serverProjections; 24 public class AbstractWMSTileSource extends TMSTileSource { 25 41 26 private EastNorth anchorPosition; 42 27 private int[] tileXMin; … … 45 30 private int[] tileYMax; 46 31 private double[] degreesPerTile; 47 48 // CHECKSTYLE.OFF: SingleSpaceSeparator49 private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");50 private static final Pattern PATTERN_PROJ = Pattern.compile("\\{proj\\}");51 private static final Pattern PATTERN_WKID = Pattern.compile("\\{wkid\\}");52 private static final Pattern PATTERN_BBOX = Pattern.compile("\\{bbox\\}");53 private static final Pattern PATTERN_W = Pattern.compile("\\{w\\}");54 private static final Pattern PATTERN_S = Pattern.compile("\\{s\\}");55 private static final Pattern PATTERN_E = Pattern.compile("\\{e\\}");56 private static final Pattern PATTERN_N = Pattern.compile("\\{n\\}");57 private static final Pattern PATTERN_WIDTH = Pattern.compile("\\{width\\}");58 private static final Pattern PATTERN_HEIGHT = Pattern.compile("\\{height\\}");59 private static final Pattern PATTERN_PARAM = Pattern.compile("\\{([^}]+)\\}");60 // CHECKSTYLE.ON: SingleSpaceSeparator61 62 private static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));63 64 private static final Pattern[] ALL_PATTERNS = {65 PATTERN_HEADER, PATTERN_PROJ, PATTERN_WKID, PATTERN_BBOX, PATTERN_W, PATTERN_S, PATTERN_E, PATTERN_N, PATTERN_WIDTH, PATTERN_HEIGHT66 };67 68 /*69 * Constant taken from OGC WMTS Implementation Specification (http://www.opengeospatial.org/standards/wmts)70 * From table E.4 - Definition of Well-known scale set GoogleMapsCompatibile71 *72 * As higher zoom levels have denominator divided by 2, we keep only zoom level 1 in the code73 */74 32 private static final float SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 559082264.0287178f; 75 33 76 /** 77 * Creates a tile source based on imagery info 78 * @param info imagery info 79 */ 80 public TemplatedWMSTileSource(ImageryInfo info) { 34 public AbstractWMSTileSource(TileSourceInfo info) { 81 35 super(info); 82 this.serverProjections = new TreeSet<>(info.getServerProjections());83 handleTemplate();84 initProjection();85 36 } 86 37 … … 129 80 tileYMax[zoom] = maxTileIndex.getYIndex(); 130 81 } 131 }132 133 @Override134 public int getDefaultTileSize() {135 return WMSLayer.PROP_IMAGE_SIZE.get();136 }137 138 @Override139 public String getTileUrl(int zoom, int tilex, int tiley) {140 String myProjCode = Main.getProjection().toCode();141 142 EastNorth nw = getTileEastNorth(tilex, tiley, zoom);143 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom);144 145 double w = nw.getX();146 double n = nw.getY();147 148 double s = se.getY();149 double e = se.getX();150 151 if (!serverProjections.contains(myProjCode) && serverProjections.contains("EPSG:4326") && "EPSG:3857".equals(myProjCode)) {152 LatLon swll = Main.getProjection().eastNorth2latlon(new EastNorth(w, s));153 LatLon nell = Main.getProjection().eastNorth2latlon(new EastNorth(e, n));154 myProjCode = "EPSG:4326";155 s = swll.lat();156 w = swll.lon();157 n = nell.lat();158 e = nell.lon();159 }160 161 if ("EPSG:4326".equals(myProjCode) && !serverProjections.contains(myProjCode) && serverProjections.contains("CRS:84")) {162 myProjCode = "CRS:84";163 }164 165 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326.166 //167 // Background:168 //169 // bbox=x_min,y_min,x_max,y_max170 //171 // SRS=... is WMS 1.1.1172 // CRS=... is WMS 1.3.0173 //174 // The difference:175 // For SRS x is east-west and y is north-south176 // For CRS x and y are as specified by the EPSG177 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326.178 // For most other EPSG code there seems to be no difference.179 // CHECKSTYLE.OFF: LineLength180 // [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:4326181 // CHECKSTYLE.ON: LineLength182 boolean switchLatLon = false;183 if (baseUrl.toLowerCase(Locale.US).contains("crs=epsg:4326")) {184 switchLatLon = true;185 } else if (baseUrl.toLowerCase(Locale.US).contains("crs=")) {186 // assume WMS 1.3.0187 switchLatLon = Main.getProjection().switchXY();188 }189 String bbox;190 if (switchLatLon) {191 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(s), latLonFormat.format(w), latLonFormat.format(n), latLonFormat.format(e));192 } else {193 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(w), latLonFormat.format(s), latLonFormat.format(e), latLonFormat.format(n));194 }195 196 // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll197 StringBuffer url = new StringBuffer(baseUrl.length());198 Matcher matcher = PATTERN_PARAM.matcher(baseUrl);199 while (matcher.find()) {200 String replacement;201 switch (matcher.group(1)) {202 case "proj":203 replacement = myProjCode;204 break;205 case "wkid":206 replacement = myProjCode.startsWith("EPSG:") ? myProjCode.substring(5) : myProjCode;207 break;208 case "bbox":209 replacement = bbox;210 break;211 case "w":212 replacement = latLonFormat.format(w);213 break;214 case "s":215 replacement = latLonFormat.format(s);216 break;217 case "e":218 replacement = latLonFormat.format(e);219 break;220 case "n":221 replacement = latLonFormat.format(n);222 break;223 case "width":224 case "height":225 replacement = String.valueOf(getTileSize());226 break;227 default:228 replacement = '{' + matcher.group(1) + '}';229 }230 matcher.appendReplacement(url, replacement);231 }232 matcher.appendTail(url);233 return url.toString().replace(" ", "%20");234 }235 236 @Override237 public String getTileId(int zoom, int tilex, int tiley) {238 return getTileUrl(zoom, tilex, tiley);239 82 } 240 83 … … 299 142 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon)); 300 143 return new Point( 301 302 144 (int) Math.round((point.east() - anchorPosition.east()) / scale), 145 (int) Math.round((anchorPosition.north() - point.north()) / scale) 303 146 ); 304 147 } … … 325 168 } 326 169 327 @Override328 public Map<String, String> getHeaders() {329 return headers;330 }331 332 /**333 * Checks if url is acceptable by this Tile Source334 * @param url URL to check335 */336 public static void checkUrl(String url) {337 CheckParameterUtil.ensureParameterNotNull(url, "url");338 Matcher m = PATTERN_PARAM.matcher(url);339 while (m.find()) {340 boolean isSupportedPattern = false;341 for (Pattern pattern : ALL_PATTERNS) {342 if (pattern.matcher(m.group()).matches()) {343 isSupportedPattern = true;344 break;345 }346 }347 if (!isSupportedPattern) {348 throw new IllegalArgumentException(349 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));350 }351 }352 }353 354 private void handleTemplate() {355 // Capturing group pattern on switch values356 StringBuffer output = new StringBuffer();357 Matcher matcher = PATTERN_HEADER.matcher(this.baseUrl);358 while (matcher.find()) {359 headers.put(matcher.group(1), matcher.group(2));360 matcher.appendReplacement(output, "");361 }362 matcher.appendTail(output);363 this.baseUrl = output.toString();364 }365 366 170 protected EastNorth getTileEastNorth(int x, int y, int z) { 367 171 double scale = getDegreesPerTile(z); 368 172 return new EastNorth( 369 370 371 173 anchorPosition.east() + x * scale, 174 anchorPosition.north() - y * scale 175 ); 372 176 } 373 177 … … 375 179 return degreesPerTile[zoom]; 376 180 } 181 377 182 } -
trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java
r10378 r10990 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Point;7 6 import java.text.DecimalFormat; 8 7 import java.text.DecimalFormatSymbols; … … 16 15 import java.util.regex.Pattern; 17 16 18 import org.openstreetmap.gui.jmapviewer.Tile;19 import org.openstreetmap.gui.jmapviewer.TileXY;20 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;21 17 import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource; 22 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;23 18 import org.openstreetmap.josm.Main; 24 import org.openstreetmap.josm.data.Bounds;25 import org.openstreetmap.josm.data.ProjectionBounds;26 19 import org.openstreetmap.josm.data.coor.EastNorth; 27 20 import org.openstreetmap.josm.data.coor.LatLon; 28 import org.openstreetmap.josm.data.projection.Projection;29 21 import org.openstreetmap.josm.gui.layer.WMSLayer; 30 22 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 36 28 * @since 8526 37 29 */ 38 public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource {30 public class TemplatedWMSTileSource extends AbstractWMSTileSource implements TemplatedTileSource { 39 31 private final Map<String, String> headers = new ConcurrentHashMap<>(); 40 32 private final Set<String> serverProjections; 41 private EastNorth anchorPosition;42 private int[] tileXMin;43 private int[] tileYMin;44 private int[] tileXMax;45 private int[] tileYMax;46 private double[] degreesPerTile;47 48 33 // CHECKSTYLE.OFF: SingleSpaceSeparator 49 34 private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}"); … … 66 51 }; 67 52 68 /*69 * Constant taken from OGC WMTS Implementation Specification (http://www.opengeospatial.org/standards/wmts)70 * From table E.4 - Definition of Well-known scale set GoogleMapsCompatibile71 *72 * As higher zoom levels have denominator divided by 2, we keep only zoom level 1 in the code73 */74 private static final float SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 559082264.0287178f;75 76 53 /** 77 54 * Creates a tile source based on imagery info … … 83 60 handleTemplate(); 84 61 initProjection(); 85 }86 87 /**88 * Initializes class with current projection in JOSM. This call is needed every time projection changes.89 */90 public void initProjection() {91 initProjection(Main.getProjection());92 }93 94 private void initAnchorPosition(Projection proj) {95 Bounds worldBounds = proj.getWorldBoundsLatLon();96 EastNorth min = proj.latlon2eastNorth(worldBounds.getMin());97 EastNorth max = proj.latlon2eastNorth(worldBounds.getMax());98 this.anchorPosition = new EastNorth(min.east(), max.north());99 }100 101 /**102 * Initializes class with projection in JOSM. This call is needed every time projection changes.103 * @param proj new projection that shall be used for computations104 */105 public void initProjection(Projection proj) {106 initAnchorPosition(proj);107 ProjectionBounds worldBounds = proj.getWorldBoundsBoxEastNorth();108 109 EastNorth topLeft = new EastNorth(worldBounds.getMin().east(), worldBounds.getMax().north());110 EastNorth bottomRight = new EastNorth(worldBounds.getMax().east(), worldBounds.getMin().north());111 112 // use 256 as "tile size" to keep the scale in line with default tiles in Mercator projection113 double crsScale = 256 * 0.28e-03 / proj.getMetersPerUnit();114 tileXMin = new int[getMaxZoom() + 1];115 tileYMin = new int[getMaxZoom() + 1];116 tileXMax = new int[getMaxZoom() + 1];117 tileYMax = new int[getMaxZoom() + 1];118 degreesPerTile = new double[getMaxZoom() + 1];119 120 for (int zoom = 1; zoom <= getMaxZoom(); zoom++) {121 // use well known scale set "GoogleCompatibile" from OGC WMTS spec to calculate number of tiles per zoom level122 // this makes the zoom levels "glued" to standard TMS zoom levels123 degreesPerTile[zoom] = (SCALE_DENOMINATOR_ZOOM_LEVEL_1 / Math.pow(2d, zoom - 1d)) * crsScale;124 TileXY minTileIndex = eastNorthToTileXY(topLeft, zoom);125 tileXMin[zoom] = minTileIndex.getXIndex();126 tileYMin[zoom] = minTileIndex.getYIndex();127 TileXY maxTileIndex = eastNorthToTileXY(bottomRight, zoom);128 tileXMax[zoom] = maxTileIndex.getXIndex();129 tileYMax[zoom] = maxTileIndex.getYIndex();130 }131 62 } 132 63 … … 237 168 public String getTileId(int zoom, int tilex, int tiley) { 238 169 return getTileUrl(zoom, tilex, tiley); 239 }240 241 @Override242 public ICoordinate tileXYToLatLon(Tile tile) {243 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());244 }245 246 @Override247 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {248 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);249 }250 251 @Override252 public ICoordinate tileXYToLatLon(int x, int y, int zoom) {253 return Main.getProjection().eastNorth2latlon(getTileEastNorth(x, y, zoom)).toCoordinate();254 }255 256 @Override257 public TileXY latLonToTileXY(double lat, double lon, int zoom) {258 Projection proj = Main.getProjection();259 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon));260 return eastNorthToTileXY(enPoint, zoom);261 }262 263 private TileXY eastNorthToTileXY(EastNorth enPoint, int zoom) {264 double scale = getDegreesPerTile(zoom);265 return new TileXY(266 (enPoint.east() - anchorPosition.east()) / scale,267 (anchorPosition.north() - enPoint.north()) / scale268 );269 }270 271 @Override272 public TileXY latLonToTileXY(ICoordinate point, int zoom) {273 return latLonToTileXY(point.getLat(), point.getLon(), zoom);274 }275 276 @Override277 public int getTileXMax(int zoom) {278 return tileXMax[zoom];279 }280 281 @Override282 public int getTileXMin(int zoom) {283 return tileXMin[zoom];284 }285 286 @Override287 public int getTileYMax(int zoom) {288 return tileYMax[zoom];289 }290 291 @Override292 public int getTileYMin(int zoom) {293 return tileYMin[zoom];294 }295 296 @Override297 public Point latLonToXY(double lat, double lon, int zoom) {298 double scale = getDegreesPerTile(zoom) / getTileSize();299 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));300 return new Point(301 (int) Math.round((point.east() - anchorPosition.east()) / scale),302 (int) Math.round((anchorPosition.north() - point.north()) / scale)303 );304 }305 306 @Override307 public Point latLonToXY(ICoordinate point, int zoom) {308 return latLonToXY(point.getLat(), point.getLon(), zoom);309 }310 311 @Override312 public ICoordinate xyToLatLon(Point point, int zoom) {313 return xyToLatLon(point.x, point.y, zoom);314 }315 316 @Override317 public ICoordinate xyToLatLon(int x, int y, int zoom) {318 double scale = getDegreesPerTile(zoom) / getTileSize();319 Projection proj = Main.getProjection();320 EastNorth ret = new EastNorth(321 anchorPosition.east() + x * scale,322 anchorPosition.north() - y * scale323 );324 return proj.eastNorth2latlon(ret).toCoordinate();325 170 } 326 171 … … 363 208 this.baseUrl = output.toString(); 364 209 } 365 366 protected EastNorth getTileEastNorth(int x, int y, int z) {367 double scale = getDegreesPerTile(z);368 return new EastNorth(369 anchorPosition.east() + x * scale,370 anchorPosition.north() - y * scale371 );372 }373 374 private double getDegreesPerTile(int zoom) {375 return degreesPerTile[zoom];376 }377 210 } -
trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
r10568 r10990 8 8 import java.util.Arrays; 9 9 import java.util.List; 10 import java.util.Map;11 10 import java.util.Set; 12 11 import java.util.TreeSet; … … 20 19 import org.openstreetmap.josm.Main; 21 20 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 21 import org.openstreetmap.josm.data.imagery.AbstractWMSTileSource; 22 22 import org.openstreetmap.josm.data.imagery.ImageryInfo; 23 23 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; … … 36 36 * 37 37 */ 38 public class WMSLayer extends AbstractCachedTileSourceLayer< TemplatedWMSTileSource> {38 public class WMSLayer extends AbstractCachedTileSourceLayer<AbstractWMSTileSource> { 39 39 private static final String PREFERENCE_PREFIX = "imagery.wms"; 40 40 /** … … 81 81 82 82 @Override 83 protected TemplatedWMSTileSource getTileSource(ImageryInfo info) {83 protected AbstractWMSTileSource getTileSource(ImageryInfo info) { 84 84 if (info.getImageryType() == ImageryType.WMS && info.getUrl() != null) { 85 85 TemplatedWMSTileSource.checkUrl(info.getUrl()); 86 TemplatedWMSTileSource tileSource = new TemplatedWMSTileSource(info);86 AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(info); 87 87 info.setAttribution(tileSource); 88 88 return tileSource; … … 108 108 ImageryLayerInfo.addLayer(new ImageryInfo(info)); 109 109 } 110 }111 112 @Override113 protected Map<String, String> getHeaders(TemplatedWMSTileSource tileSource) {114 return tileSource.getHeaders();115 110 } 116 111
Note:
See TracChangeset
for help on using the changeset viewer.