424 lines
12 KiB
Java
424 lines
12 KiB
Java
|
|
/**
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
package viewer.hoese;
|
||
|
|
|
||
|
|
import java.awt.geom.AffineTransform;
|
||
|
|
import java.awt.geom.Rectangle2D;
|
||
|
|
import java.net.URL;
|
||
|
|
import java.util.LinkedList;
|
||
|
|
|
||
|
|
import tools.Pair;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @author duentgen
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
public class StaticOSMMapper implements Rect2UrlMapper {
|
||
|
|
|
||
|
|
public StaticOSMMapper(int tileSizeX, int tileSizeY, int DIM_X, int DIM_Y,
|
||
|
|
int minZoomLevel, int maxZoomLevel, URL baseUrl, String prefix) {
|
||
|
|
this.tileSizeX = tileSizeX;
|
||
|
|
this.tileSizeY = tileSizeY;
|
||
|
|
this.DIM_X = DIM_X;
|
||
|
|
this.DIM_Y = DIM_Y;
|
||
|
|
this.minZoomLevel = minZoomLevel;
|
||
|
|
this.maxZoomLevel = maxZoomLevel;
|
||
|
|
this.baseUrl = baseUrl;
|
||
|
|
this.prefix = prefix;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Computes a url from the x,y indexes at specified zoom level.
|
||
|
|
* Additionally, a affine transformation is computed to map the image to be
|
||
|
|
* retrieved from the url to the proper location within the world.
|
||
|
|
*
|
||
|
|
* @see viewer.hoese.Rect2UrlMapper#computeURLs(java.awt.geom.Rectangle2D.Double)
|
||
|
|
* @param x
|
||
|
|
* X index of a tile
|
||
|
|
* @param y
|
||
|
|
* Y index of a tile
|
||
|
|
* @param z
|
||
|
|
* zoom level
|
||
|
|
* @return A pair of image URL and affine transformation to shift it to its
|
||
|
|
* proper location and zoom it according to zoom level and visible
|
||
|
|
* screen
|
||
|
|
**/
|
||
|
|
protected Pair<URL, AffineTransform> computeURL(int x, int y, int z) {
|
||
|
|
URL url;
|
||
|
|
try {
|
||
|
|
url = new URL(baseUrl, "" + z + "/" + x + "/" + y + ".png");
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
Rectangle2D.Double r = getBBoxForTile(x, y, z);
|
||
|
|
AffineTransform at = computeTransform(r);
|
||
|
|
|
||
|
|
return new Pair<URL, AffineTransform>(url, at);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected URL getURL(int x, int y, int z){
|
||
|
|
try {
|
||
|
|
return new URL(baseUrl, "" + z + "/" + x + "/" + y + ".png");
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/** computes the affine transformation to map an rectangle (0,0,tileSizeX, tileSizeY)
|
||
|
|
* to the given rectangle.
|
||
|
|
**/
|
||
|
|
protected AffineTransform computeTransform(Rectangle2D.Double r) {
|
||
|
|
// compute projected bounding box
|
||
|
|
java.awt.geom.Point2D.Double p1 = new java.awt.geom.Point2D.Double();
|
||
|
|
java.awt.geom.Point2D.Double p2 = new java.awt.geom.Point2D.Double();
|
||
|
|
|
||
|
|
ProjectionManager.projectWithoutScale(r.getX(), r.getY(), p1);
|
||
|
|
ProjectionManager.projectWithoutScale(r.getX() + r.getWidth(), r.getY() + r.getHeight(), p2);
|
||
|
|
|
||
|
|
r.setRect(p1.getX(), p1.getY(), p2.getX() - p1.getX(),
|
||
|
|
p2.getY() - p1.getY());
|
||
|
|
|
||
|
|
double scale_x = r.getWidth() / (double) tileSizeX;
|
||
|
|
double scale_y = -1.0 * r.getHeight() / (double) tileSizeY;
|
||
|
|
|
||
|
|
AffineTransform at = AffineTransform.getTranslateInstance(r.getX(),
|
||
|
|
r.getY() + r.getHeight());
|
||
|
|
at.scale(scale_x, scale_y);
|
||
|
|
return at;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Computes the urls required to cover at least the given rectangle.
|
||
|
|
*
|
||
|
|
* @see viewer.hoese.Rect2UrlMapper#computeURLs(java.awt.geom.Rectangle2D.Double)
|
||
|
|
* @param bbox
|
||
|
|
* the rectangle to be covered in world coordinates
|
||
|
|
* @return The list of all visible map tiles and according translation/scale
|
||
|
|
* matrices
|
||
|
|
**/
|
||
|
|
public LinkedList<Pair<URL, AffineTransform>> computeURLs(
|
||
|
|
Rectangle2D.Double bbox, AffineTransform world2screen ) {
|
||
|
|
return computeURLs(bbox,false, world2screen);
|
||
|
|
}
|
||
|
|
|
||
|
|
public LinkedList<Pair<URL, AffineTransform>> computeURLs(
|
||
|
|
Rectangle2D.Double bbox , boolean first, AffineTransform world2screen) {
|
||
|
|
if (bbox == null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bbox.equals(lastClipRect)) {
|
||
|
|
return lastURLs;
|
||
|
|
}
|
||
|
|
|
||
|
|
int zoom = computeZoomLevel(bbox.getWidth(), bbox.getHeight(), world2screen);
|
||
|
|
|
||
|
|
int x1 = getTileX(zoom, bbox.getX());
|
||
|
|
int x2 = getTileX(zoom, bbox.getX() + bbox.getWidth());
|
||
|
|
int y1 = getTileY(zoom, bbox.getY() + bbox.getHeight());
|
||
|
|
int y2 = getTileY(zoom, bbox.getY());
|
||
|
|
|
||
|
|
if (x1 < 0) {
|
||
|
|
x1 = 0;
|
||
|
|
}
|
||
|
|
if (x2 > ((1 << zoom) - 1)) {
|
||
|
|
x2 = (1 << zoom) - 1;
|
||
|
|
}
|
||
|
|
if (y1 < 0) {
|
||
|
|
y1 = 0;
|
||
|
|
}
|
||
|
|
if (y2 > ((1 << zoom) - 1)) {
|
||
|
|
y2 = (1 << zoom) - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
LinkedList<Pair<URL, AffineTransform>> res = new LinkedList<Pair<URL, AffineTransform>>();
|
||
|
|
for (int x = x1; x <= x2; x++) {
|
||
|
|
for (int y = y1; y <= y2; y++) {
|
||
|
|
if(isValidTile(x,y,zoom)){
|
||
|
|
Pair<URL, AffineTransform> p = computeURL(x, y, zoom);
|
||
|
|
if (p != null) {
|
||
|
|
res.add(p);
|
||
|
|
if(first){
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
System.err
|
||
|
|
.println("Problem in computinmg URL from (x,y,z) = ("
|
||
|
|
+ x + ", " + y + ", " + zoom + ")");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
lastURLs = res;
|
||
|
|
lastClipRect = (Rectangle2D) bbox.clone();
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public double getAntiScale(AffineTransform at, Rectangle2D clipRect){
|
||
|
|
// at transforms world to screen
|
||
|
|
|
||
|
|
clipRect = toGeoCoords(clipRect);
|
||
|
|
|
||
|
|
int z = computeZoomLevel(clipRect.getWidth(), clipRect.getHeight(), at);
|
||
|
|
Rectangle2D.Double r = getBBoxForTile(1, 1, z);
|
||
|
|
AffineTransform tile2world = computeTransform(r);
|
||
|
|
|
||
|
|
AffineTransform world2Screen = new AffineTransform(at);
|
||
|
|
world2Screen.concatenate(tile2world);
|
||
|
|
double sc = world2Screen.getScaleX();
|
||
|
|
if(sc==0){
|
||
|
|
return 1.0;
|
||
|
|
}
|
||
|
|
return 1.0/sc;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public Rectangle2D.Double toGeoCoords(Rectangle2D r){
|
||
|
|
if (ProjectionManager.isReversible()) {
|
||
|
|
java.awt.geom.Point2D.Double p1 = new java.awt.geom.Point2D.Double();
|
||
|
|
java.awt.geom.Point2D.Double p2 = new java.awt.geom.Point2D.Double();
|
||
|
|
|
||
|
|
ProjectionManager.getOrigWithoutScale(r.getX(), r.getY(), p1);
|
||
|
|
ProjectionManager.getOrigWithoutScale(r.getX() + r.getWidth(),
|
||
|
|
r.getY() + r.getHeight(), p2);
|
||
|
|
return new Rectangle2D.Double(p1.getX(), p1.getY(), p2.getX() - p1.getX(),
|
||
|
|
p2.getY() - p1.getY());
|
||
|
|
} else {
|
||
|
|
return new Rectangle2D.Double(r.getX(),r.getY(),r.getWidth(),r.getHeight());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public int numberOfPreloadFiles(Rectangle2D.Double clipRect){
|
||
|
|
int res = 0;
|
||
|
|
clipRect = toGeoCoords(clipRect);
|
||
|
|
for(int i=minZoomLevel; i<=maxZoomLevel;i++){
|
||
|
|
res += numberOfPreloadFiles(clipRect,i);
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public LinkedList<URL> getPreloadURLs(Rectangle2D.Double clipRect){
|
||
|
|
LinkedList<URL> res = new LinkedList<URL>();
|
||
|
|
clipRect = toGeoCoords(clipRect);
|
||
|
|
for(int i=minZoomLevel; i<=maxZoomLevel;i++){
|
||
|
|
getPreloadURLs(clipRect,i, res);
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private int numberOfPreloadFiles(Rectangle2D.Double clipRect, int zoom){
|
||
|
|
double x1 = clipRect.getX();
|
||
|
|
double x2 = x1 + clipRect.getWidth();
|
||
|
|
double y1 = clipRect.getY();
|
||
|
|
double y2 = y1 + clipRect.getHeight();
|
||
|
|
int dx = Math.abs(getTileX(zoom,x2) - getTileX(zoom,x1));
|
||
|
|
int dy = Math.abs(getTileY(zoom,y2) - getTileY(zoom,y1));
|
||
|
|
return (dx+1)*(dy+1);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void getPreloadURLs(Rectangle2D.Double clipRect, int zoom, LinkedList<URL> result){
|
||
|
|
double x1 = clipRect.getX();
|
||
|
|
double x2 = x1 + clipRect.getWidth();
|
||
|
|
double y1 = clipRect.getY();
|
||
|
|
double y2 = y1 + clipRect.getHeight();
|
||
|
|
for(int x = getTileX(zoom,x1); x<= getTileX(zoom,x2); x++){
|
||
|
|
for(int y = getTileY(zoom,y2); y <= getTileY(zoom,y1); y++){
|
||
|
|
result.add( getURL(x,y,zoom));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
private boolean isValidTile(int x, int y, int zoom){
|
||
|
|
if(x<0 || y<0){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
int numTiles = 1 << zoom;
|
||
|
|
if(x>numTiles || y>numTiles){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* returns the X index of a tile covering longitude at specified zoom level.
|
||
|
|
*
|
||
|
|
* @param zoom
|
||
|
|
* the used zoom level
|
||
|
|
* @param lon
|
||
|
|
* longitude
|
||
|
|
* @return the X-index of the tile covering the given longitude
|
||
|
|
**/
|
||
|
|
private int getTileX(int zoom, double lon) {
|
||
|
|
return (int) Math.floor((lon + 180) / 360 * (1 << zoom));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* returns the Y index of a tile covering latitude at specified zoom level.
|
||
|
|
*
|
||
|
|
* @param zoom
|
||
|
|
* the used zoom level
|
||
|
|
* @param lat
|
||
|
|
* latitude
|
||
|
|
* @return the Y-index of the tile covering the given latitude
|
||
|
|
**/
|
||
|
|
private int getTileY(int zoom, double lat) {
|
||
|
|
return (int) Math.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1
|
||
|
|
/ Math.cos(Math.toRadians(lat)))
|
||
|
|
/ Math.PI)
|
||
|
|
/ 2 * (1 << zoom));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* compute a zoom level for a given size within the world.
|
||
|
|
*
|
||
|
|
* @param witdh
|
||
|
|
* size in x dimension within the world
|
||
|
|
* @param height
|
||
|
|
* size in y dimension within the world
|
||
|
|
* @return The recommended zoom level
|
||
|
|
**/
|
||
|
|
protected int computeZoomLevel(double width, double height, AffineTransform at) {
|
||
|
|
|
||
|
|
// DIM_X : X-resolution of the screen
|
||
|
|
// DIM_Y : Y-resolution of the screen
|
||
|
|
// tileSizeX : x-size of a single tile
|
||
|
|
// tileSizeY : y-size of a single tile
|
||
|
|
// l2 : log(2)
|
||
|
|
|
||
|
|
double z_x = Math.log((360 * DIM_X) / (width * tileSizeX)) / l2;
|
||
|
|
|
||
|
|
double z_y = Math.log((180 * DIM_Y) / (height * tileSizeY)) / l2;
|
||
|
|
|
||
|
|
//System.out.println("z_x = " + z_x);
|
||
|
|
//System.out.println("z_y = " + z_y);
|
||
|
|
|
||
|
|
double z = 0;
|
||
|
|
|
||
|
|
boolean useX;
|
||
|
|
if(z_x<z_y){
|
||
|
|
z = z_x;
|
||
|
|
useX = true;
|
||
|
|
} else{
|
||
|
|
z = z_y;
|
||
|
|
useX = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int zoom = (int) z;
|
||
|
|
if(zoom < z){
|
||
|
|
zoom++;
|
||
|
|
}
|
||
|
|
zoom--;
|
||
|
|
|
||
|
|
if(zoom < minZoomLevel){
|
||
|
|
zoom = minZoomLevel;
|
||
|
|
}
|
||
|
|
if(zoom > maxZoomLevel){
|
||
|
|
zoom = maxZoomLevel;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(at!=null){
|
||
|
|
Rectangle2D.Double r = getBBoxForTile(1, 1, zoom);
|
||
|
|
AffineTransform tile2world = computeTransform(r);
|
||
|
|
AffineTransform world2Screen = new AffineTransform(at);
|
||
|
|
world2Screen.concatenate(tile2world);
|
||
|
|
double sc = world2Screen.getScaleX();
|
||
|
|
if(sc<0.5){
|
||
|
|
zoom--;
|
||
|
|
} else if(sc>1.5){
|
||
|
|
zoom++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
return zoom;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* compute the bounding box covered by a specified tile in world
|
||
|
|
* coordinates.
|
||
|
|
*
|
||
|
|
* @param x
|
||
|
|
* X index of a tile
|
||
|
|
* @param y
|
||
|
|
* Y index of a tile
|
||
|
|
* @param z
|
||
|
|
* zoom level
|
||
|
|
* @return The images's MBR in world coordinates
|
||
|
|
**/
|
||
|
|
protected Rectangle2D.Double getBBoxForTile(int x, int y, int zoom) {
|
||
|
|
double north = tile2lat(y, zoom);
|
||
|
|
double south = tile2lat(y + 1, zoom);
|
||
|
|
double west = tile2lon(x, zoom);
|
||
|
|
double east = tile2lon(x + 1, zoom);
|
||
|
|
return new Rectangle2D.Double(west, south, east - west, north - south);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* computes the western boundary of a specified tile in world coordinates.
|
||
|
|
*
|
||
|
|
* @param x
|
||
|
|
* The map tile's X-index
|
||
|
|
* @param z
|
||
|
|
* The zoom level
|
||
|
|
* @return The longitude of the western boundary of a map tile
|
||
|
|
**/
|
||
|
|
private static double tile2lon(int x, int z) {
|
||
|
|
return x / Math.pow(2.0, z) * 360.0 - 180;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* computes the northern boundary of a specified tile in world coordinates.
|
||
|
|
*
|
||
|
|
* @param x
|
||
|
|
* The map tile's Y-index
|
||
|
|
* @param z
|
||
|
|
* The zoom level
|
||
|
|
* @return The latitude of the nortehrn boundary of a map tile
|
||
|
|
**/
|
||
|
|
private static double tile2lat(int y, int z) {
|
||
|
|
double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
|
||
|
|
return Math.toDegrees(Math.atan(Math.sinh(n)));
|
||
|
|
}
|
||
|
|
|
||
|
|
public String toString() {
|
||
|
|
return "tileSizeX = " + tileSizeX + ", tileSizeY = " + tileSizeY
|
||
|
|
+ ", DIM_X = " + DIM_X + ", DIM_Y = " + DIM_Y
|
||
|
|
+ ", minZoomLevel = " + minZoomLevel + ", maxZoomLevel = "
|
||
|
|
+ maxZoomLevel + ", baseUrl = " + baseUrl;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** the last used bounding box **/
|
||
|
|
private Rectangle2D lastClipRect = null;
|
||
|
|
|
||
|
|
/** the urls according to the last used bounding box **/
|
||
|
|
LinkedList<Pair<URL, AffineTransform>> lastURLs;
|
||
|
|
|
||
|
|
int tileSizeX;
|
||
|
|
int tileSizeY;
|
||
|
|
int DIM_X;
|
||
|
|
int DIM_Y;
|
||
|
|
int minZoomLevel;
|
||
|
|
int maxZoomLevel;
|
||
|
|
URL baseUrl;
|
||
|
|
String prefix;
|
||
|
|
|
||
|
|
/** logarithm of 2 to save computation time **/
|
||
|
|
private static final double l2 = Math.log(2);
|
||
|
|
|
||
|
|
}
|