gt: 		ViewPort viewPort = mapPresenter.getViewPort();
context: /*
   * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
   *
   * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
   *
   * The program is available in open source according to the GNU Affero
   * General Public License. All contributions in this program are covered
   * by the Geomajas Contributors License Agreement. For full licensing
   * details, see LICENSE.txt in the project root.
   */
  
  package org.geomajas.gwt2.client.controller;
  
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.logging.Logger;
  
  import org.geomajas.annotation.Api;
  import org.geomajas.geometry.Coordinate;
  import org.geomajas.geometry.service.MathService;
  import org.geomajas.gwt.client.map.RenderSpace;
  import org.geomajas.gwt2.client.animation.KineticTrajectory;
  import org.geomajas.gwt2.client.animation.NavigationAnimationFactory;
  import org.geomajas.gwt2.client.animation.NavigationAnimationImpl;
  import org.geomajas.gwt2.client.map.MapPresenter;
  import org.geomajas.gwt2.client.map.View;
  import org.geomajas.gwt2.client.map.ViewPort;
  
  import com.google.gwt.dom.client.NativeEvent;
  import com.google.gwt.event.dom.client.DoubleClickEvent;
  import com.google.gwt.event.dom.client.HumanInputEvent;
  import com.google.gwt.event.dom.client.MouseDownEvent;
  import com.google.gwt.event.dom.client.MouseMoveEvent;
  import com.google.gwt.event.dom.client.MouseOutEvent;
  import com.google.gwt.event.dom.client.MouseWheelEvent;
  
  /**
   * Generic navigation map controller. This controller allows for panning and zooming on the map in many different ways.
   * Options are the following:
   * <ul>
   * <li><b>Panning</b>: Drag the map to pan.</li>
   * <li><b>Double click</b>: Double clicking on some location will see the map zoom in to that location.</li>
   * <li><b>Zoom to rectangle</b>: By holding shift or ctrl and dragging at the same time, a rectangle will appear on the
   * map. On mouse up, the map will than zoom to that rectangle.</li>
   * <li></li>
   * </ul>
   * For zooming using the mouse wheel there are 2 options, defined by the {@link ScrollZoomType} enum. These options are
   * the following:
   * <ul>
   * <li><b>ScrollZoomType.ZOOM_CENTER</b>: Zoom in/out so that the current center of the map remains.</li>
   * <li><b>ScrollZoomType.ZOOM_POSITION</b>: Zoom in/out so that the mouse world position remains the same. Can be great
   * for many subsequent double clicks, to make sure you keep zooming to the same location (wherever the mouse points to).
   * </li>
   * </ul>
   * 
   * @author Pieter De Graef
   * @since 2.0.0
   */
  @Api(allMethods = true)
  public class NavigationController extends AbstractMapController {
  
  	private static Logger logger = Logger.getLogger("MapPresenterImpl");
  
  	/** Zooming types on mouse wheel scroll. */
  	public static enum ScrollZoomType {
  		/** When scroll zooming, retain the center of the map position. */
  		ZOOM_CENTER,
  
  		/** When scroll zooming, retain the mouse position. */
  		ZOOM_POSITION
  	}
  
  	private final ZoomToRectangleController zoomToRectangleController;
  
  	protected Coordinate dragOrigin;
  
  	protected List<Coordinate> dragPositions = new ArrayList<Coordinate>();
  
  	protected List<Date> dragTimes = new ArrayList<Date>();
  
  	protected Coordinate lastClickPosition;
  
  	protected boolean zooming;
  
  	protected boolean dragging;
  
  	private ScrollZoomType scrollZoomType = ScrollZoomType.ZOOM_POSITION;
  
  	private long lastMillis;
  
  	private static final long MAX_KINETIC_DELAY = 30;
  
  	// ------------------------------------------------------------------------
  	// Constructors:
  	// ------------------------------------------------------------------------
  
  	/** Create a NavigationController instance. */
  	public NavigationController() {
  		super(false);
  		zoomToRectangleController = new ZoomToRectangleController();
  	}
  
  	// ------------------------------------------------------------------------
  	// MapController implementation:
  	// ------------------------------------------------------------------------
  
  	@Override
  	public void onActivate(MapPresenter mapPresenter) {
  		super.onActivate(mapPresenter);
  		zoomToRectangleController.onActivate(mapPresenter);
  	}
  
  	@Override
  	public void onMouseDown(MouseDownEvent event) {
  		super.onMouseDown(event);
  		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
  			// Trigger the dragging on the zoomToRectangleController:
  			zoomToRectangleController.onMouseDown(event);
  		}
  	}
  
  	@Override
  	public void onDown(HumanInputEvent<?> event) {
  		if (event.isControlKeyDown() || event.isShiftKeyDown()) {
  			zooming = true;
  		} else if (!isRightMouseButton(event)) {
  			dragging = true;
  			dragOrigin = getLocation(event, RenderSpace.SCREEN);
  			dragPositions.clear();
  			dragTimes.clear();
  			mapPresenter.setCursor("move");
  		}
  		lastClickPosition = getLocation(event, RenderSpace.WORLD);
  	}
  
  	@Override
  	public void onUp(HumanInputEvent<?> event) {
  		if (zooming) {
  			logger.info("zooming");
  			Coordinate coordinate = getLocation(event, RenderSpace.WORLD);
  			if (!coordinate.equals(lastClickPosition)) {
  				zoomToRectangleController.onUp(event);
  			}
  			zooming = false;
  		} else if (dragging) {
  			stopPanning(event);
  		}
  	}
  
  	@Override
  	public void onMouseMove(MouseMoveEvent event) {
  		if (zooming) {
  			zoomToRectangleController.onMouseMove(event);
  		} else if (dragging) {
  			super.onMouseMove(event);
  		}
  	}
  
  	@Override
  	public void onDrag(HumanInputEvent<?> event) {
  		dragPositions.add(getLocation(event, RenderSpace.SCREEN));
  		Date time = new Date();
  		dragTimes.add(time);
  		if (time.getTime() - this.dragTimes.get(0).getTime() > 200) {
  			this.dragTimes.remove(0);
  			this.dragPositions.remove(0);
  		}
  		updateView(event);
  	}
  
  	@Override
  	public void onMouseOut(MouseOutEvent event) {
  		if (zooming) {
  			zoomToRectangleController.onMouseOut(event);
  		} else {
  			stopPanning(null);
  		}
  	}
  
  	@Override
  	public void onDoubleClick(DoubleClickEvent event) {
  		mapPresenter.getViewPort().registerAnimation(
  				NavigationAnimationFactory.createZoomIn(mapPresenter, calculatePosition(true, lastClickPosition)));
  	}
  
  	@Override
  	public void onMouseWheel(MouseWheelEvent event) {
  		final boolean isNorth;
  		if (event.getDeltaY() == 0) {
  			isNorth = (getWheelDelta(event.getNativeEvent()) < 0);
  		} else {
  			isNorth = event.isNorth();
  		}
  		Coordinate location = getLocation(event, RenderSpace.WORLD);
  		scrollZoomTo(isNorth, location);
  	}
  
  	// Getters and setters:
  	// ------------------------------------------------------------------------
  
  	/**
  	 * Get the scroll zoom type of this controller.
  	 * 
  	 * @return the scroll zoom type.
  	 */
  	public ScrollZoomType getScrollZoomType() {
  		return scrollZoomType;
  	}
  
  	/**
  	 * Set the scroll zoom type of this controller.
  	 * 
  	 * @param scrollZoomType the scroll zoom type.
  	 */
  	public void setScrollZoomType(ScrollZoomType scrollZoomType) {
  		this.scrollZoomType = scrollZoomType;
  	}
  
  	// ------------------------------------------------------------------------
  	// Private methods:
  	// ------------------------------------------------------------------------
  
  	protected void stopPanning(HumanInputEvent<?> event) {
  		dragging = false;
  		mapPresenter.setCursor("default");
  		if (null != event && dragPositions.size() >= 2) {
  			logger.info("kinetics started");
  			// start kinetic animation
  			Coordinate dragStart = toWorld(dragPositions.get(0));
  			Coordinate dragStop = toWorld(dragPositions.get(dragPositions.size() - 1));
  			long timeStart = dragTimes.get(0).getTime();
  			long timeStop = dragTimes.get(dragTimes.size() - 1).getTime();
  			int delta = (int) (timeStop - timeStart);
  			long delay = new Date().getTime() - timeStop;
  			if (delta > 0 && delay < MAX_KINETIC_DELAY) {
  				// map moves in the inverse direction !
  				Coordinate direction = subtract(dragStart, dragStop);
  				double distance = abs(direction);
  				Coordinate current = mapPresenter.getViewPort().getPosition();
  				double resolution = mapPresenter.getViewPort().getResolution();
  				KineticTrajectory trajectory = new KineticTrajectory(new View(current, resolution), direction, distance
  						/ delta);
  				mapPresenter.getViewPort().registerAnimation(
  						new NavigationAnimationImpl(mapPresenter.getViewPort(), mapPresenter.getEventBus(), trajectory,
  								(int) trajectory.getDuration()));
  			} else {
  				mapPresenter.getViewPort().stopInteraction();
  			}
  		}
  	}
  
  	private double abs(Coordinate c) {
  		return MathService.distance(c, new Coordinate());
  	}
  
  	private Coordinate toWorld(Coordinate coordinate) {
  		return mapPresenter.getViewPort().getTransformationService()
  				.transform(coordinate, RenderSpace.SCREEN, RenderSpace.WORLD);
  	}
  
  	private Coordinate subtract(Coordinate c1, Coordinate c2) {
  		return new Coordinate(c1.getX() - c2.getX(), c1.getY() - c2.getY());
  	}
  
  	private Coordinate add(Coordinate c1, Coordinate c2) {
  		return new Coordinate(c2.getX() + c1.getX(), c2.getY() + c1.getY());
  	}
  
  	protected void updateView(HumanInputEvent<?> event) {
  		if (dragging) {
  			Coordinate end = getLocation(event, RenderSpace.SCREEN);
  			Coordinate beginWorld = mapPresenter.getViewPort().getTransformationService()
  					.transform(dragOrigin, RenderSpace.SCREEN, RenderSpace.WORLD);
  			Coordinate endWorld = mapPresenter.getViewPort().getTransformationService()
  					.transform(end, RenderSpace.SCREEN, RenderSpace.WORLD);
  			double x = mapPresenter.getViewPort().getPosition().getX() + beginWorld.getX() - endWorld.getX();
  			double y = mapPresenter.getViewPort().getPosition().getY() + beginWorld.getY() - endWorld.getY();
  			View view = new View(new Coordinate(x, y), mapPresenter.getViewPort().getResolution());
  			view.setDragging(true);
  			mapPresenter.getViewPort().applyView(view);
  			dragOrigin = end;
  		}
  	}
  
  	protected native int getWheelDelta(NativeEvent evt) /*-{
  														return Math.round(-evt.wheelDelta) || 0;
  														}-*/;
  
  	protected void scrollZoomTo(boolean isNorth, Coordinate location) {
  		ViewPort viewPort = mapPresenter.getViewPort();
  		int index = viewPort.getResolutionIndex(viewPort.getResolution());
  		if (isNorth) {
  			if (index < viewPort.getResolutionCount() - 1) {
  				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
  					Coordinate position = calculatePosition(true, location);
  					viewPort.registerAnimation(NavigationAnimationFactory.createZoomIn(mapPresenter, position));
  				} else {
  					viewPort.registerAnimation(NavigationAnimationFactory.createZoomIn(mapPresenter));
  				}
  			}
  		} else {
  			if (index > 0) {
  				if (scrollZoomType == ScrollZoomType.ZOOM_POSITION) {
  					Coordinate position = calculatePosition(false, location);
  					viewPort.registerAnimation(NavigationAnimationFactory.createZoomOut(mapPresenter, position));
  				} else {
  					viewPort.registerAnimation(NavigationAnimationFactory.createZoomOut(mapPresenter));
  				}
  			}
  		}
  	}
  
  	/**
  	 * Calculate the target position should there be a rescale point. The idea is that after zooming in or out, the
  	 * mouse cursor would still lie at the same position in world space.
  	 */
  	protected Coordinate calculatePosition(boolean zoomIn, Coordinate rescalePoint) {
  