// License: GPL. See LICENSE file for details. package org.openstreetmap.josm.gui; import java.beans.PropertyChangeSupport; import java.util.Date; import java.util.Stack; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.beboj.CanvasView; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.ProjectionBounds; import org.openstreetmap.josm.data.coor.CachedLatLon; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.LatLon; /** * GWT * * This class is not present in JOSM. The code was originally in NavigationonalComponent.java * and moved out to introduce other methods of zoom. * * notes * zoomNoUndoTo does not trigger repaint */ /** * Support smooth (continuous) zoom. */ public class SmoothZoomNavigationSupport extends AbstractNavigationSupport { public PropertyChangeSupport propertyChangeManager = new PropertyChangeSupport(this); /** * The scale factor in x or y-units per pixel. This means, if scale = 10, * every physical pixel on screen are 10 x or 10 y units in the * northing/easting space of the projection. */ protected double scale = Main.proj.getDefaultZoomInPPD(); public SmoothZoomNavigationSupport(CanvasView view) { super(view); } public double getScale() { return scale; } @Override public boolean isReady() { return center != null && scale > 0; } /** * Zoom to the given coordinate. * @param newCenter The center x-value (easting) to zoom to. * @param scale The scale to use. */ private void zoomTo(EastNorth newCenter, double newScale) { Bounds b = getProjection().getWorldBoundsLatLon(); CachedLatLon cl = new CachedLatLon(newCenter); boolean changed = false; double lat = cl.lat(); double lon = cl.lon(); if(lat < b.getMin().lat()) {changed = true; lat = b.getMin().lat(); } else if(lat > b.getMax().lat()) {changed = true; lat = b.getMax().lat(); } if(lon < b.getMin().lon()) {changed = true; lon = b.getMin().lon(); } else if(lon > b.getMax().lon()) {changed = true; lon = b.getMax().lon(); } if(changed) { newCenter = new CachedLatLon(lat, lon).getEastNorth(); } int width = view.getWidth()/2; int height = view.getHeight()/2; LatLon l1 = new LatLon(b.getMin().lat(), lon); LatLon l2 = new LatLon(b.getMax().lat(), lon); EastNorth e1 = getProjection().latlon2eastNorth(l1); EastNorth e2 = getProjection().latlon2eastNorth(l2); double d = e2.north() - e1.north(); if(d < height*newScale) { double newScaleH = d/height; e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMin().lon())); e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMax().lon())); d = e2.east() - e1.east(); if(d < width*newScale) { newScale = Math.max(newScaleH, d/width); } } else { d = d/(l1.greatCircleDistance(l2)*height*10); if(newScale < d) { newScale = d; } } if (!newCenter.equals(center) || (scale != newScale)) { pushZoomUndo(center, scale); zoomNoUndoTo(newCenter, newScale); } } /** * Zoom to the given coordinate without adding to the zoom undo buffer. * @param newCenter The center x-value (easting) to zoom to. * @param scale The scale to use. */ private void zoomNoUndoTo(EastNorth newCenter, double newScale) { if (!newCenter.equals(center)) { EastNorth oldCenter = center; center = newCenter; propertyChangeManager.firePropertyChange("center", oldCenter, newCenter); } if (scale != newScale) { double oldScale = scale; scale = newScale; propertyChangeManager.firePropertyChange("scale", oldScale, newScale); } view.repaint(); fireZoomChanged(); } @Override public void zoomTo(EastNorth newCenter) { zoomTo(newCenter, scale); } @Override public void zoomTo(LatLon newCenter) { if(newCenter instanceof CachedLatLon) { zoomTo(((CachedLatLon)newCenter).getEastNorth(), scale); } else { zoomTo(getProjection().latlon2eastNorth(newCenter), scale); } } public void zoomToFactor(double x, double y, double factor) { double newScale = scale*factor; // New center position so that point under the mouse pointer stays the same place as it was before zooming // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom zoomTo(new EastNorth( center.east() - (x - view.getWidth()/2.0) * (newScale - scale), center.north() + (y - view.getHeight()/2.0) * (newScale - scale)), newScale); } public void zoomToFactor(EastNorth newCenter, double factor) { zoomTo(newCenter, scale*factor); } public void zoomToFactor(double factor) { zoomTo(center, scale*factor); } @Override public void zoomTo(ProjectionBounds box) { // -20 to leave some border int w = view.getWidth()-20; if (w < 20) { w = 20; } int h = view.getHeight()-20; if (h < 20) { h = 20; } double scaleX = (box.maxEast-box.minEast)/w; double scaleY = (box.maxNorth-box.minNorth)/h; double newScale = Math.max(scaleX, scaleY); zoomTo(box.getCenter(), newScale); } public void zoomTo(Bounds box) { zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()), getProjection().latlon2eastNorth(box.getMax()))); } private class ZoomData { LatLon center; double scale; public ZoomData(EastNorth center, double scale) { this.center = new CachedLatLon(center); this.scale = scale; } public EastNorth getCenterEastNorth() { return getProjection().latlon2eastNorth(center); } public double getScale() { return scale; } } private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>(); private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>(); private Date zoomTimestamp = new Date(); private void pushZoomUndo(EastNorth center, double scale) { Date now = new Date(); if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) { zoomUndoBuffer.push(new ZoomData(center, scale)); if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) { zoomUndoBuffer.remove(0); } zoomRedoBuffer.clear(); } zoomTimestamp = now; } public void zoomPrevious() { if (!zoomUndoBuffer.isEmpty()) { ZoomData zoom = zoomUndoBuffer.pop(); zoomRedoBuffer.push(new ZoomData(center, scale)); zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale()); } } public void zoomNext() { if (!zoomRedoBuffer.isEmpty()) { ZoomData zoom = zoomRedoBuffer.pop(); zoomUndoBuffer.push(new ZoomData(center, scale)); zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale()); } } public boolean hasZoomUndoEntries() { return !zoomUndoBuffer.isEmpty(); } public boolean hasZoomRedoEntries() { return !zoomRedoBuffer.isEmpty(); } }