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