Ticket #12350: NativeScaleLayer.patch

File NativeScaleLayer.patch, 39.4 KB (added by kolesar, 5 years ago)
  • src/org/openstreetmap/gui/jmapviewer/OsmMercator.java

    diff --git a/images/dialogs/layerlist/scale.png b/images/dialogs/layerlist/scale.png
    new file mode 100644
    index 0000000..f47f480
    Binary files /dev/null and b/images/dialogs/layerlist/scale.png differ
    diff --git a/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java b/src/org/openstreetmap/gui/jmapviewer/OsmMercator.java
    index fbd1028..7bfdff5 100644
    a b public class OsmMercator { 
    2121    /** minimum latitude (south) for mercator display */
    2222    public static final double MIN_LAT = -85.05112877980659;
    2323    /** equatorial earth radius for EPSG:3857 (Mercator) */
    24     private static double EARTH_RADIUS = 6378137;
     24    public static final double EARTH_RADIUS = 6378137;
    2525
    2626    /**
    2727     * instance with tile size of 256 for easy conversions
  • src/org/openstreetmap/josm/actions/ZoomInAction.java

    diff --git a/src/org/openstreetmap/josm/actions/ZoomInAction.java b/src/org/openstreetmap/josm/actions/ZoomInAction.java
    index 3b32f49..20853e9 100644
    a b public final class ZoomInAction extends JosmAction { 
    4646    @Override
    4747    public void actionPerformed(ActionEvent e) {
    4848        if (!Main.isDisplayingMapView()) return;
    49         Main.map.mapView.zoomToFactor(1/Math.sqrt(2));
     49        Main.map.mapView.zoomIn();
    5050    }
    5151
    5252    @Override
  • src/org/openstreetmap/josm/actions/ZoomOutAction.java

    diff --git a/src/org/openstreetmap/josm/actions/ZoomOutAction.java b/src/org/openstreetmap/josm/actions/ZoomOutAction.java
    index 33f4a84..98bb1c2 100644
    a b public final class ZoomOutAction extends JosmAction { 
    3232    @Override
    3333    public void actionPerformed(ActionEvent e) {
    3434        if (!Main.isDisplayingMapView()) return;
    35         Main.map.mapView.zoomToFactor(Math.sqrt(2));
     35        Main.map.mapView.zoomOut();
    3636    }
    3737
    3838    @Override
  • src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java

    diff --git a/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java b/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java
    index 60217ae..4a66efd 100644
    a b import org.openstreetmap.josm.data.coor.LatLon; 
    4646import org.openstreetmap.josm.data.projection.Projection;
    4747import org.openstreetmap.josm.data.projection.Projections;
    4848import org.openstreetmap.josm.gui.ExtendedDialog;
     49import org.openstreetmap.josm.gui.layer.NativeScaleLayer.Scale;
     50import org.openstreetmap.josm.gui.layer.NativeScaleLayer.ScaleList;
    4951import org.openstreetmap.josm.io.CachedFile;
    5052import org.openstreetmap.josm.tools.CheckParameterUtil;
    5153import org.openstreetmap.josm.tools.GBC;
    public class WMTSTileSource extends AbstractTMSTileSource implements TemplatedTi 
    285287        return output.toString();
    286288    }
    287289
    288     private Collection<Layer> getCapabilities() throws IOException {
     290    private Collection<Layer> getCapabilities() {
    289291        XMLInputFactory factory = XMLInputFactory.newFactory();
    290292        // do not try to load external entities, nor validate the XML
    291293        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
    public class WMTSTileSource extends AbstractTMSTileSource implements TemplatedTi 
    708710        if (zoom > getMaxZoom()) {
    709711            return null;
    710712        }
    711         if (zoom < 1) {
     713        if (zoom < 0) {
    712714            return null;
    713715        }
    714         return this.currentTileMatrixSet.tileMatrix.get(zoom - 1);
     716        return this.currentTileMatrixSet.tileMatrix.get(zoom);
    715717    }
    716718
    717719    @Override
    public class WMTSTileSource extends AbstractTMSTileSource implements TemplatedTi 
    829831    @Override
    830832    public int getMaxZoom() {
    831833        if (this.currentTileMatrixSet != null) {
    832             return this.currentTileMatrixSet.tileMatrix.size();
     834            return this.currentTileMatrixSet.tileMatrix.size()-1;
    833835        }
    834836        return 0;
    835837    }
    public class WMTSTileSource extends AbstractTMSTileSource implements TemplatedTi 
    910912        EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax());
    911913        return (int) Math.ceil(Math.abs(max.east() - min.east()) / scale);
    912914    }
     915
     916    /**
     917     * Get native scales of tile source.
     918     * @return {@link ScaleList} of native scales
     919     */
     920    public ScaleList getNativeScales() {
     921        ScaleList scales = new ScaleList();
     922        if (currentTileMatrixSet != null) {
     923            for (TileMatrix tileMatrix : currentTileMatrixSet.tileMatrix) {
     924                scales.add(new Scale(tileMatrix.scaleDenominator * 0.28e-03));
     925            }
     926        }
     927        return scales;
     928    }
     929
    913930}
  • new file src/org/openstreetmap/josm/data/preferences/DoubleProperty.java

    diff --git a/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java b/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java
    new file mode 100644
    index 0000000..44ca4c2
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.preferences;
     3
     4import org.openstreetmap.josm.Main;
     5
     6/**
     7 * A property containing an {@code Double} value.
     8 * @since 3246
     9 */
     10public class DoubleProperty extends AbstractProperty<Double> {
     11
     12    /**
     13     * Constructs a new {@code DoubleProperty}.
     14     * @param key The property key
     15     * @param defaultValue The default value
     16     */
     17    public DoubleProperty(String key, double defaultValue) {
     18        super(key, defaultValue);
     19    }
     20
     21    @Override
     22    public Double get() {
     23        return Main.pref.getDouble(getKey(), getDefaultValue());
     24    }
     25
     26    @Override
     27    public boolean put(Double value) {
     28        return Main.pref.putDouble(getKey(), value);
     29    }
     30
     31    /**
     32     * parses and saves a double precision value
     33     * @param value the value to be parsed
     34     * @return true - preference value has changed
     35     *         false - parsing failed or preference value has not changed
     36     */
     37    public boolean parseAndPut(String value) {
     38        try {
     39            return put(Double.valueOf(value));
     40        } catch (NumberFormatException ex) {
     41            return false;
     42        }
     43    }
     44}
  • src/org/openstreetmap/josm/gui/MapMover.java

    diff --git a/src/org/openstreetmap/josm/gui/MapMover.java b/src/org/openstreetmap/josm/gui/MapMover.java
    index 4de6049..5580884 100644
    a b public class MapMover extends MouseAdapter implements MouseMotionListener, Mouse 
    213213     */
    214214    @Override
    215215    public void mouseWheelMoved(MouseWheelEvent e) {
    216         nc.zoomToFactor(e.getX(), e.getY(), Math.pow(Math.sqrt(2), e.getWheelRotation()));
     216        nc.zoomManyTimes(e.getX(), e.getY(), e.getWheelRotation());
    217217    }
    218218
    219219    /**
  • 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..f6f2fc1 100644
    a b import javax.swing.JSlider; 
    1010import javax.swing.event.ChangeEvent;
    1111import javax.swing.event.ChangeListener;
    1212
    13 import org.openstreetmap.josm.data.ProjectionBounds;
    1413import org.openstreetmap.josm.gui.help.Helpful;
    1514
    1615class MapSlider extends JSlider implements PropertyChangeListener, ChangeListener, Helpful {
    1716
     17    private static final double zoomStep = 1.1;
    1818    private final MapView mv;
    1919    private boolean preventChange;
     20    private int lastValue;
    2021
    2122    MapSlider(MapView mv) {
    22         super(35, 150);
     23        super(0, 150);
    2324        setOpaque(false);
    2425        this.mv = mv;
    2526        mv.addPropertyChangeListener("scale", this);
    class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene 
    3031
    3132    @Override
    3233    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         }
     34        double maxScale = this.mv.getMaxScale();
     35        int zoom = (int) Math.round(Math.log(maxScale/mv.getScale())/Math.log(zoomStep));
    5236        preventChange = true;
    5337        setValue(zoom);
     38        lastValue = zoom;
    5439        preventChange = false;
    5540    }
    5641
    class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene 
    5843    public void stateChanged(ChangeEvent e) {
    5944        if (preventChange) return;
    6045
    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));
     46        if (!getModel().getValueIsAdjusting() && mv.getNativeScaleLayer() != null) {
     47            if (getValue() < lastValue) {
     48                mv.zoomOut();
     49            } else if (getValue() > lastValue) {
     50                mv.zoomIn();
     51            }
     52        } else {
     53            double maxScale = this.mv.getMaxScale();
     54            double scale = maxScale/Math.pow(zoomStep, getValue());
     55            double snapped = mv.scaleFloor(scale);
     56            mv.zoomTo(this.mv.getCenter(), snapped);
     57        }
     58        propertyChange(null);
    6759    }
    6860
    6961    @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 530856e..83e9b36 100644
    a b import org.openstreetmap.josm.gui.layer.ImageryLayer; 
    5959import org.openstreetmap.josm.gui.layer.Layer;
    6060import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    6161import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     62import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
    6263import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
    6364import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    6465import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer 
    412413                ((OsmDataLayer) layer).addLayerStateChangeListener(this);
    413414            }
    414415
     416            if (layer instanceof NativeScaleLayer) {
     417                Main.map.mapView.setNativeScaleLayer((NativeScaleLayer) layer);
     418            }
     419
    415420            layer.addPropertyChangeListener(this);
    416421            Main.addProjectionChangeListener(layer);
    417422            AudioPlayer.reset();
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer 
    914919     * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
    915920     *
    916921     * @param layer the layer to be activate; must be one of the layers in the list of layers
    917      * @throws IllegalArgumentException if layer is not in the lis of layers
     922     * @throws IllegalArgumentException if layer is not in the list of layers
    918923     */
    919924    public void setActiveLayer(Layer layer) {
    920925        EnumSet<LayerListenerType> listenersToFire;
  • 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 cfdb495..1939de3 100644
    a b import org.openstreetmap.josm.data.osm.Way; 
    4444import org.openstreetmap.josm.data.osm.WaySegment;
    4545import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    4646import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
     47import org.openstreetmap.josm.data.preferences.BooleanProperty;
     48import org.openstreetmap.josm.data.preferences.DoubleProperty;
    4749import org.openstreetmap.josm.data.preferences.IntegerProperty;
    4850import org.openstreetmap.josm.data.projection.Projection;
    4951import org.openstreetmap.josm.data.projection.Projections;
    5052import org.openstreetmap.josm.gui.download.DownloadDialog;
    5153import org.openstreetmap.josm.gui.help.Helpful;
     54import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
     55import org.openstreetmap.josm.gui.layer.NativeScaleLayer.Scale;
     56import org.openstreetmap.josm.gui.layer.NativeScaleLayer.ScaleList;
    5257import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    5358import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
    5459import org.openstreetmap.josm.gui.util.CursorManager;
    public class NavigatableComponent extends JComponent implements Helpful { 
    8994    };
    9095
    9196    public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
     97    public static final DoubleProperty PROP_ZOOM_RATIO = new DoubleProperty("zoom.ratio", 2.0);
     98    public static final BooleanProperty PROP_ZOOM_INTERMEDIATE_STEPS = new BooleanProperty("zoom.intermediate-steps", true);
    9299
    93100    public static final String PROPNAME_CENTER = "center";
    94101    public static final String PROPNAME_SCALE  = "scale";
    95102
    96103    /**
     104     * The layer which scale is set to.
     105     */
     106    private transient NativeScaleLayer nativeScaleLayer;
     107
     108    /**
    97109     * the zoom listeners
    98110     */
    99111    private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<>();
    public class NavigatableComponent extends JComponent implements Helpful { 
    143155     */
    144156    public NavigatableComponent() {
    145157        setLayout(null);
     158        PROP_ZOOM_RATIO.get(); // make sure it is available in preferences
     159    }
     160
     161    /**
     162     * Choose a layer that scale will be snap to its native scales.
     163     * @param nativeScaleLayer
     164     */
     165    public void setNativeScaleLayer(NativeScaleLayer nativeScaleLayer) {
     166        this.nativeScaleLayer = nativeScaleLayer;
     167        zoomTo(center, scaleRound(scale));
     168        repaint();
     169    }
     170
     171    /**
     172     * Replies the layer which scale is set to.
     173     * @return the current scale layer (may be null)
     174     */
     175    public NativeScaleLayer getNativeScaleLayer() {
     176        return nativeScaleLayer;
     177    }
     178
     179    /**
     180     * Get a new scale that is zoomed in from previous scale
     181     * and snapped to selected native scale layer.
     182     * @return new scale
     183     */
     184    public double scaleZoomIn() {
     185        return scaleZoomManyTimes(-1);
     186    }
     187
     188    /**
     189     * Get a new scale that is zoomed out from previous scale
     190     * and snapped to selected native scale layer.
     191     * @return new scale
     192     */
     193    public double scaleZoomOut() {
     194        return scaleZoomManyTimes(1);
     195    }
     196
     197    /**
     198     * Get a new scale that is zoomed in/out a number of times
     199     * from previous scale and snapped to selected native scale layer.
     200     * @param times count of zoom operations, negative means zoom in
     201     * @return new scale
     202     */
     203    public double scaleZoomManyTimes(int times) {
     204        if (nativeScaleLayer != null) {
     205            ScaleList scaleList = nativeScaleLayer.getNativeScales();
     206            if (PROP_ZOOM_INTERMEDIATE_STEPS.get()) {
     207                scaleList = scaleList.withIntermediateSteps(PROP_ZOOM_RATIO.get());
     208            }
     209            Scale scale = scaleList.scaleZoomTimes(getScale(), PROP_ZOOM_RATIO.get(), times);
     210            return scale.scale;
     211        } else {
     212            return getScale() * Math.pow(PROP_ZOOM_RATIO.get(), times);
     213        }
     214    }
     215
     216    /**
     217     * Get a scale snapped to native resolutions, use round method.
     218     * It gives nearest step from scale list.
     219     * Use round method.
     220     * @param scale to snap
     221     * @return snapped scale
     222     */
     223    public double scaleRound(double scale) {
     224        return scaleSnap(scale, false);
     225    }
     226
     227    /**
     228     * Get a scale snapped to native resolutions.
     229     * It gives nearest lower step from scale list, usable to fit objects.
     230     * @param scale to snap
     231     * @return snapped scale
     232     */
     233    public double scaleFloor(double scale) {
     234        return scaleSnap(scale, true);
     235    }
     236
     237    /**
     238     * Get a scale snapped to native resolutions.
     239     * It gives nearest lower step from scale list, usable to fit objects.
     240     * @param scale to snap
     241     * @param floor use floor instead of round, set true when fitting view to objects
     242     * @return new scale
     243     */
     244    public double scaleSnap(double scale, boolean floor) {
     245        if (nativeScaleLayer != null) {
     246            ScaleList scaleList = nativeScaleLayer.getNativeScales();
     247            return scaleList.getSnapScale(scale, PROP_ZOOM_RATIO.get(), floor).scale;
     248        } else {
     249            return scale;
     250        }
     251    }
     252
     253    /**
     254     * Zoom in current view. Use configured zoom step and scaling settings.
     255     */
     256    public void zoomIn() {
     257        zoomTo(center, scaleZoomIn());
     258    }
     259
     260    /**
     261     * Zoom out current view. Use configured zoom step and scaling settings.
     262     */
     263    public void zoomOut() {
     264        zoomTo(center, scaleZoomOut());
    146265    }
    147266
    148267    protected DataSet getCurrentDataSet() {
    public class NavigatableComponent extends JComponent implements Helpful { 
    435554            }
    436555        }
    437556
     557        // snap scale to imagery if needed
     558        scale = scaleRound(scale);
     559
    438560        if (!newCenter.equals(center) || !Utils.equalsEpsilon(scale, newScale)) {
    439561            if (!initial) {
    440562                pushZoomUndo(center, scale);
    public class NavigatableComponent extends JComponent implements Helpful { 
    516638        }
    517639    }
    518640
     641    public void zoomManyTimes(double x, double y, int times) {
     642        double oldScale = scale;
     643        double newScale = scaleZoomManyTimes(times);
     644        zoomToFactor(x, y, newScale / oldScale);
     645    }
     646
    519647    public void zoomToFactor(double x, double y, double factor) {
    520648        double newScale = scale*factor;
    521649        // New center position so that point under the mouse pointer stays the same place as it was before zooming
    public class NavigatableComponent extends JComponent implements Helpful { 
    549677        double scaleY = (box.maxNorth-box.minNorth)/h;
    550678        double newScale = Math.max(scaleX, scaleY);
    551679
     680        newScale = scaleFloor(newScale);
    552681        zoomTo(box.getCenter(), newScale);
    553682    }
    554683
    public class NavigatableComponent extends JComponent implements Helpful { 
    15041633        }
    15051634        repaint();
    15061635    }
     1636
     1637    /**
     1638     * Get a max scale for projection that describes world in 256 pixels
     1639     * @return max scale
     1640     */
     1641    public double getMaxScale() {
     1642        ProjectionBounds world = getMaxProjectionBounds();
     1643        return Math.max(
     1644            world.maxNorth-world.minNorth,
     1645            world.maxEast-world.minEast
     1646        )/256;
     1647    }
    15071648}
  • src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
    index 4223c02..4031c5e 100644
    a b import org.openstreetmap.josm.gui.layer.JumpToMarkerActions; 
    6060import org.openstreetmap.josm.gui.layer.Layer;
    6161import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
    6262import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     63import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
    6364import org.openstreetmap.josm.gui.util.GuiHelper;
    6465import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
    6566import org.openstreetmap.josm.gui.widgets.JosmTextField;
    public class LayerListDialog extends ToggleDialog { 
    187188        layerList.getColumnModel().getColumn(0).setMaxWidth(12);
    188189        layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
    189190        layerList.getColumnModel().getColumn(0).setResizable(false);
    190         layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer());
    191         layerList.getColumnModel().getColumn(1).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
    192         layerList.getColumnModel().getColumn(1).setMaxWidth(16);
    193         layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
     191
     192        layerList.getColumnModel().getColumn(1).setCellRenderer(new NativeScaleLayerCellRenderer());
     193        layerList.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(new NativeScaleLayerCheckBox()));
     194        layerList.getColumnModel().getColumn(1).setMaxWidth(12);
     195        layerList.getColumnModel().getColumn(1).setPreferredWidth(12);
    194196        layerList.getColumnModel().getColumn(1).setResizable(false);
    195         layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer());
    196         layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new DisableShortcutsOnFocusGainedTextField()));
     197
     198        layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerVisibleCellRenderer());
     199        layerList.getColumnModel().getColumn(2).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
     200        layerList.getColumnModel().getColumn(2).setMaxWidth(16);
     201        layerList.getColumnModel().getColumn(2).setPreferredWidth(16);
     202        layerList.getColumnModel().getColumn(2).setResizable(false);
     203
     204        layerList.getColumnModel().getColumn(3).setCellRenderer(new LayerNameCellRenderer());
     205        layerList.getColumnModel().getColumn(3).setCellEditor(new LayerNameCellEditor(new DisableShortcutsOnFocusGainedTextField()));
    197206        // Disable some default JTable shortcuts to use JOSM ones (see #5678, #10458)
    198207        for (KeyStroke ks : new KeyStroke[] {
    199208                KeyStroke.getKeyStroke(KeyEvent.VK_C, GuiHelper.getMenuShortcutKeyMaskEx()),
    public class LayerListDialog extends ToggleDialog { 
    10251034        }
    10261035    }
    10271036
     1037    private static class NativeScaleLayerCheckBox extends JCheckBox {
     1038        NativeScaleLayerCheckBox() {
     1039            setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
     1040            ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
     1041            ImageIcon active = ImageProvider.get("dialogs/layerlist", "scale");
     1042            setIcon(blank);
     1043            setSelectedIcon(active);
     1044        }
     1045    }
     1046
    10281047    private static class ActiveLayerCellRenderer implements TableCellRenderer {
    10291048        private final JCheckBox cb;
    10301049
    public class LayerListDialog extends ToggleDialog { 
    10371056
    10381057        @Override
    10391058        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    1040             boolean active =  value != null && (Boolean) value;
     1059            boolean active = value != null && (Boolean) value;
    10411060            cb.setSelected(active);
    10421061            cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
    10431062            return cb;
    public class LayerListDialog extends ToggleDialog { 
    10781097        }
    10791098    }
    10801099
     1100    private static class NativeScaleLayerCellRenderer implements TableCellRenderer {
     1101        private final JCheckBox cb;
     1102
     1103        /**
     1104         * Constructs a new {@code ActiveLayerCellRenderer}.
     1105         */
     1106        NativeScaleLayerCellRenderer() {
     1107            cb = new NativeScaleLayerCheckBox();
     1108        }
     1109
     1110        @Override
     1111        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
     1112            Layer layer = (Layer) value;
     1113            if (layer instanceof NativeScaleLayer) {
     1114                boolean active = layer != null && layer == Main.map.mapView.getNativeScaleLayer();
     1115                cb.setSelected(active);
     1116                cb.setToolTipText(active
     1117                    ? tr("scale follows native resolution of this layer")
     1118                    : tr("scale follows native resolution of another layer (click to set this layer)")
     1119                );
     1120            } else {
     1121                cb.setSelected(false);
     1122                cb.setToolTipText(tr("this layer has no native resolution"));
     1123            }
     1124            return cb;
     1125        }
     1126    }
     1127
    10811128    private class LayerNameCellRenderer extends DefaultTableCellRenderer {
    10821129
    10831130        protected boolean isActiveLayer(Layer layer) {
    public class LayerListDialog extends ToggleDialog { 
    15491596            return Main.map.mapView.getActiveLayer();
    15501597        }
    15511598
     1599        /**
     1600         * Replies the scale layer. null, if no active layer is available
     1601         *
     1602         * @return the scale layer. null, if no active layer is available
     1603         */
     1604        protected NativeScaleLayer getNativeScaleLayer() {
     1605            if (!Main.isDisplayingMapView()) return null;
     1606            return Main.map.mapView.getNativeScaleLayer();
     1607        }
     1608
    15521609        /* ------------------------------------------------------------------------------ */
    15531610        /* Interface TableModel                                                           */
    15541611        /* ------------------------------------------------------------------------------ */
    public class LayerListDialog extends ToggleDialog { 
    15621619
    15631620        @Override
    15641621        public int getColumnCount() {
    1565             return 3;
     1622            return 4;
    15661623        }
    15671624
    15681625        @Override
    public class LayerListDialog extends ToggleDialog { 
    15731630                case 0: return layers.get(row) == getActiveLayer();
    15741631                case 1: return layers.get(row);
    15751632                case 2: return layers.get(row);
     1633                case 3: return layers.get(row);
    15761634                default: throw new RuntimeException();
    15771635                }
    15781636            }
    public class LayerListDialog extends ToggleDialog { 
    15971655                    l.setVisible(true);
    15981656                    break;
    15991657                case 1:
    1600                     l.setVisible((Boolean) value);
     1658                    if (Main.map.mapView.getNativeScaleLayer() == l) {
     1659                        Main.map.mapView.setNativeScaleLayer(null);
     1660                    } else if (l instanceof NativeScaleLayer) {
     1661                        Main.map.mapView.setNativeScaleLayer((NativeScaleLayer) l);
     1662                        l.setVisible(true);
     1663                        // set above imagery layers invisible
     1664                        for (int i=0; i<row; i++) {
     1665                            Layer above = layers.get(i);
     1666                            if (above instanceof ImageryLayer &&
     1667                                above.isVisible() &&
     1668                                above.getOpacity() == 1) {
     1669                                    above.setVisible(false);
     1670                            }
     1671                        }
     1672                    }
    16011673                    break;
    16021674                case 2:
     1675                    l.setVisible((Boolean) value);
     1676                    break;
     1677                case 3:
    16031678                    l.rename((String) value);
    16041679                    break;
    16051680                default: throw new RuntimeException();
  • new file src/org/openstreetmap/josm/gui/layer/NativeScaleLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/NativeScaleLayer.java b/src/org/openstreetmap/josm/gui/layer/NativeScaleLayer.java
    new file mode 100644
    index 0000000..e12d2ac
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.layer;
     3
     4import java.util.ArrayList;
     5
     6import org.openstreetmap.josm.gui.NavigatableComponent;
     7
     8/**
     9 * Represents a layer that has native scales.
     10 * @author András Kolesár
     11 */
     12public interface NativeScaleLayer {
     13
     14    /**
     15     * Get native scales of this layer.
     16     * @return {@link ScaleList} of native scales
     17     */
     18    public ScaleList getNativeScales();
     19
     20    /**
     21     * Represents a scale with native flag, used in {@link ScaleList}
     22     */
     23    public static class Scale {
     24        /**
     25         * Scale factor, same unit as in {@link NavigatableComponent}
     26         */
     27        public double scale;
     28
     29        /**
     30         * True if this scale is native resolution for data source.
     31         */
     32        public boolean isNative;
     33
     34        private int index;
     35
     36        /**
     37         * Constructs a new Scale with given scale, native defaults to true.
     38         * @param scale
     39         */
     40        public Scale(double scale) {
     41            this.scale = scale;
     42            this.isNative = true;
     43        }
     44
     45        /**
     46         * Constructs a new Scale with given scale and native values.
     47         * @param scale
     48         * @param isNative
     49         */
     50        public Scale(double scale, boolean isNative) {
     51            this.scale = scale;
     52            this.isNative = isNative;
     53        }
     54
     55        /**
     56         * Constructs a new Scale with given scale, native and index values.
     57         * @param scale
     58         * @param isNative
     59         * @param index
     60         */
     61        public Scale(double scale, boolean isNative, int index) {
     62            this.scale = scale;
     63            this.isNative = isNative;
     64            this.index = index;
     65        }
     66
     67        @Override
     68        public String toString() {
     69            return String.format("%f [%s]", scale, isNative);
     70        }
     71
     72        /**
     73         * Get index of this scale in a {@link ScaleList}
     74         * @return index
     75         */
     76        public int getIndex() {
     77            return index;
     78        }
     79    }
     80
     81    /**
     82     * List of scales, may include intermediate steps
     83     * between native resolutions
     84     */
     85    public static class ScaleList extends ArrayList<Scale> {
     86
     87        /**
     88         * Returns a ScaleList that has intermediate steps between native scales.
     89         * Native steps are split to equal steps near given ratio.
     90         * @param ratio user defined zoom ratio
     91         * @return a {@link ScaleList} with intermediate steps
     92         */
     93        public ScaleList withIntermediateSteps(double ratio) {
     94            int size = size();
     95            ScaleList result = new ScaleList();
     96            Scale previous = null;
     97            for (int i=0; i<size; i++) {
     98                Scale current = this.get(i);
     99                if (previous != null) {
     100                    double step = previous.scale / current.scale;
     101                    double factor = Math.log(step) / Math.log(ratio);
     102                    int steps = (int) Math.round(factor);
     103                    double smallStep = Math.pow(step, 1.0/steps);
     104                    for (int j=1; j<steps; j++) {
     105                        double intermediate = previous.scale / Math.pow(smallStep, j);
     106                        result.add(new Scale(intermediate, false));
     107                    }
     108                }
     109                result.add(current);
     110                previous = current;
     111            }
     112            return result;
     113        }
     114
     115        /**
     116         * Get a scale from this ScaleList or a new scale if zoomed outside.
     117         * @param scale previous scale
     118         * @param ratio zoom ratio from starting from previous scale
     119         * @param floor use floor instead of round, set true when fitting view to objects
     120         * @return new {@link Scale}
     121         */
     122        public Scale getSnapScale(double scale, double ratio, boolean floor) {
     123            int size = size();
     124            Scale first = get(0);
     125            Scale last = get(size-1);
     126            if (scale > first.scale) {
     127                double step = scale / first.scale;
     128                double factor = Math.log(step) / Math.log(ratio);
     129                int steps = (int) (floor ? Math.floor(factor) : Math.round(factor));
     130                if (steps == 0) {
     131                    return new Scale(first.scale, first.isNative, steps);
     132                } else {
     133                    return new Scale(first.scale * Math.pow(ratio, steps), false, steps);
     134                }
     135            } else if (scale < last.scale) {
     136                double step = last.scale / scale;
     137                double factor = Math.log(step) / Math.log(ratio);
     138                int steps = (int) (floor ? Math.floor(factor) : Math.round(factor));
     139                if (steps == 0) {
     140                    return new Scale(last.scale, last.isNative, size-1+steps);
     141                } else {
     142                    return new Scale(last.scale / Math.pow(ratio, steps), false, size-1+steps);
     143                }
     144            } else {
     145                Scale previous = null;
     146                for (int i=0; i<size; i++) {
     147                    Scale current = this.get(i);
     148                    if (previous != null) {
     149                        if (scale <= previous.scale && scale >= current.scale) {
     150                            if (floor || previous.scale / scale < scale / current.scale) {
     151                                return new Scale(previous.scale, previous.isNative, i-1);
     152                            } else {
     153                                return new Scale(current.scale, current.isNative, i);
     154                            }
     155                        }
     156                    }
     157                    previous = current;
     158                }
     159                return null;
     160            }
     161        }
     162
     163        /**
     164         * Get new scale for zoom in/out with a ratio at a number of times.
     165         * Used by mousewheel zoom where wheel can step more than one between events.
     166         * @param scale previois scale
     167         * @param ratio user defined zoom ratio
     168         * @param times number of times to zoom
     169         * @return new {@link Scale} object from {@link ScaleList} or outside
     170         */
     171        public Scale scaleZoomTimes(double scale, double ratio, int times) {
     172            Scale next = getSnapScale(scale, ratio, false);
     173            int abs = Math.abs(times);
     174            for (int i=0; i<abs; i++) {
     175                if (times<0) {
     176                    next = getNextIn(next, ratio);
     177                } else {
     178                    next = getNextOut(next, ratio);
     179                }
     180            }
     181            return next;
     182        }
     183
     184        /**
     185         * Get new scale for zoom in.
     186         * @param scale previous scale
     187         * @param ratio user defined zoom ratio
     188         * @return next scale in list or a new scale when zoomed outside
     189         */
     190        public Scale scaleZoomIn(double scale, double ratio) {
     191            Scale snap = getSnapScale(scale, ratio, false);
     192            Scale next = getNextIn(snap, ratio);
     193            return next;
     194        }
     195
     196        /**
     197         * Get new scale for zoom out.
     198         * @param scale previous scale
     199         * @param ratio user defined zoom ratio
     200         * @return next scale in list or a new scale when zoomed outside
     201         */
     202        public Scale scaleZoomOut(double scale, double ratio) {
     203            Scale snap = getSnapScale(scale, ratio, false);
     204            Scale next = getNextOut(snap, ratio);
     205            return next;
     206        }
     207
     208        @Override
     209        public String toString() {
     210            StringBuilder stringBuilder = new StringBuilder();
     211            int size = size();
     212            for (int i=0; i<size; i++) {
     213                stringBuilder.append(get(i) + "\n");
     214            }
     215            return stringBuilder.toString();
     216        }
     217
     218        private Scale getNextIn(Scale scale, double ratio) {
     219            int nextIndex = scale.getIndex() + 1;
     220            if (nextIndex <= 0 || nextIndex > size()-1) {
     221                return new Scale(scale.scale / ratio, nextIndex == 0, nextIndex);
     222            } else {
     223                Scale nextScale = get(nextIndex);
     224                return new Scale(nextScale.scale, nextScale.isNative, nextIndex);
     225            }
     226        }
     227
     228        private Scale getNextOut(Scale scale, double ratio) {
     229            int nextIndex = scale.getIndex() - 1;
     230            if (nextIndex < 0 || nextIndex >= size()-1) {
     231                return new Scale(scale.scale * ratio, nextIndex == size()-1, nextIndex);
     232            } else {
     233                Scale nextScale = get(nextIndex);
     234                return new Scale(nextScale.scale, nextScale.isNative, nextIndex);
     235            }
     236        }
     237    }
     238}
  • src/org/openstreetmap/josm/gui/layer/TMSLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/TMSLayer.java b/src/org/openstreetmap/josm/gui/layer/TMSLayer.java
    index aad0c67..58561df 100644
    a b package org.openstreetmap.josm.gui.layer; 
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import org.apache.commons.jcs.access.CacheAccess;
     7import org.openstreetmap.gui.jmapviewer.OsmMercator;
    78import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
    89import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
    910import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
    import org.openstreetmap.josm.data.projection.Projection; 
    2829 * @author Upliner &lt;upliner@gmail.com&gt;
    2930 *
    3031 */
    31 public class TMSLayer extends AbstractCachedTileSourceLayer {
     32public class TMSLayer extends AbstractCachedTileSourceLayer implements NativeScaleLayer {
    3233    private static final String CACHE_REGION_NAME = "TMS";
    3334
    3435    private static final String PREFERENCE_PREFIX = "imagery.tms";
    public class TMSLayer extends AbstractCachedTileSourceLayer { 
    144145    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
    145146        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
    146147    }
     148
     149    @Override
     150    public ScaleList getNativeScales() {
     151        ScaleList scales = new ScaleList();
     152        for (int zoom = info.getMinZoom(); zoom <= info.getMaxZoom(); zoom++) {
     153            double scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, zoom) / OsmMercator.DEFAUL_TILE_SIZE;
     154            scales.add(new Scale(scale));
     155        }
     156        return scales;
     157    }
    147158}
  • src/org/openstreetmap/josm/gui/layer/WMTSLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/WMTSLayer.java b/src/org/openstreetmap/josm/gui/layer/WMTSLayer.java
    index ad643e3..5da5d31 100644
    a b import org.openstreetmap.josm.gui.MapView; 
    3030 * @author Wiktor Niesiobędzki
    3131 *
    3232 */
    33 public class WMTSLayer extends AbstractCachedTileSourceLayer {
     33public class WMTSLayer extends AbstractCachedTileSourceLayer implements NativeScaleLayer {
    3434    /**
    3535     * default setting of autozoom per layer
    3636     */
    public class WMTSLayer extends AbstractCachedTileSourceLayer { 
    8181
    8282    @Override
    8383    protected int getBestZoom() {
    84         if (!Main.isDisplayingMapView()) return 1;
    85 
    86         for (int i = getMinZoomLvl() + 1; i <= getMaxZoomLvl(); i++) {
    87             double ret = getTileToScreenRatio(i);
    88             if (ret < 1) {
    89                 return i - 1;
     84        if (!Main.isDisplayingMapView()) return 0;
     85        ScaleList scaleList = getNativeScales();
     86        for (int i = scaleList.size()-1; i >= 0; i--) {
     87            Scale scale = scaleList.get(i);
     88            if (scale.scale >= Main.map.mapView.getScale()) {
     89                return i;
    9090            }
    9191        }
    92         return getMaxZoomLvl();
     92        return 0;
     93    }
     94
     95    @Override
     96    protected int getMaxZoomLvl() {
     97        return getNativeScales().size()-1;
     98    }
     99
     100    @Override
     101    protected int getMinZoomLvl() {
     102        return 0;
    93103    }
    94104
    95105    @Override
    public class WMTSLayer extends AbstractCachedTileSourceLayer { 
    129139    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
    130140        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
    131141    }
     142
     143    @Override
     144    public ScaleList getNativeScales() {
     145        return ((WMTSTileSource) tileSource).getNativeScales();
     146    }
    132147}