/*
* Copyright 2012 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.map;
import org.oscim.core.BoundingBox;
import org.oscim.core.Box;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Point;
import org.oscim.core.Tile;
import org.oscim.renderer.GLMatrix;
import org.oscim.utils.FastMath;
/**
* The Viewport class contains a MapPosition and the projection matrices.
* It provides functions to modify the MapPosition and translate between
* map and screen coordinates.
* <p>
* Public methods are thread safe.
*/
public class Viewport {
//static final Logger log = LoggerFactory.getLogger(Viewport.class);
public final static int MAX_ZOOMLEVEL = 20;
public final static int MIN_ZOOMLEVEL = 2;
public final static double MAX_SCALE = (1 << MAX_ZOOMLEVEL);
public final static double MIN_SCALE = (1 << MIN_ZOOMLEVEL);
public final static float MAX_TILT = 65;
protected final MapPosition mPos = new MapPosition();
protected final GLMatrix mProjMatrix = new GLMatrix();
protected final GLMatrix mProjMatrixUnscaled = new GLMatrix();
protected final GLMatrix mProjMatrixInverse = new GLMatrix();
protected final GLMatrix mRotationMatrix = new GLMatrix();
protected final GLMatrix mViewMatrix = new GLMatrix();
protected final GLMatrix mViewProjMatrix = new GLMatrix();
protected final GLMatrix mUnprojMatrix = new GLMatrix();
protected final GLMatrix mTmpMatrix = new GLMatrix();
/* temporary vars: only use in synchronized functions! */
protected final Point mMovePoint = new Point();
protected final float[] mv = new float[4];
protected final float[] mu = new float[4];
protected final float[] mViewCoords = new float[8];
protected final Box mMapBBox = new Box();
protected float mHeight, mWidth;
public final static float VIEW_DISTANCE = 3.0f;
public final static float VIEW_NEAR = 1;
public final static float VIEW_FAR = 8;
/** scale map plane at VIEW_DISTANCE to near plane */
public final static float VIEW_SCALE = (VIEW_NEAR / VIEW_DISTANCE) * 0.5f;
protected Viewport() {
mPos.scale = MIN_SCALE;
mPos.x = 0.5;
mPos.y = 0.5;
mPos.bearing = 0;
mPos.tilt = 0;
}
/**
* Get the current MapPosition.
*
* @param pos MapPosition to be updated.
*
* @return true iff current position is different from
* passed position.
*/
public synchronized boolean getMapPosition(MapPosition pos) {
boolean changed = (pos.scale != mPos.scale
|| pos.x != mPos.x
|| pos.y != mPos.y
|| pos.bearing != mPos.bearing
|| pos.tilt != mPos.tilt);
pos.bearing = mPos.bearing;
pos.tilt = mPos.tilt;
pos.x = mPos.x;
pos.y = mPos.y;
pos.scale = mPos.scale;
pos.zoomLevel = FastMath.log2((int) mPos.scale);
return changed;
}
/**
* Get the inverse projection of the viewport, i.e. the
* coordinates with z==0 that will be projected exactly
* to screen corners by current view-projection-matrix.
*
* @param box float[8] will be set.
* @param add increase extents of box
*/
public synchronized void getMapExtents(float[] box, float add) {
float t = getDepth(1);
float t2 = getDepth(-1);
// top-right
unproject(1, -1, t, box, 0);
// top-left
unproject(-1, -1, t, box, 2);
// bottom-left
unproject(-1, 1, t2, box, 4);
// bottom-right
unproject(1, 1, t2, box, 6);
if (add == 0)
return;
for (int i = 0; i < 8; i += 2) {
float x = box[i];
float y = box[i + 1];
float len = (float) Math.sqrt(x * x + y * y);
box[i + 0] += x / len * add;
box[i + 1] += y / len * add;
}
}
/**
* Get Z-value of the map-plane for a point on screen -
* calculate the intersection of a ray from camera origin
* and the map plane
*/
protected float getDepth(float y) {
// origin is moved by VIEW_DISTANCE
double cx = VIEW_DISTANCE;
// 'height' of the ray
double ry = y * (mHeight / mWidth) * 0.5f;
double ua;
if (y == 0)
ua = 1;
else {
// tilt of the plane (center is kept on x = 0)
double t = Math.toRadians(mPos.tilt);
double px = y * Math.sin(t);
double py = y * Math.cos(t);
ua = 1 + (px * ry) / (py * cx);
}
mv[0] = 0;
mv[1] = (float) (ry / ua);
mv[2] = (float) (cx - cx / ua);
mProjMatrixUnscaled.prj(mv);
return mv[2];
}
protected void unproject(float x, float y, float z, float[] coords, int position) {
mv[0] = x;
mv[1] = y;
mv[2] = z;
mUnprojMatrix.prj(mv);
coords[position + 0] = mv[0];
coords[position + 1] = mv[1];
}
/**
* Get the minimal axis-aligned BoundingBox that encloses
* the visible part of the map.
*
* @return BoundingBox containing view
*/
public synchronized BoundingBox getBBox(int expand) {
getBBox(mMapBBox, expand);
/* scale map-pixel coordinates at current scale to
* absolute coordinates and apply mercator projection. */
double minLon = MercatorProjection.toLongitude(mMapBBox.xmin);
double maxLon = MercatorProjection.toLongitude(mMapBBox.xmax);
double minLat = MercatorProjection.toLatitude(mMapBBox.ymax);
double maxLat = MercatorProjection.toLatitude(mMapBBox.ymin);
return new BoundingBox(minLat, minLon, maxLat, maxLon);
}
public synchronized BoundingBox getBBox() {
return getBBox(0);
}
/**
* Get the minimal axis-aligned BoundingBox that encloses
* the visible part of the map. Sets box to map coordinates:
* xmin,ymin,ymax,ymax
*/
public synchronized void getBBox(Box box, int expand) {
float[] coords = mViewCoords;
getMapExtents(coords, expand);
box.xmin = coords[0];
box.xmax = coords[0];
box.ymin = coords[1];
box.ymax = coords[1];
for (int i = 2; i < 8; i += 2) {
box.xmin = Math.min(box.xmin, coords[i]);
box.xmax = Math.max(box.xmax, coords[i]);
box.ymin = Math.min(box.ymin, coords[i + 1]);
box.ymax = Math.max(box.ymax, coords[i + 1]);
}
//updatePosition();
double cs = mPos.scale * Tile.SIZE;
double cx = mPos.x * cs;
double cy = mPos.y * cs;
box.xmin = (cx + box.xmin) / cs;
box.xmax = (cx + box.xmax) / cs;
box.ymin = (cy + box.ymin) / cs;
box.ymax = (cy + box.ymax) / cs;
}
/**
* Get the GeoPoint for x,y in screen coordinates.
*
* @param x screen coordinate
* @param y screen coordinate
* @return the corresponding GeoPoint
*/
public synchronized GeoPoint fromScreenPoint(float x, float y) {
fromScreenPoint(x, y, mMovePoint);
return new GeoPoint(
MercatorProjection.toLatitude(mMovePoint.y),
MercatorProjection.toLongitude(mMovePoint.x));
}
/**
* Get the map position for x,y in screen coordinates.
*
* @param x screen coordinate
* @param y screen coordinate
*/
public synchronized void fromScreenPoint(double x, double y, Point out) {
// scale to -1..1
float mx = (float) (1 - (x / mWidth * 2));
float my = (float) (1 - (y / mHeight * 2));
unproject(-mx, my, getDepth(-my), mu, 0);
double cs = mPos.scale * Tile.SIZE;
double cx = mPos.x * cs;
double cy = mPos.y * cs;
double dx = cx + mu[0];
double dy = cy + mu[1];
dx /= cs;
dy /= cs;
while (dx > 1)
dx -= 1;
while (dx < 0)
dx += 1;
if (dy > 1)
dy = 1;
else if (dy < 0)
dy = 0;
out.x = dx;
out.y = dy;
}
/**
* Get the screen pixel for a GeoPoint
*
* @param geoPoint the GeoPoint
* @param out Point projected to screen pixel relative to center
*/
public synchronized void toScreenPoint(GeoPoint geoPoint, Point out) {
MercatorProjection.project(geoPoint, out);
toScreenPoint(out.x, out.y, out);
}
/**
* Get the screen pixel for map coordinates
*
* @param out Point projected to screen coordinate
*/
public synchronized void toScreenPoint(double x, double y, Point out) {
double cs = mPos.scale * Tile.SIZE;
double cx = mPos.x * cs;
double cy = mPos.y * cs;
mv[0] = (float) (x * cs - cx);
mv[1] = (float) (y * cs - cy);
mv[2] = 0;
mv[3] = 1;
mViewProjMatrix.prj(mv);
out.x = (mv[0] * (mWidth / 2));
out.y = -(mv[1] * (mHeight / 2));
}
public synchronized boolean copy(Viewport viewport) {
mUnprojMatrix.copy(viewport.mUnprojMatrix);
mRotationMatrix.copy(viewport.mRotationMatrix);
mViewMatrix.copy(viewport.mViewMatrix);
mViewProjMatrix.copy(viewport.mViewProjMatrix);
return viewport.getMapPosition(mPos);
}
public synchronized void initFrom(Viewport viewport) {
mProjMatrix.copy(viewport.mProjMatrix);
mProjMatrixUnscaled.copy(viewport.mProjMatrixUnscaled);
mProjMatrixInverse.copy(viewport.mProjMatrixInverse);
mHeight = viewport.mHeight;
mWidth = viewport.mWidth;
}
}