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