Ticket #12350: snapZoomToMercator.patch

File snapZoomToMercator.patch, 16.2 KB (added by kolesar, 6 years ago)
  • src/org/openstreetmap/josm/gui/MapSlider.java

    diff --git a/src/org/openstreetmap/josm/gui/MapSlider.java b/src/org/openstreetmap/josm/gui/MapSlider.java
    index fab953d..2b3f726 100644
    a b import javax.swing.event.ChangeListener; 
    1212
    1313import org.openstreetmap.josm.data.ProjectionBounds;
    1414import org.openstreetmap.josm.gui.help.Helpful;
     15import static org.openstreetmap.josm.gui.MapView.snapZoomMode;
    1516
    1617class MapSlider extends JSlider implements PropertyChangeListener, ChangeListener, Helpful {
    1718
    1819    private final MapView mv;
    1920    private boolean preventChange;
     21    private static final double zoomStep = Math.pow(2, 0.2);
    2022
    2123    MapSlider(MapView mv) {
    22         super(35, 150);
     24        super(0, 150);
    2325        setOpaque(false);
    2426        this.mv = mv;
    2527        mv.addPropertyChangeListener("scale", this);
    class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene 
    3032
    3133    @Override
    3234    public void propertyChange(PropertyChangeEvent evt) {
    33         if (getModel().getValueIsAdjusting()) return;
    34 
    35         ProjectionBounds world = this.mv.getMaxProjectionBounds();
    36         ProjectionBounds current = this.mv.getProjectionBounds();
    37 
    38         double cur_e = current.maxEast-current.minEast;
    39         double cur_n = current.maxNorth-current.minNorth;
    40         double e = world.maxEast-world.minEast;
    41         double n = world.maxNorth-world.minNorth;
    42         int zoom = 0;
    43 
    44         while (zoom <= 150) {
    45             e /= 1.1;
    46             n /= 1.1;
    47             if (e < cur_e && n < cur_n) {
    48                 break;
    49             }
    50             ++zoom;
    51         }
     35        double maxScale = this.mv.getMaxScale();
     36        int zoom = (int) Math.round(Math.log(maxScale/mv.getScale())/Math.log(zoomStep));
    5237        preventChange = true;
    5338        setValue(zoom);
    5439        preventChange = false;
    class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene 
    5742    @Override
    5843    public void stateChanged(ChangeEvent e) {
    5944        if (preventChange) return;
    60 
    61         ProjectionBounds world = this.mv.getMaxProjectionBounds();
    62         double fact = Math.pow(1.1, getValue());
    63         double es = world.maxEast-world.minEast;
    64         double n = world.maxNorth-world.minNorth;
    65 
    66         this.mv.zoomTo(new ProjectionBounds(this.mv.getCenter(), es/fact, n/fact));
     45        double maxScale = this.mv.getMaxScale();
     46        double scale = maxScale/Math.pow(zoomStep, getValue());
     47        boolean isAdjusting = getModel().getValueIsAdjusting();
     48        snapZoomMode snap = isAdjusting ? snapZoomMode.FLOOR : snapZoomMode.STEP;
     49        double snapped = mv.getSnappedScale(scale, snap);
     50        this.mv.zoomTo(this.mv.getCenter(), snapped);
     51        propertyChange(null);
    6752    }
    6853
    6954    @Override
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index ad767f2..23ef09a 100644
    a b implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer 
    11031103
    11041104    @Override
    11051105    public void preferenceChanged(PreferenceChangeEvent e) {
     1106        super.preferenceChanged(e);
    11061107        synchronized (this) {
    11071108            paintPreferencesChanged = true;
    11081109        }
  • src/org/openstreetmap/josm/gui/NavigatableComponent.java

    diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
    index feae89f..e82ee85 100644
    a b import org.openstreetmap.josm.data.osm.WaySegment; 
    4545import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    4646import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    4747import org.openstreetmap.josm.data.preferences.IntegerProperty;
     48import org.openstreetmap.josm.data.preferences.BooleanProperty;
    4849import org.openstreetmap.josm.data.projection.Projection;
    4950import org.openstreetmap.josm.data.projection.Projections;
     51import org.openstreetmap.josm.data.projection.proj.Mercator;
    5052import org.openstreetmap.josm.gui.download.DownloadDialog;
    5153import org.openstreetmap.josm.gui.help.Helpful;
    5254import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 
    5456import org.openstreetmap.josm.gui.util.CursorManager;
    5557import org.openstreetmap.josm.tools.Predicate;
    5658import org.openstreetmap.josm.tools.Utils;
     59import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
     60import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
    5761
    5862/**
    5963 * A component that can be navigated by a {@link MapMover}. Used as map view and for the
    import org.openstreetmap.josm.tools.Utils; 
    6266 * @author imi
    6367 * @since 41
    6468 */
    65 public class NavigatableComponent extends JComponent implements Helpful {
     69public class NavigatableComponent extends JComponent implements Helpful, PreferenceChangedListener {
    6670
    6771    /**
    6872     * Interface to notify listeners of the change of the zoom area.
    public class NavigatableComponent extends JComponent implements Helpful { 
    8993    };
    9094
    9195    public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
     96    public static final BooleanProperty PROP_SNAP_ZOOM = new BooleanProperty("zoom.snap-scale-to-mercator-zoom-levels", true);
     97    public enum snapZoomMode { FLOOR, STEP, ROUND }
    9298
    9399    public static final String PROPNAME_CENTER = "center";
    94100    public static final String PROPNAME_SCALE  = "scale";
    public class NavigatableComponent extends JComponent implements Helpful { 
    143149     */
    144150    public NavigatableComponent() {
    145151        setLayout(null);
     152        Main.pref.addPreferenceChangeListener(this);
     153        scale = getSnappedScale(scale, snapZoomMode.ROUND);
    146154    }
    147155
    148156    protected DataSet getCurrentDataSet() {
    public class NavigatableComponent extends JComponent implements Helpful { 
    284292                getProjection().latlon2eastNorth(b.getMax()));
    285293    }
    286294
     295    // maximum: world in 256 pixels
     296    // getSnappedScale() and also MaxSlider uses this value
     297    // as scale for zoom 0
     298    public double getMaxScale() {
     299        ProjectionBounds world = getMaxProjectionBounds();
     300        return Math.max(
     301            world.maxNorth-world.minNorth,
     302            world.maxEast-world.minEast
     303        )/256;
     304    }
     305
    287306    /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
    288307    public Bounds getRealBounds() {
    289308        return new Bounds(
    public class NavigatableComponent extends JComponent implements Helpful { 
    409428     * @param initial true if this call initializes the viewport.
    410429     */
    411430    public void zoomTo(EastNorth newCenter, double newScale, boolean initial) {
    412         Bounds b = getProjection().getWorldBoundsLatLon();
    413431        ProjectionBounds pb = getProjection().getWorldBoundsBoxEastNorth();
    414         int width = getWidth();
    415         int height = getHeight();
    416432
    417433        // make sure, the center of the screen is within projection bounds
    418434        double east = newCenter.east();
    public class NavigatableComponent extends JComponent implements Helpful { 
    423439        north = Math.min(north, pb.maxNorth);
    424440        newCenter = new EastNorth(east, north);
    425441
    426         // don't zoom out too much, the world bounds should be at least
    427         // half the size of the screen
    428         double pbHeight = pb.maxNorth - pb.minNorth;
    429         if (height > 0 && 2 * pbHeight < height * newScale) {
    430             double newScaleH = 2 * pbHeight / height;
    431             double pbWidth = pb.maxEast - pb.minEast;
    432             if (width > 0 && 2 * pbWidth < width * newScale) {
    433                 double newScaleW = 2 * pbWidth / width;
    434                 newScale = Math.max(newScaleH, newScaleW);
    435             }
    436         }
     442        // don't zoom out too much
     443        newScale = Math.min(newScale, getMaxScale());
    437444
    438445        // don't zoom in too much, minimum: 100 px = 1 cm
    439         LatLon ll1 = getLatLon(width / 2 - 50, height / 2);
    440         LatLon ll2 = getLatLon(width / 2 + 50, height / 2);
    441         if (ll1.isValid() && ll2.isValid() && b.contains(ll1) && b.contains(ll2)) {
    442             double d_m = ll1.greatCircleDistance(ll2);
    443             double d_en = 100 * scale;
    444             double scaleMin = 0.01 * d_en / d_m / 100;
    445             if (!Double.isInfinite(scaleMin) && newScale < scaleMin) {
    446                 newScale = scaleMin;
    447             }
    448         }
     446        double d_m = getDist100Pixel();
     447        double scaleMin = 0.01 * scale / d_m;
     448        if (!Double.isInfinite(scaleMin) && newScale < scaleMin)
     449            newScale = scaleMin;
    449450
     451        newScale = getSnappedScale(newScale, snapZoomMode.FLOOR);
    450452        if (!newCenter.equals(center) || !Utils.equalsEpsilon(scale, newScale)) {
    451453            if (!initial) {
    452454                pushZoomUndo(center, scale);
    public class NavigatableComponent extends JComponent implements Helpful { 
    455457        }
    456458    }
    457459
     460    public double getSnappedScale(double newScale) {
     461        return getSnappedScale(newScale, snapZoomMode.STEP);
     462    }
     463
     464    public double getSnappedScale(double newScale, snapZoomMode mode) {
     465        if (!(PROP_SNAP_ZOOM.get() && "EPSG:3857".equals(Main.getProjection().toCode()))) return newScale;
     466        double askedScale = newScale;
     467        double tileSizeAtZeroZoom = getMaxScale();
     468        double tmsZoom = Math.log(tileSizeAtZeroZoom/newScale)/Math.log(2);
     469        int tmsZoomLevel = (int) (mode == snapZoomMode.FLOOR ? Math.floor(tmsZoom) : Math.round(tmsZoom));
     470        newScale = tileSizeAtZeroZoom/Math.pow(2, tmsZoomLevel);
     471        double diff = askedScale/this.scale-1;
     472        if (mode == snapZoomMode.STEP && newScale == this.scale && Math.abs(diff) > 0.0001) {
     473            if (diff > 0) newScale *= 2;
     474            if (diff < 0) newScale /= 2;
     475        }
     476        return newScale;
     477    }
     478
    458479    /**
    459480     * Zoom to the given coordinate without adding to the zoom undo buffer.
    460481     *
    public class NavigatableComponent extends JComponent implements Helpful { 
    484505        }
    485506    }
    486507
     508    public void zoomTo(double newScale) {
     509        zoomTo(center, newScale);
     510    }
     511
    487512    public void zoomTo(EastNorth newCenter) {
    488513        zoomTo(newCenter, scale);
    489514    }
    public class NavigatableComponent extends JComponent implements Helpful { 
    529554    }
    530555
    531556    public void zoomToFactor(double x, double y, double factor) {
    532         double newScale = scale*factor;
     557        double newScale = getSnappedScale(scale*factor);
    533558        // New center position so that point under the mouse pointer stays the same place as it was before zooming
    534559        // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
    535560        zoomTo(new EastNorth(
    public class NavigatableComponent extends JComponent implements Helpful { 
    539564    }
    540565
    541566    public void zoomToFactor(EastNorth newCenter, double factor) {
    542         zoomTo(newCenter, scale*factor);
     567        zoomTo(newCenter, getSnappedScale(scale*factor));
    543568    }
    544569
    545570    public void zoomToFactor(double factor) {
    546         zoomTo(center, scale*factor);
     571        zoomTo(center, getSnappedScale(scale*factor));
    547572    }
    548573
    549574    public void zoomTo(ProjectionBounds box) {
    public class NavigatableComponent extends JComponent implements Helpful { 
    15161541        }
    15171542        repaint();
    15181543    }
     1544
     1545    @Override
     1546    public void preferenceChanged(PreferenceChangeEvent e) {
     1547        if (e != null && e.getKey() == PROP_SNAP_ZOOM.getKey()) {
     1548            zoomTo(getSnappedScale(scale, snapZoomMode.ROUND));
     1549        }
     1550    }
    15191551}
  • src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
    index 926bf61..a17ca3f 100644
    a b import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 
    8484import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    8585import org.openstreetmap.josm.io.WMSLayerImporter;
    8686import org.openstreetmap.josm.tools.GBC;
     87import org.openstreetmap.josm.tools.Utils;
    8788
    8889/**
    8990 * Base abstract class that supports displaying images provided by TileSource. It might be TMS source, WMS or WMTS
    public abstract class AbstractTileSourceLayer extends ImageryLayer implements Im 
    278279
    279280        int screenPixels = mv.getWidth()*mv.getHeight();
    280281        double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
     282        tilePixels = Utils.roundToSignificantDigits(tilePixels, 9);
    281283        if (screenPixels == 0 || tilePixels == 0) return 1;
    282284        return screenPixels/tilePixels;
    283285    }
  • src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java

    diff --git a/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java b/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java
    index 026fbeb..80a1ec2 100644
    a b import javax.swing.UIManager.LookAndFeelInfo; 
    2424import org.openstreetmap.josm.Main;
    2525import org.openstreetmap.josm.actions.ExpertToggleAction;
    2626import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
     27import org.openstreetmap.josm.gui.NavigatableComponent;
    2728import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    2829import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    2930import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
    public class LafPreference implements SubPreferenceSetting { 
    6162    private final JCheckBox dynamicButtons = new JCheckBox(tr("Dynamic buttons in side menus"));
    6263    private final JCheckBox isoDates = new JCheckBox(tr("Display ISO dates"));
    6364    private final JCheckBox nativeFileChoosers = new JCheckBox(tr("Use native file choosers (nicer, but do not support file filters)"));
     65    private final JCheckBox snapToMercatorZoomLevels = new JCheckBox(tr("Snap zoom to Mercator zoom levels (imagery looks perfect, zoom steps are larger)"));
    6466
    6567    @Override
    6668    public void addGui(PreferenceTabbedPane gui) {
    public class LafPreference implements SubPreferenceSetting { 
    140142        nativeFileChoosers.setSelected(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get());
    141143        panel.add(nativeFileChoosers, GBC.eop().insets(20, 0, 0, 0));
    142144
     145        snapToMercatorZoomLevels.setToolTipText(
     146                tr("Displays tiles without resizing, similar to most websites. Zoom steps are twice larger than default. Ignored when using different projection."));
     147        snapToMercatorZoomLevels.setSelected(NavigatableComponent.PROP_SNAP_ZOOM.get());
     148        panel.add(snapToMercatorZoomLevels, GBC.eop().insets(20, 0, 0, 0));
     149
    143150        panel.add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
    144151
    145152        panel.add(new JLabel(tr("Look and Feel")), GBC.std().insets(20, 0, 0, 0));
    public class LafPreference implements SubPreferenceSetting { 
    161168        Main.pref.put(ToggleDialog.PROP_DYNAMIC_BUTTONS.getKey(), dynamicButtons.isSelected());
    162169        Main.pref.put(DateUtils.PROP_ISO_DATES.getKey(), isoDates.isSelected());
    163170        Main.pref.put(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.getKey(), nativeFileChoosers.isSelected());
     171        Main.pref.put(NavigatableComponent.PROP_SNAP_ZOOM.getKey(), snapToMercatorZoomLevels.isSelected());
    164172        mod |= Main.pref.put("laf", ((LookAndFeelInfo) lafCombo.getSelectedItem()).getClassName());
    165173        return mod;
    166174    }
  • src/org/openstreetmap/josm/tools/Utils.java

    diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java
    index b08d5f9..8ab1f5e 100644
    a b public final class Utils { 
    16361636        return gvs;
    16371637    }
    16381638
     1639    public static double roundToSignificantDigits(double number, int digits) {
     1640        double scale = Math.pow(10, Math.floor(Math.log10(Math.abs(number))) + 1 - digits);
     1641        return scale * Math.round(number / scale);
     1642    }
     1643
    16391644}