/*
* Copyright 2010, 2011, 2012 mapsforge.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.mapsforge.android.maps;
import org.mapsforge.android.maps.inputhandling.ZoomAnimator;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.GeoPoint;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.MercatorProjection;
import android.graphics.Point;
/**
* A MapViewPosition stores the latitude and longitude coordinates of a MapView together with its zoom level.
*/
public class MapViewPosition {
private double latitude;
private double longitude;
private final MapView mapView;
private byte zoomLevel;
MapViewPosition(MapView mapView) {
this.mapView = mapView;
this.latitude = 0;
this.longitude = 0;
this.zoomLevel = 0;
}
/**
* @return the currently visible boundaries of the map.
*/
public synchronized BoundingBox getBoundingBox() {
double pixelX = MercatorProjection.longitudeToPixelX(this.longitude, this.zoomLevel);
double pixelY = MercatorProjection.latitudeToPixelY(this.latitude, this.zoomLevel);
int halfCanvasWidth = this.mapView.getWidth() / 2;
int halfCanvasHeight = this.mapView.getHeight() / 2;
long mapSize = MercatorProjection.getMapSize(this.zoomLevel);
double pixelXMin = Math.max(0, pixelX - halfCanvasWidth);
double pixelYMin = Math.max(0, pixelY - halfCanvasHeight);
double pixelXMax = Math.min(mapSize, pixelX + halfCanvasWidth);
double pixelYMax = Math.min(mapSize, pixelY + halfCanvasHeight);
double minLatitude = MercatorProjection.pixelYToLatitude(pixelYMax, this.zoomLevel);
double minLongitude = MercatorProjection.pixelXToLongitude(pixelXMin, this.zoomLevel);
double maxLatitude = MercatorProjection.pixelYToLatitude(pixelYMin, this.zoomLevel);
double maxLongitude = MercatorProjection.pixelXToLongitude(pixelXMax, this.zoomLevel);
return new BoundingBox(minLatitude, minLongitude, maxLatitude, maxLongitude);
}
/**
* @return the current center position of the map.
*/
public synchronized GeoPoint getCenter() {
return new GeoPoint(this.latitude, this.longitude);
}
/**
* @return the current center position and zoom level of the map.
*/
public synchronized MapPosition getMapPosition() {
return new MapPosition(getCenter(), this.zoomLevel);
}
/**
* @return the current zoom level of the map.
*/
public synchronized byte getZoomLevel() {
return this.zoomLevel;
}
/**
* Moves the center position of the map by the given amount of pixels without an animation.
*
* @param moveHorizontal
* the amount of pixels to move this MapViewPosition horizontally.
* @param moveVertical
* the amount of pixels to move this MapViewPosition vertically.
*/
public void moveCenter(float moveHorizontal, float moveVertical) {
synchronized (this) {
double pixelX = MercatorProjection.longitudeToPixelX(this.longitude, this.zoomLevel) - moveHorizontal;
double pixelY = MercatorProjection.latitudeToPixelY(this.latitude, this.zoomLevel) - moveVertical;
long mapSize = MercatorProjection.getMapSize(this.zoomLevel);
pixelX = Math.min(Math.max(0, pixelX), mapSize);
pixelY = Math.min(Math.max(0, pixelY), mapSize);
double newLatitude = MercatorProjection.pixelYToLatitude(pixelY, this.zoomLevel);
double newLongitude = MercatorProjection.pixelXToLongitude(pixelX, this.zoomLevel);
setCenterInternal(new GeoPoint(newLatitude, newLongitude));
}
this.mapView.redraw();
}
/**
* Sets the new center position of the map without an animation.
*
* @param geoPoint
* the new center position of the map.
*/
public void setCenter(GeoPoint geoPoint) {
setCenterInternal(geoPoint);
this.mapView.redraw();
}
/**
* Sets the new center position and zoom level of the map without an animation.
*
* @param mapPosition
* the new center position and zoom level of the map.
*/
public void setMapPosition(MapPosition mapPosition) {
synchronized (this) {
setCenterInternal(mapPosition.geoPoint);
setZoomLevelInternal(mapPosition.zoomLevel);
}
this.mapView.redraw();
}
/**
* Sets the new zoom level of the map without an animation.
*
* @param zoomLevel
* the new zoom level of the map.
* @throws IllegalArgumentException
* if the given zoom level is negative.
*/
public void setZoomLevel(byte zoomLevel) {
setZoomLevelInternal(zoomLevel);
this.mapView.redraw();
}
/**
* Starts an animation to increase or decrease the current zoom level.
*
* @param zoomLevelDiff
* the difference to the current zoom level.
* @param scaleFactorStart
* the scale factor at the begin of the animation.
*/
public void zoom(byte zoomLevelDiff, float scaleFactorStart) {
float scaleFactorEnd = setZoomLevelDiff(zoomLevelDiff);
int pivotX = this.mapView.getWidth() / 2;
int pivotY = this.mapView.getHeight() / 2;
ZoomAnimator zoomAnimator = this.mapView.getZoomAnimator();
zoomAnimator.startAnimation(scaleFactorStart, scaleFactorEnd, pivotX, pivotY);
}
/**
* Starts an animation to increase the current zoom level by one.
*/
public void zoomIn() {
zoom((byte) 1, 1);
}
/**
* Starts an animation to decrease the current zoom level by one.
*/
public void zoomOut() {
zoom((byte) -1, 1);
}
private byte limitZoomLevel(byte newZoomLevel) {
byte zoomLevelMin = this.mapView.getMapZoomControls().getZoomLevelMin();
byte zoomLevelMax = this.mapView.getZoomLevelMax();
return (byte) Math.max(Math.min(newZoomLevel, zoomLevelMax), zoomLevelMin);
}
private void setCenterInternal(GeoPoint geoPoint) {
MapPosition mapPositionBefore = getMapPosition();
synchronized (this) {
this.latitude = geoPoint.latitude;
this.longitude = geoPoint.longitude;
}
Projection projection = this.mapView.getProjection();
Point pointBefore = projection.toPoint(mapPositionBefore.geoPoint, null, mapPositionBefore.zoomLevel);
Point pointAfter = projection.toPoint(getCenter(), null, mapPositionBefore.zoomLevel);
FrameBuffer frameBuffer = this.mapView.getFrameBuffer();
frameBuffer.matrixPostTranslate(pointBefore.x - pointAfter.x, pointBefore.y - pointAfter.y);
}
private synchronized float setZoomLevelDiff(byte zoomLevelDiff) {
return setZoomLevelNew((byte) (this.zoomLevel + zoomLevelDiff));
}
private void setZoomLevelInternal(byte zoomLevelNew) {
float scaleFactor = setZoomLevelNew(zoomLevelNew);
int pivotX = this.mapView.getWidth() / 2;
int pivotY = this.mapView.getHeight() / 2;
FrameBuffer frameBuffer = this.mapView.getFrameBuffer();
frameBuffer.matrixPostScale(scaleFactor, scaleFactor, pivotX, pivotY);
}
private synchronized float setZoomLevelNew(byte zoomLevelUnlimited) {
byte zoomLevelOld = this.zoomLevel;
byte zoomLevelNew = limitZoomLevel(zoomLevelUnlimited);
if (zoomLevelNew == zoomLevelOld) {
return 1;
}
this.zoomLevel = zoomLevelNew;
this.mapView.getMapZoomControls().onZoomLevelChange(zoomLevelNew);
return (float) Math.pow(2, zoomLevelNew - zoomLevelOld);
}
}