Ticket #7563: wms_auto_zoom_v2.patch

File wms_auto_zoom_v2.patch, 8.9 KB (added by xeen, 12 years ago)

the rounding was too simple, after all. In practice I regularly managed to create resolutions differing by only one meter. The updated patch now snaps the resolution to the same one as used for tile sources. It appears to work and since there is no fine tuning required, I will merge this in the next few days unless I encounter a show stopper.

  • src/org/openstreetmap/josm/gui/layer/WMSLayer.java

     
    9696        }
    9797    }
    9898
    99     private static final ObjectFactory OBJECT_FACTORY = null; // Fake reference to keep build scripts from removing ObjectFactory class. This class is not used directly but it's necessary for jaxb to work
     99    // Fake reference to keep build scripts from removing ObjectFactory class. This class is not used directly but it's necessary for jaxb to work
     100    private static final ObjectFactory OBJECT_FACTORY = null;
    100101
     102    // these values correspond to the zoom levels used throughout OSM and are in meters/pixel from zoom level 0 to 18.
     103    // taken from http://wiki.openstreetmap.org/wiki/Zoom_levels
     104    private static final Double[] snapLevels = { 156412.0, 78206.0, 39103.0, 19551.0, 9776.0, 4888.0,
     105        2444.0, 1222.0, 610.984, 305.492, 152.746, 76.373, 38.187, 19.093, 9.547, 4.773, 2.387, 1.193, 0.596 };
     106
    101107    public static final BooleanProperty PROP_ALPHA_CHANNEL = new BooleanProperty("imagery.wms.alpha_channel", true);
    102108    public static final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("imagery.wms.simultaneousConnections", 3);
    103109    public static final BooleanProperty PROP_OVERLAP = new BooleanProperty("imagery.wms.overlap", false);
     
    106112    public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty("imagery.wms.imageSize", 500);
    107113
    108114    public int messageNum = 5; //limit for messages per layer
    109     protected String resolution;
     115    protected double resolution;
     116    protected String resolutionText;
    110117    protected int imageSize;
    111118    protected int dax = 10;
    112119    protected int day = 10;
     
    116123    protected GeorefImage[][] images;
    117124    protected final int serializeFormatVersion = 5;
    118125    protected boolean autoDownloadEnabled = true;
     126    protected boolean autoResolutionEnabled = true;
    119127    protected boolean settingsChanged;
    120128    public WmsCache cache;
    121129    private AttributionSupport attribution = new AttributionSupport();
     
    179187                cache.loadIndex();
    180188            }
    181189        }
    182         if(this.info.getPixelPerDegree() == 0.0) {
    183             this.info.setPixelPerDegree(getPPD());
    184         }
    185         resolution = Main.map.mapView.getDist100PixelText();
    186190
     191        // if automatic resolution is enabled, ensure that the first zoom level
     192        // is already snapped. Otherwise it may load tiles that will never get
     193        // used again when zooming.
     194        updateResolutionSetting(this, autoResolutionEnabled);
     195
    187196        final MouseAdapter adapter = new MouseAdapter() {
    188197            @Override
    189198            public void mouseClicked(MouseEvent e) {
     
    286295
    287296    @Override public String getToolTipText() {
    288297        if(autoDownloadEnabled)
    289             return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolution);
     298            return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolutionText);
    290299        else
    291             return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolution);
     300            return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolutionText);
    292301    }
    293302
    294303    private int modulo (int a, int b) {
     
    303312    @Override public void paint(Graphics2D g, final MapView mv, Bounds b) {
    304313        if(info.getUrl() == null || (usesInvalidUrl && !isInvalidUrlConfirmed)) return;
    305314
     315        if (autoResolutionEnabled && getBestZoom() != mv.getDist100Pixel()) {
     316            changeResolution(this, true);
     317        }
     318
    306319        settingsChanged = false;
    307320
    308321        ProjectionBounds bounds = mv.getProjectionBounds();
     
    477490                new LayerSaveAsAction(this),
    478491                new BookmarkWmsAction(),
    479492                SeparatorLayerAction.INSTANCE,
    480                 new ZoomToNativeResolution(),
    481493                new StartStopAction(),
    482494                new ToggleAlphaAction(),
     495                new ToggleAutoResolutionAction(),
    483496                new ChangeResolutionAction(),
     497                new ZoomToNativeResolution(),
    484498                new ReloadErrorTilesAction(),
    485499                new DownloadAction(),
    486500                SeparatorLayerAction.INSTANCE,
     
    666680        }
    667681    }
    668682
    669     public static class ChangeResolutionAction extends AbstractAction implements LayerAction {
    670         public ChangeResolutionAction() {
    671             super(tr("Change resolution"));
     683    /**
     684     * Finds the most suitable resolution for the current zoom level, but prefers
     685     * higher resolutions. Snaps to values defined in snapLevels.
     686     * @return
     687     */
     688    private static double getBestZoom() {
     689        // not sure why getDist100Pixel returns values corresponding to
     690        // the snapLevels, which are in meters per pixel. It works, though.
     691        double dist = Main.map.mapView.getDist100Pixel();
     692        for(int i = snapLevels.length-2; i >= 0; i--) {
     693            if(snapLevels[i+1]/3 + snapLevels[i]*2/3 > dist)
     694                return snapLevels[i+1];
    672695        }
     696        return snapLevels[0];
     697    }
    673698
    674         private void changeResolution(WMSLayer layer) {
    675             layer.resolution = Main.map.mapView.getDist100PixelText();
    676             layer.info.setPixelPerDegree(layer.getPPD());
    677             layer.settingsChanged = true;
     699    /**
     700     * Updates the given layer’s resolution settings to the current zoom level. Does
     701     * not update existing tiles, only new ones will be subject to the new settings.
     702     *
     703     * @param layer
     704     * @param snap  Set to true if the resolution should snap to certain values instead of
     705     *              matching the current zoom level perfectly
     706     */
     707    private static void updateResolutionSetting(WMSLayer layer, boolean snap) {
     708        if(snap) {
     709            layer.resolution = getBestZoom();
     710            layer.resolutionText = MapView.getDistText(layer.resolution);
     711        } else {
     712            layer.resolution = Main.map.mapView.getDist100Pixel();
     713            layer.resolutionText = Main.map.mapView.getDist100PixelText();
     714        }
     715        layer.info.setPixelPerDegree(layer.getPPD());
     716    }
     717
     718    /**
     719     * Updates the given layer’s resolution settings to the current zoom level and
     720     * updates existing tiles. If round is true, tiles will be updated gradually, if
     721     * false they will be removed instantly (and redrawn only after the new resolution
     722     * image has been loaded).
     723     * @param layer
     724     * @param snap  Set to true if the resolution should snap to certain values instead of
     725     *              matching the current zoom level perfectly
     726     */
     727    private static void changeResolution(WMSLayer layer, boolean snap) {
     728        updateResolutionSetting(layer, snap);
     729
     730        layer.settingsChanged = true;
     731
     732        // Don’t move tiles off screen when the resolution is rounded. This
     733        // prevents some flickering when zooming with auto-resolution enabled
     734        // and instead gradually updates each tile.
     735        if(!snap) {
    678736            for(int x = 0; x<layer.dax; ++x) {
    679737                for(int y = 0; y<layer.day; ++y) {
    680738                    layer.images[x][y].changePosition(-1, -1);
    681739                }
    682740            }
    683741        }
     742    }
    684743
     744
     745    public static class ChangeResolutionAction extends AbstractAction implements LayerAction {
     746        public ChangeResolutionAction() {
     747            super(tr("Change resolution"));
     748        }
     749
    685750        @Override
    686751        public void actionPerformed(ActionEvent ev) {
    687752
     
    690755
    691756            List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
    692757            for (Layer l: layers) {
    693                 changeResolution((WMSLayer) l);
     758                changeResolution((WMSLayer) l, false);
    694759            }
    695760            Main.map.mapView.repaint();
    696761        }
     
    767832        }
    768833    }
    769834
     835
     836    public class ToggleAutoResolutionAction extends AbstractAction implements LayerAction {
     837        public ToggleAutoResolutionAction() {
     838            super(tr("Automatically change resolution"));
     839        }
     840
     841        @Override
     842        public void actionPerformed(ActionEvent ev) {
     843            JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
     844            autoResolutionEnabled = checkbox.isSelected();
     845        }
     846
     847        @Override
     848        public Component createMenuComponent() {
     849            JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
     850            item.setSelected(autoResolutionEnabled);
     851            return item;
     852        }
     853
     854        @Override
     855        public boolean supportLayers(List<Layer> layers) {
     856            return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
     857        }
     858    }
     859
    770860    /**
    771861     * This action will add a WMS layer menu entry with the current WMS layer
    772862     * URL and name extended by the current resolution.