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;
|
| 12 | 12 | |
| 13 | 13 | import org.openstreetmap.josm.data.ProjectionBounds; |
| 14 | 14 | import org.openstreetmap.josm.gui.help.Helpful; |
| | 15 | import static org.openstreetmap.josm.gui.MapView.snapZoomMode; |
| 15 | 16 | |
| 16 | 17 | class MapSlider extends JSlider implements PropertyChangeListener, ChangeListener, Helpful { |
| 17 | 18 | |
| 18 | 19 | private final MapView mv; |
| 19 | 20 | private boolean preventChange; |
| | 21 | private static final double zoomStep = Math.pow(2, 0.2); |
| 20 | 22 | |
| 21 | 23 | MapSlider(MapView mv) { |
| 22 | | super(35, 150); |
| | 24 | super(0, 150); |
| 23 | 25 | setOpaque(false); |
| 24 | 26 | this.mv = mv; |
| 25 | 27 | mv.addPropertyChangeListener("scale", this); |
| … |
… |
class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene
|
| 30 | 32 | |
| 31 | 33 | @Override |
| 32 | 34 | 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)); |
| 52 | 37 | preventChange = true; |
| 53 | 38 | setValue(zoom); |
| 54 | 39 | preventChange = false; |
| … |
… |
class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene
|
| 57 | 42 | @Override |
| 58 | 43 | public void stateChanged(ChangeEvent e) { |
| 59 | 44 | 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); |
| 67 | 52 | } |
| 68 | 53 | |
| 69 | 54 | @Override |
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
|
| 1103 | 1103 | |
| 1104 | 1104 | @Override |
| 1105 | 1105 | public void preferenceChanged(PreferenceChangeEvent e) { |
| | 1106 | super.preferenceChanged(e); |
| 1106 | 1107 | synchronized (this) { |
| 1107 | 1108 | paintPreferencesChanged = true; |
| 1108 | 1109 | } |
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;
|
| 45 | 45 | import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; |
| 46 | 46 | import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; |
| 47 | 47 | import org.openstreetmap.josm.data.preferences.IntegerProperty; |
| | 48 | import org.openstreetmap.josm.data.preferences.BooleanProperty; |
| 48 | 49 | import org.openstreetmap.josm.data.projection.Projection; |
| 49 | 50 | import org.openstreetmap.josm.data.projection.Projections; |
| | 51 | import org.openstreetmap.josm.data.projection.proj.Mercator; |
| 50 | 52 | import org.openstreetmap.josm.gui.download.DownloadDialog; |
| 51 | 53 | import org.openstreetmap.josm.gui.help.Helpful; |
| 52 | 54 | import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; |
| … |
… |
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
|
| 54 | 56 | import org.openstreetmap.josm.gui.util.CursorManager; |
| 55 | 57 | import org.openstreetmap.josm.tools.Predicate; |
| 56 | 58 | import org.openstreetmap.josm.tools.Utils; |
| | 59 | import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; |
| | 60 | import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; |
| 57 | 61 | |
| 58 | 62 | /** |
| 59 | 63 | * A component that can be navigated by a {@link MapMover}. Used as map view and for the |
| … |
… |
import org.openstreetmap.josm.tools.Utils;
|
| 62 | 66 | * @author imi |
| 63 | 67 | * @since 41 |
| 64 | 68 | */ |
| 65 | | public class NavigatableComponent extends JComponent implements Helpful { |
| | 69 | public class NavigatableComponent extends JComponent implements Helpful, PreferenceChangedListener { |
| 66 | 70 | |
| 67 | 71 | /** |
| 68 | 72 | * Interface to notify listeners of the change of the zoom area. |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 89 | 93 | }; |
| 90 | 94 | |
| 91 | 95 | 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 } |
| 92 | 98 | |
| 93 | 99 | public static final String PROPNAME_CENTER = "center"; |
| 94 | 100 | public static final String PROPNAME_SCALE = "scale"; |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 143 | 149 | */ |
| 144 | 150 | public NavigatableComponent() { |
| 145 | 151 | setLayout(null); |
| | 152 | Main.pref.addPreferenceChangeListener(this); |
| | 153 | scale = getSnappedScale(scale, snapZoomMode.ROUND); |
| 146 | 154 | } |
| 147 | 155 | |
| 148 | 156 | protected DataSet getCurrentDataSet() { |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 284 | 292 | getProjection().latlon2eastNorth(b.getMax())); |
| 285 | 293 | } |
| 286 | 294 | |
| | 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 | |
| 287 | 306 | /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */ |
| 288 | 307 | public Bounds getRealBounds() { |
| 289 | 308 | return new Bounds( |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 409 | 428 | * @param initial true if this call initializes the viewport. |
| 410 | 429 | */ |
| 411 | 430 | public void zoomTo(EastNorth newCenter, double newScale, boolean initial) { |
| 412 | | Bounds b = getProjection().getWorldBoundsLatLon(); |
| 413 | 431 | ProjectionBounds pb = getProjection().getWorldBoundsBoxEastNorth(); |
| 414 | | int width = getWidth(); |
| 415 | | int height = getHeight(); |
| 416 | 432 | |
| 417 | 433 | // make sure, the center of the screen is within projection bounds |
| 418 | 434 | double east = newCenter.east(); |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 423 | 439 | north = Math.min(north, pb.maxNorth); |
| 424 | 440 | newCenter = new EastNorth(east, north); |
| 425 | 441 | |
| 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()); |
| 437 | 444 | |
| 438 | 445 | // 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; |
| 449 | 450 | |
| | 451 | newScale = getSnappedScale(newScale, snapZoomMode.FLOOR); |
| 450 | 452 | if (!newCenter.equals(center) || !Utils.equalsEpsilon(scale, newScale)) { |
| 451 | 453 | if (!initial) { |
| 452 | 454 | pushZoomUndo(center, scale); |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 455 | 457 | } |
| 456 | 458 | } |
| 457 | 459 | |
| | 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 | |
| 458 | 479 | /** |
| 459 | 480 | * Zoom to the given coordinate without adding to the zoom undo buffer. |
| 460 | 481 | * |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 484 | 505 | } |
| 485 | 506 | } |
| 486 | 507 | |
| | 508 | public void zoomTo(double newScale) { |
| | 509 | zoomTo(center, newScale); |
| | 510 | } |
| | 511 | |
| 487 | 512 | public void zoomTo(EastNorth newCenter) { |
| 488 | 513 | zoomTo(newCenter, scale); |
| 489 | 514 | } |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 529 | 554 | } |
| 530 | 555 | |
| 531 | 556 | public void zoomToFactor(double x, double y, double factor) { |
| 532 | | double newScale = scale*factor; |
| | 557 | double newScale = getSnappedScale(scale*factor); |
| 533 | 558 | // New center position so that point under the mouse pointer stays the same place as it was before zooming |
| 534 | 559 | // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom |
| 535 | 560 | zoomTo(new EastNorth( |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 539 | 564 | } |
| 540 | 565 | |
| 541 | 566 | public void zoomToFactor(EastNorth newCenter, double factor) { |
| 542 | | zoomTo(newCenter, scale*factor); |
| | 567 | zoomTo(newCenter, getSnappedScale(scale*factor)); |
| 543 | 568 | } |
| 544 | 569 | |
| 545 | 570 | public void zoomToFactor(double factor) { |
| 546 | | zoomTo(center, scale*factor); |
| | 571 | zoomTo(center, getSnappedScale(scale*factor)); |
| 547 | 572 | } |
| 548 | 573 | |
| 549 | 574 | public void zoomTo(ProjectionBounds box) { |
| … |
… |
public class NavigatableComponent extends JComponent implements Helpful {
|
| 1516 | 1541 | } |
| 1517 | 1542 | repaint(); |
| 1518 | 1543 | } |
| | 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 | } |
| 1519 | 1551 | } |
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;
|
| 84 | 84 | import org.openstreetmap.josm.gui.progress.ProgressMonitor; |
| 85 | 85 | import org.openstreetmap.josm.io.WMSLayerImporter; |
| 86 | 86 | import org.openstreetmap.josm.tools.GBC; |
| | 87 | import org.openstreetmap.josm.tools.Utils; |
| 87 | 88 | |
| 88 | 89 | /** |
| 89 | 90 | * 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
|
| 278 | 279 | |
| 279 | 280 | int screenPixels = mv.getWidth()*mv.getHeight(); |
| 280 | 281 | double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize()); |
| | 282 | tilePixels = Utils.roundToSignificantDigits(tilePixels, 9); |
| 281 | 283 | if (screenPixels == 0 || tilePixels == 0) return 1; |
| 282 | 284 | return screenPixels/tilePixels; |
| 283 | 285 | } |
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;
|
| 24 | 24 | import org.openstreetmap.josm.Main; |
| 25 | 25 | import org.openstreetmap.josm.actions.ExpertToggleAction; |
| 26 | 26 | import org.openstreetmap.josm.gui.dialogs.ToggleDialog; |
| | 27 | import org.openstreetmap.josm.gui.NavigatableComponent; |
| 27 | 28 | import org.openstreetmap.josm.gui.preferences.PreferenceSetting; |
| 28 | 29 | import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; |
| 29 | 30 | import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; |
| … |
… |
public class LafPreference implements SubPreferenceSetting {
|
| 61 | 62 | private final JCheckBox dynamicButtons = new JCheckBox(tr("Dynamic buttons in side menus")); |
| 62 | 63 | private final JCheckBox isoDates = new JCheckBox(tr("Display ISO dates")); |
| 63 | 64 | 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)")); |
| 64 | 66 | |
| 65 | 67 | @Override |
| 66 | 68 | public void addGui(PreferenceTabbedPane gui) { |
| … |
… |
public class LafPreference implements SubPreferenceSetting {
|
| 140 | 142 | nativeFileChoosers.setSelected(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get()); |
| 141 | 143 | panel.add(nativeFileChoosers, GBC.eop().insets(20, 0, 0, 0)); |
| 142 | 144 | |
| | 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 | |
| 143 | 150 | panel.add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0)); |
| 144 | 151 | |
| 145 | 152 | panel.add(new JLabel(tr("Look and Feel")), GBC.std().insets(20, 0, 0, 0)); |
| … |
… |
public class LafPreference implements SubPreferenceSetting {
|
| 161 | 168 | Main.pref.put(ToggleDialog.PROP_DYNAMIC_BUTTONS.getKey(), dynamicButtons.isSelected()); |
| 162 | 169 | Main.pref.put(DateUtils.PROP_ISO_DATES.getKey(), isoDates.isSelected()); |
| 163 | 170 | Main.pref.put(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.getKey(), nativeFileChoosers.isSelected()); |
| | 171 | Main.pref.put(NavigatableComponent.PROP_SNAP_ZOOM.getKey(), snapToMercatorZoomLevels.isSelected()); |
| 164 | 172 | mod |= Main.pref.put("laf", ((LookAndFeelInfo) lafCombo.getSelectedItem()).getClassName()); |
| 165 | 173 | return mod; |
| 166 | 174 | } |
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 {
|
| 1636 | 1636 | return gvs; |
| 1637 | 1637 | } |
| 1638 | 1638 | |
| | 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 | |
| 1639 | 1644 | } |