diff --git a/src/org/openstreetmap/josm/actions/AutoScaleAction.java b/src/org/openstreetmap/josm/actions/AutoScaleAction.java
index c5cd57374..832c13196 100644
|
a
|
b
|
public void autoScale() {
|
| 291 | 291 | case NEXT: |
| 292 | 292 | mapView.zoomNext(); |
| 293 | 293 | break; |
| 294 | | default: |
| 295 | | BoundingXYVisitor bbox = getBoundingBox(); |
| 296 | | if (bbox != null && bbox.getBounds() != null) { |
| 297 | | mapView.zoomTo(bbox); |
| 298 | | } |
| | 294 | case PROBLEM: |
| | 295 | modeProblem(new ValidatorBoundingXYVisitor()); |
| | 296 | break; |
| | 297 | case DATA: |
| | 298 | modeData(new BoundingXYVisitor()); |
| | 299 | break; |
| | 300 | case LAYER: |
| | 301 | modeLayer(new BoundingXYVisitor()); |
| | 302 | break; |
| | 303 | case SELECTION: |
| | 304 | case CONFLICT: |
| | 305 | modeSelectionOrConflict(new BoundingXYVisitor()); |
| | 306 | break; |
| | 307 | case DOWNLOAD: |
| | 308 | modeDownload(new BoundingXYVisitor()); |
| | 309 | break; |
| 299 | 310 | } |
| | 311 | putValue("active", Boolean.TRUE); |
| 300 | 312 | } |
| 301 | | putValue("active", Boolean.TRUE); |
| 302 | 313 | } |
| 303 | 314 | |
| 304 | 315 | @Override |
| … |
… |
protected Layer getFirstSelectedLayer() {
|
| 327 | 338 | return null; |
| 328 | 339 | } |
| 329 | 340 | |
| 330 | | private BoundingXYVisitor getBoundingBox() { |
| 331 | | switch (mode) { |
| 332 | | case PROBLEM: |
| 333 | | return modeProblem(new ValidatorBoundingXYVisitor()); |
| 334 | | case DATA: |
| 335 | | return modeData(new BoundingXYVisitor()); |
| 336 | | case LAYER: |
| 337 | | return modeLayer(new BoundingXYVisitor()); |
| 338 | | case SELECTION: |
| 339 | | case CONFLICT: |
| 340 | | return modeSelectionOrConflict(new BoundingXYVisitor()); |
| 341 | | case DOWNLOAD: |
| 342 | | return modeDownload(new BoundingXYVisitor()); |
| 343 | | default: |
| 344 | | return new BoundingXYVisitor(); |
| 345 | | } |
| 346 | | } |
| 347 | | |
| 348 | | private static BoundingXYVisitor modeProblem(ValidatorBoundingXYVisitor v) { |
| | 341 | private static void modeProblem(ValidatorBoundingXYVisitor v) { |
| 349 | 342 | TestError error = MainApplication.getMap().validatorDialog.getSelectedError(); |
| 350 | 343 | if (error == null) |
| 351 | | return null; |
| | 344 | return; |
| 352 | 345 | v.visit(error); |
| 353 | 346 | if (v.getBounds() == null) |
| 354 | | return null; |
| 355 | | v.enlargeBoundingBox(Config.getPref().getDouble("validator.zoom-enlarge-bbox", 0.0002)); |
| 356 | | return v; |
| | 347 | return; |
| | 348 | MainApplication.getMap().mapView.zoomTo(v); |
| 357 | 349 | } |
| 358 | 350 | |
| 359 | | private static BoundingXYVisitor modeData(BoundingXYVisitor v) { |
| | 351 | private static void modeData(BoundingXYVisitor v) { |
| 360 | 352 | for (Layer l : MainApplication.getLayerManager().getLayers()) { |
| 361 | 353 | l.visitBoundingBox(v); |
| 362 | 354 | } |
| 363 | | return v; |
| | 355 | MainApplication.getMap().mapView.zoomTo(v); |
| 364 | 356 | } |
| 365 | 357 | |
| 366 | | private BoundingXYVisitor modeLayer(BoundingXYVisitor v) { |
| | 358 | private void modeLayer(BoundingXYVisitor v) { |
| 367 | 359 | // try to zoom to the first selected layer |
| 368 | 360 | Layer l = getFirstSelectedLayer(); |
| 369 | 361 | if (l == null) |
| 370 | | return null; |
| | 362 | return; |
| 371 | 363 | l.visitBoundingBox(v); |
| 372 | | return v; |
| | 364 | MainApplication.getMap().mapView.zoomTo(v); |
| 373 | 365 | } |
| 374 | 366 | |
| 375 | | private BoundingXYVisitor modeSelectionOrConflict(BoundingXYVisitor v) { |
| | 367 | private void modeSelectionOrConflict(BoundingXYVisitor v) { |
| 376 | 368 | Collection<IPrimitive> sel = new HashSet<>(); |
| 377 | 369 | if (AutoScaleMode.SELECTION == mode) { |
| 378 | 370 | OsmData<?, ?, ?, ?> dataSet = getLayerManager().getActiveData(); |
| … |
… |
private BoundingXYVisitor modeSelectionOrConflict(BoundingXYVisitor v) {
|
| 394 | 386 | AutoScaleMode.SELECTION == mode ? tr("Nothing selected to zoom to.") : tr("No conflicts to zoom to"), |
| 395 | 387 | tr("Information"), |
| 396 | 388 | JOptionPane.INFORMATION_MESSAGE); |
| 397 | | return null; |
| | 389 | return; |
| 398 | 390 | } |
| 399 | 391 | for (IPrimitive osm : sel) { |
| 400 | 392 | osm.accept(v); |
| 401 | 393 | } |
| | 394 | if (v.getBounds() == null) { |
| | 395 | return; |
| | 396 | } |
| 402 | 397 | |
| 403 | | // Increase the bounding box by up to 100% to give more context. |
| 404 | | v.enlargeBoundingBoxLogarithmically(100); |
| 405 | | // Make the bounding box at least 100 meter wide to |
| 406 | | // ensure reasonable zoom level when zooming onto single nodes. |
| 407 | | v.enlargeToMinSize(Config.getPref().getDouble("zoom_to_selection_min_size_in_meter", 100)); |
| 408 | | return v; |
| | 398 | // Do not zoom if the current scale covers the selection, #16706 |
| | 399 | final MapView mapView = MainApplication.getMap().mapView; |
| | 400 | final double mapScale = mapView.getScale(); |
| | 401 | final double minScale = v.getBounds().getScale(mapView.getWidth(), mapView.getHeight()); |
| | 402 | v.enlargeBoundingBoxLogarithmically(); |
| | 403 | final double maxScale = v.getBounds().getScale(mapView.getWidth(), mapView.getHeight()); |
| | 404 | if (minScale <= mapScale && mapScale < maxScale) { |
| | 405 | mapView.zoomTo(v.getBounds().getCenter()); |
| | 406 | } else { |
| | 407 | mapView.zoomTo(v); |
| | 408 | } |
| 409 | 409 | } |
| 410 | 410 | |
| 411 | | private BoundingXYVisitor modeDownload(BoundingXYVisitor v) { |
| | 411 | private void modeDownload(BoundingXYVisitor v) { |
| 412 | 412 | if (lastZoomTime > 0 && |
| 413 | 413 | System.currentTimeMillis() - lastZoomTime > Config.getPref().getLong("zoom.bounds.reset.time", TimeUnit.SECONDS.toMillis(10))) { |
| 414 | 414 | lastZoomTime = -1; |
| … |
… |
private BoundingXYVisitor modeDownload(BoundingXYVisitor v) {
|
| 437 | 437 | lastZoomArea = -1; |
| 438 | 438 | } |
| 439 | 439 | } |
| 440 | | return v; |
| | 440 | MainApplication.getMap().mapView.zoomTo(v); |
| 441 | 441 | } |
| 442 | 442 | |
| 443 | 443 | @Override |
diff --git a/src/org/openstreetmap/josm/data/ProjectionBounds.java b/src/org/openstreetmap/josm/data/ProjectionBounds.java
index e34a2db01..d3fdc0306 100644
|
a
|
b
|
public EastNorth getMax() {
|
| 191 | 191 | public boolean hasExtend() { |
| 192 | 192 | return !Utils.equalsEpsilon(minEast, maxEast) || !Utils.equalsEpsilon(minNorth, maxNorth); |
| 193 | 193 | } |
| | 194 | |
| | 195 | /** |
| | 196 | * Computes the scale of this bounds with respect to the given width/height. |
| | 197 | * @param width the width |
| | 198 | * @param height the height |
| | 199 | * @return the computed scale |
| | 200 | */ |
| | 201 | public double getScale(final int width, final int height) { |
| | 202 | // -20 to leave some border |
| | 203 | int w = width - 20; |
| | 204 | if (w < 20) { |
| | 205 | w = 20; |
| | 206 | } |
| | 207 | int h = height - 20; |
| | 208 | if (h < 20) { |
| | 209 | h = 20; |
| | 210 | } |
| | 211 | |
| | 212 | double scaleX = getDeltaEast() / w; |
| | 213 | double scaleY = getDeltaNorth() / h; |
| | 214 | return Math.max(scaleX, scaleY); |
| | 215 | } |
| | 216 | |
| | 217 | private double getDeltaNorth() { |
| | 218 | return maxNorth - minNorth; |
| | 219 | } |
| | 220 | |
| | 221 | private double getDeltaEast() { |
| | 222 | return maxEast - minEast; |
| | 223 | } |
| 194 | 224 | } |
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java b/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java
index c8f1cdc2a..5da8bdf31 100644
|
a
|
b
|
|
| 2 | 2 | package org.openstreetmap.josm.data.osm.visitor; |
| 3 | 3 | |
| 4 | 4 | import java.util.Collection; |
| | 5 | import java.util.function.DoubleUnaryOperator; |
| 5 | 6 | |
| 6 | 7 | import org.openstreetmap.josm.data.Bounds; |
| 7 | 8 | import org.openstreetmap.josm.data.ProjectionBounds; |
| … |
… |
|
| 18 | 19 | import org.openstreetmap.josm.data.osm.Relation; |
| 19 | 20 | import org.openstreetmap.josm.data.osm.Way; |
| 20 | 21 | import org.openstreetmap.josm.data.projection.ProjectionRegistry; |
| 21 | | import org.openstreetmap.josm.gui.MainApplication; |
| 22 | | import org.openstreetmap.josm.gui.MapFrame; |
| 23 | 22 | import org.openstreetmap.josm.spi.preferences.Config; |
| 24 | 23 | |
| 25 | 24 | /** |
| … |
… |
public ProjectionBounds getBounds() {
|
| 140 | 139 | } |
| 141 | 140 | |
| 142 | 141 | /** |
| 143 | | * Enlarges the calculated bounding box by 0.002 degrees. |
| | 142 | * Enlarges the calculated bounding box by 0.001 degrees. |
| 144 | 143 | * If the bounding box has not been set (<code>min</code> or <code>max</code> |
| 145 | 144 | * equal <code>null</code>) this method does not do anything. |
| 146 | 145 | */ |
| 147 | 146 | public void enlargeBoundingBox() { |
| 148 | | enlargeBoundingBox(Config.getPref().getDouble("edit.zoom-enlarge-bbox", 0.002)); |
| | 147 | final double enlarge = Config.getPref().getDouble("edit.zoom-enlarge-bbox", 0.001); |
| | 148 | enlargeBoundingBox(enlarge, enlarge); |
| 149 | 149 | } |
| 150 | 150 | |
| 151 | 151 | /** |
| … |
… |
public void enlargeBoundingBox() {
|
| 153 | 153 | * If the bounding box has not been set (<code>min</code> or <code>max</code> |
| 154 | 154 | * equal <code>null</code>) this method does not do anything. |
| 155 | 155 | * |
| 156 | | * @param enlargeDegree number of degrees to enlarge on each side |
| | 156 | * @param enlargeDegreeX number of degrees to enlarge on each side along X |
| | 157 | * @param enlargeDegreeY number of degrees to enlarge on each side along Y |
| 157 | 158 | */ |
| 158 | | public void enlargeBoundingBox(double enlargeDegree) { |
| | 159 | public void enlargeBoundingBox(double enlargeDegreeX, double enlargeDegreeY) { |
| 159 | 160 | if (bounds == null) |
| 160 | 161 | return; |
| 161 | 162 | LatLon minLatlon = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMin()); |
| 162 | 163 | LatLon maxLatlon = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMax()); |
| 163 | 164 | bounds = new ProjectionBounds(new LatLon( |
| 164 | | Math.max(-90, minLatlon.lat() - enlargeDegree), |
| 165 | | Math.max(-180, minLatlon.lon() - enlargeDegree)).getEastNorth(ProjectionRegistry.getProjection()), |
| | 165 | Math.max(-90, minLatlon.lat() - enlargeDegreeY), |
| | 166 | Math.max(-180, minLatlon.lon() - enlargeDegreeX)).getEastNorth(ProjectionRegistry.getProjection()), |
| 166 | 167 | new LatLon( |
| 167 | | Math.min(90, maxLatlon.lat() + enlargeDegree), |
| 168 | | Math.min(180, maxLatlon.lon() + enlargeDegree)).getEastNorth(ProjectionRegistry.getProjection())); |
| | 168 | Math.min(90, maxLatlon.lat() + enlargeDegreeY), |
| | 169 | Math.min(180, maxLatlon.lon() + enlargeDegreeX)).getEastNorth(ProjectionRegistry.getProjection())); |
| 169 | 170 | } |
| 170 | 171 | |
| 171 | 172 | /** |
| 172 | | * Enlarges the bounding box up to <code>maxEnlargePercent</code>, depending on |
| | 173 | * Enlarges the bounding box up to 0.001 degrees, depending on |
| 173 | 174 | * its size. If the bounding box is small, it will be enlarged more in relation |
| 174 | 175 | * to its beginning size. The larger the bounding box, the smaller the change, |
| 175 | | * down to the minimum of 1% enlargement. |
| 176 | | * |
| 177 | | * Warning: if the bounding box only contains a single node, no expansion takes |
| 178 | | * place because a node has no width/height. Use {@link #enlargeBoundingBox(double)} |
| 179 | | * instead. |
| 180 | | * |
| 181 | | * Example: You specify enlargement to be up to 100%. |
| 182 | | * |
| 183 | | * Bounding box is a small house: enlargement will be 95–100%, i.e. |
| 184 | | * making enough space so that the house fits twice on the screen in |
| 185 | | * each direction. |
| 186 | | * |
| 187 | | * Bounding box is a large landuse, like a forest: Enlargement will |
| 188 | | * be 1–10%, i.e. just add a little border around the landuse. |
| | 176 | * down to 0.0 degrees. |
| 189 | 177 | * |
| 190 | 178 | * If the bounding box has not been set (<code>min</code> or <code>max</code> |
| 191 | 179 | * equal <code>null</code>) this method does not do anything. |
| 192 | | * |
| 193 | | * @param maxEnlargePercent maximum enlargement in percentage (100.0 for 100%) |
| 194 | | */ |
| 195 | | public void enlargeBoundingBoxLogarithmically(double maxEnlargePercent) { |
| 196 | | if (bounds == null) |
| 197 | | return; |
| 198 | | |
| 199 | | double diffEast = bounds.getMax().east() - bounds.getMin().east(); |
| 200 | | double diffNorth = bounds.getMax().north() - bounds.getMin().north(); |
| 201 | | |
| 202 | | double enlargeEast = Math.min(maxEnlargePercent - 10*Math.log(diffEast), 1)/100; |
| 203 | | double enlargeNorth = Math.min(maxEnlargePercent - 10*Math.log(diffNorth), 1)/100; |
| 204 | | |
| 205 | | visit(bounds.getMin().add(-enlargeEast/2, -enlargeNorth/2)); |
| 206 | | visit(bounds.getMax().add(+enlargeEast/2, +enlargeNorth/2)); |
| 207 | | } |
| 208 | | |
| 209 | | /** |
| 210 | | * Specify a degree larger than 0 in order to make the bounding box at least |
| 211 | | * the specified size in width and height. The value is ignored if the |
| 212 | | * bounding box is already larger than the specified amount. |
| 213 | | * |
| 214 | | * If the bounding box has not been set (<code>min</code> or <code>max</code> |
| 215 | | * equal <code>null</code>) this method does not do anything. |
| 216 | | * |
| 217 | | * If the bounding box contains objects and is to be enlarged, the objects |
| 218 | | * will be centered within the new bounding box. |
| 219 | | * |
| 220 | | * @param size minimum width and height in meter |
| 221 | 180 | */ |
| 222 | | public void enlargeToMinSize(double size) { |
| | 181 | public void enlargeBoundingBoxLogarithmically() { |
| 223 | 182 | if (bounds == null) |
| 224 | 183 | return; |
| 225 | | // convert size from meters to east/north units |
| 226 | | MapFrame map = MainApplication.getMap(); |
| 227 | | double enSize = size * map.mapView.getScale() / map.mapView.getDist100Pixel() * 100; |
| 228 | | visit(bounds.getMin().add(-enSize/2, -enSize/2)); |
| 229 | | visit(bounds.getMax().add(+enSize/2, +enSize/2)); |
| | 184 | final LatLon min = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMin()); |
| | 185 | final LatLon max = ProjectionRegistry.getProjection().eastNorth2latlon(bounds.getMax()); |
| | 186 | final double deltaLat = max.lat() - min.lat(); |
| | 187 | final double deltaLon = max.lon() - min.lon(); |
| | 188 | // [0.001, 0.1] degree -> [0.001, 0.0] degree enlargement |
| | 189 | final DoubleUnaryOperator enlargement = deg -> deg < 0.001 |
| | 190 | ? 0.001 |
| | 191 | : deg < 0.1 |
| | 192 | ? 0.001 - deg / 100 |
| | 193 | : 0.0; |
| | 194 | enlargeBoundingBox(enlargement.applyAsDouble(deltaLon), enlargement.applyAsDouble(deltaLat)); |
| 230 | 195 | } |
| 231 | 196 | |
| 232 | 197 | @Override |
diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
index fcc63ecf3..87e28de08 100644
|
a
|
b
|
public void zoomToFactor(double factor) {
|
| 789 | 789 | * @param box new projection bounds |
| 790 | 790 | */ |
| 791 | 791 | public void zoomTo(ProjectionBounds box) { |
| 792 | | // -20 to leave some border |
| 793 | | int w = getWidth()-20; |
| 794 | | if (w < 20) { |
| 795 | | w = 20; |
| 796 | | } |
| 797 | | int h = getHeight()-20; |
| 798 | | if (h < 20) { |
| 799 | | h = 20; |
| 800 | | } |
| 801 | | |
| 802 | | double scaleX = (box.maxEast-box.minEast)/w; |
| 803 | | double scaleY = (box.maxNorth-box.minNorth)/h; |
| 804 | | double newScale = Math.max(scaleX, scaleY); |
| 805 | | |
| | 792 | double newScale = box.getScale(getWidth(), getHeight()); |
| 806 | 793 | newScale = scaleFloor(newScale); |
| 807 | 794 | zoomTo(box.getCenter(), newScale); |
| 808 | 795 | } |