001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.awt.Point; 005import java.text.DecimalFormat; 006import java.text.DecimalFormatSymbols; 007import java.text.NumberFormat; 008import java.util.Locale; 009 010import org.openstreetmap.gui.jmapviewer.Projected; 011import org.openstreetmap.gui.jmapviewer.Tile; 012import org.openstreetmap.gui.jmapviewer.TileXY; 013import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 014import org.openstreetmap.gui.jmapviewer.interfaces.IProjected; 015import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; 016import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; 017import org.openstreetmap.josm.data.Bounds; 018import org.openstreetmap.josm.data.ProjectionBounds; 019import org.openstreetmap.josm.data.coor.EastNorth; 020import org.openstreetmap.josm.data.coor.LatLon; 021import org.openstreetmap.josm.data.projection.Projection; 022 023/** 024 * Base class for different WMS tile sources those based on URL templates and those based on WMS endpoints 025 * @author Wiktor Niesiobędzki 026 * @since 10990 027 */ 028public abstract class AbstractWMSTileSource extends TMSTileSource { 029 030 static final NumberFormat LATLON_FORMAT = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US)); 031 032 private EastNorth anchorPosition; 033 private int[] tileXMin; 034 private int[] tileYMin; 035 private int[] tileXMax; 036 private int[] tileYMax; 037 private double[] degreesPerTile; 038 private static final double SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 5.59082264028718e08; 039 private Projection tileProjection; 040 041 /** 042 * Constructs a new {@code AbstractWMSTileSource}. 043 * @param info tile source info 044 * @param tileProjection the tile projection 045 */ 046 protected AbstractWMSTileSource(TileSourceInfo info, Projection tileProjection) { 047 super(info); 048 this.tileProjection = tileProjection; 049 } 050 051 private void initAnchorPosition(Projection proj) { 052 Bounds worldBounds = proj.getWorldBoundsLatLon(); 053 EastNorth min = proj.latlon2eastNorth(worldBounds.getMin()); 054 EastNorth max = proj.latlon2eastNorth(worldBounds.getMax()); 055 this.anchorPosition = new EastNorth(min.east(), max.north()); 056 } 057 058 public void setTileProjection(Projection tileProjection) { 059 this.tileProjection = tileProjection; 060 initProjection(); 061 } 062 063 public Projection getTileProjection() { 064 return this.tileProjection; 065 } 066 067 /** 068 * Initializes class with current projection in JOSM. This call is needed every time projection changes. 069 */ 070 public void initProjection() { 071 initProjection(this.tileProjection); 072 } 073 074 /** 075 * Initializes class with projection in JOSM. This call is needed every time projection changes. 076 * @param proj new projection that shall be used for computations 077 */ 078 public void initProjection(Projection proj) { 079 initAnchorPosition(proj); 080 ProjectionBounds worldBounds = proj.getWorldBoundsBoxEastNorth(); 081 082 EastNorth topLeft = new EastNorth(worldBounds.getMin().east(), worldBounds.getMax().north()); 083 EastNorth bottomRight = new EastNorth(worldBounds.getMax().east(), worldBounds.getMin().north()); 084 085 // use 256 as "tile size" to keep the scale in line with default tiles in Mercator projection 086 double crsScale = 256 * 0.28e-03 / proj.getMetersPerUnit(); 087 tileXMin = new int[getMaxZoom() + 1]; 088 tileYMin = new int[getMaxZoom() + 1]; 089 tileXMax = new int[getMaxZoom() + 1]; 090 tileYMax = new int[getMaxZoom() + 1]; 091 degreesPerTile = new double[getMaxZoom() + 1]; 092 093 for (int zoom = 1; zoom <= getMaxZoom(); zoom++) { 094 // use well known scale set "GoogleCompatible" from OGC WMTS spec to calculate number of tiles per zoom level 095 // this makes the zoom levels "glued" to standard TMS zoom levels 096 degreesPerTile[zoom] = (SCALE_DENOMINATOR_ZOOM_LEVEL_1 / Math.pow(2d, zoom - 1d)) * crsScale; 097 TileXY minTileIndex = eastNorthToTileXY(topLeft, zoom); 098 tileXMin[zoom] = minTileIndex.getXIndex(); 099 tileYMin[zoom] = minTileIndex.getYIndex(); 100 TileXY maxTileIndex = eastNorthToTileXY(bottomRight, zoom); 101 tileXMax[zoom] = maxTileIndex.getXIndex(); 102 tileYMax[zoom] = maxTileIndex.getYIndex(); 103 } 104 } 105 106 @Override 107 public ICoordinate tileXYToLatLon(Tile tile) { 108 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom()); 109 } 110 111 @Override 112 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) { 113 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom); 114 } 115 116 @Override 117 public ICoordinate tileXYToLatLon(int x, int y, int zoom) { 118 return CoordinateConversion.llToCoor(tileProjection.eastNorth2latlon(getTileEastNorth(x, y, zoom))); 119 } 120 121 private TileXY eastNorthToTileXY(EastNorth enPoint, int zoom) { 122 double scale = getDegreesPerTile(zoom); 123 return new TileXY( 124 (enPoint.east() - anchorPosition.east()) / scale, 125 (anchorPosition.north() - enPoint.north()) / scale 126 ); 127 } 128 129 @Override 130 public TileXY latLonToTileXY(double lat, double lon, int zoom) { 131 EastNorth enPoint = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 132 return eastNorthToTileXY(enPoint, zoom); 133 } 134 135 @Override 136 public TileXY latLonToTileXY(ICoordinate point, int zoom) { 137 return latLonToTileXY(point.getLat(), point.getLon(), zoom); 138 } 139 140 @Override 141 public int getTileXMax(int zoom) { 142 return tileXMax[zoom]; 143 } 144 145 @Override 146 public int getTileXMin(int zoom) { 147 return tileXMin[zoom]; 148 } 149 150 @Override 151 public int getTileYMax(int zoom) { 152 return tileYMax[zoom]; 153 } 154 155 @Override 156 public int getTileYMin(int zoom) { 157 return tileYMin[zoom]; 158 } 159 160 @Override 161 public Point latLonToXY(double lat, double lon, int zoom) { 162 double scale = getDegreesPerTile(zoom) / getTileSize(); 163 EastNorth point = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 164 return new Point( 165 (int) Math.round((point.east() - anchorPosition.east()) / scale), 166 (int) Math.round((anchorPosition.north() - point.north()) / scale) 167 ); 168 } 169 170 @Override 171 public Point latLonToXY(ICoordinate point, int zoom) { 172 return latLonToXY(point.getLat(), point.getLon(), zoom); 173 } 174 175 @Override 176 public ICoordinate xyToLatLon(Point point, int zoom) { 177 return xyToLatLon(point.x, point.y, zoom); 178 } 179 180 @Override 181 public ICoordinate xyToLatLon(int x, int y, int zoom) { 182 double scale = getDegreesPerTile(zoom) / getTileSize(); 183 EastNorth ret = new EastNorth( 184 anchorPosition.east() + x * scale, 185 anchorPosition.north() - y * scale 186 ); 187 return CoordinateConversion.llToCoor(tileProjection.eastNorth2latlon(ret)); 188 } 189 190 protected EastNorth getTileEastNorth(int x, int y, int z) { 191 double scale = getDegreesPerTile(z); 192 return new EastNorth( 193 anchorPosition.east() + x * scale, 194 anchorPosition.north() - y * scale 195 ); 196 } 197 198 private double getDegreesPerTile(int zoom) { 199 return degreesPerTile[zoom]; 200 } 201 202 @Override 203 public IProjected tileXYtoProjected(int x, int y, int zoom) { 204 EastNorth en = getTileEastNorth(x, y, zoom); 205 return new Projected(en.east(), en.north()); 206 } 207 208 @Override 209 public TileXY projectedToTileXY(IProjected p, int zoom) { 210 return eastNorthToTileXY(new EastNorth(p.getEast(), p.getNorth()), zoom); 211 } 212 213 @Override 214 public String getServerCRS() { 215 return this.tileProjection.toCode(); 216 } 217 218 protected String getBbox(int zoom, int tilex, int tiley, boolean switchLatLon) { 219 EastNorth nw = getTileEastNorth(tilex, tiley, zoom); 220 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom); 221 222 double w = nw.getX(); 223 double n = nw.getY(); 224 225 double s = se.getY(); 226 double e = se.getX(); 227 228 return switchLatLon ? 229 getBboxstr(s, w, n, e) 230 : getBboxstr(w, s, e, n); 231 } 232 233 private static String getBboxstr(double x1, double x2, double x3, double x4) { 234 return new StringBuilder(64) 235 .append(LATLON_FORMAT.format(x1)) 236 .append(',') 237 .append(LATLON_FORMAT.format(x2)) 238 .append(',') 239 .append(LATLON_FORMAT.format(x3)) 240 .append(',') 241 .append(LATLON_FORMAT.format(x4)) 242 .toString(); 243 } 244}