source: josm/trunk/src/org/openstreetmap/josm/gui/MapViewState.java@ 10894

Last change on this file since 10894 was 10875, checked in by Don-vip, 8 years ago

fix #13413 - Clean ImproveWayAccuracyAction, add new class MapViewPath (patch by michael2402, modified) - gsoc-core

  • Property svn:eol-style set to native
File size: 21.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import java.awt.Container;
5import java.awt.Point;
6import java.awt.geom.AffineTransform;
7import java.awt.geom.Area;
8import java.awt.geom.Path2D;
9import java.awt.geom.Point2D;
10import java.awt.geom.Point2D.Double;
11import java.awt.geom.Rectangle2D;
12import java.io.Serializable;
13
14import javax.swing.JComponent;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.Bounds;
18import org.openstreetmap.josm.data.ProjectionBounds;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.projection.Projecting;
23import org.openstreetmap.josm.data.projection.Projection;
24import org.openstreetmap.josm.gui.download.DownloadDialog;
25import org.openstreetmap.josm.tools.bugreport.BugReport;
26
27/**
28 * This class represents a state of the {@link MapView}.
29 * @author Michael Zangl
30 * @since 10343
31 */
32public final class MapViewState implements Serializable {
33
34 private static final long serialVersionUID = 1L;
35
36 /**
37 * A flag indicating that the point is outside to the top of the map view.
38 * @since 10827
39 */
40 public static final int OUTSIDE_TOP = 1;
41
42 /**
43 * A flag indicating that the point is outside to the bottom of the map view.
44 * @since 10827
45 */
46 public static final int OUTSIDE_BOTTOM = 2;
47
48 /**
49 * A flag indicating that the point is outside to the left of the map view.
50 * @since 10827
51 */
52 public static final int OUTSIDE_LEFT = 4;
53
54 /**
55 * A flag indicating that the point is outside to the right of the map view.
56 * @since 10827
57 */
58 public static final int OUTSIDE_RIGHT = 8;
59
60 private final transient Projecting projecting;
61
62 private final int viewWidth;
63 private final int viewHeight;
64
65 private final double scale;
66
67 /**
68 * Top left {@link EastNorth} coordinate of the view.
69 */
70 private final EastNorth topLeft;
71
72 private final Point topLeftOnScreen;
73 private final Point topLeftInWindow;
74
75 /**
76 * Create a new {@link MapViewState}
77 * @param projection The projection to use.
78 * @param viewWidth The view width
79 * @param viewHeight The view height
80 * @param scale The scale to use
81 * @param topLeft The top left corner in east/north space.
82 */
83 private MapViewState(Projecting projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
84 this.projecting = projection;
85 this.scale = scale;
86 this.topLeft = topLeft;
87
88 this.viewWidth = viewWidth;
89 this.viewHeight = viewHeight;
90 topLeftInWindow = new Point(0, 0);
91 topLeftOnScreen = new Point(0, 0);
92 }
93
94 private MapViewState(EastNorth topLeft, MapViewState mapViewState) {
95 this.projecting = mapViewState.projecting;
96 this.scale = mapViewState.scale;
97 this.topLeft = topLeft;
98
99 viewWidth = mapViewState.viewWidth;
100 viewHeight = mapViewState.viewHeight;
101 topLeftInWindow = mapViewState.topLeftInWindow;
102 topLeftOnScreen = mapViewState.topLeftOnScreen;
103 }
104
105 private MapViewState(double scale, MapViewState mapViewState) {
106 this.projecting = mapViewState.projecting;
107 this.scale = scale;
108 this.topLeft = mapViewState.topLeft;
109
110 viewWidth = mapViewState.viewWidth;
111 viewHeight = mapViewState.viewHeight;
112 topLeftInWindow = mapViewState.topLeftInWindow;
113 topLeftOnScreen = mapViewState.topLeftOnScreen;
114 }
115
116 private MapViewState(JComponent position, MapViewState mapViewState) {
117 this.projecting = mapViewState.projecting;
118 this.scale = mapViewState.scale;
119 this.topLeft = mapViewState.topLeft;
120
121 viewWidth = position.getWidth();
122 viewHeight = position.getHeight();
123 topLeftInWindow = new Point();
124 // better than using swing utils, since this allows us to use the mehtod if no screen is present.
125 Container component = position;
126 while (component != null) {
127 topLeftInWindow.x += component.getX();
128 topLeftInWindow.y += component.getY();
129 component = component.getParent();
130 }
131 try {
132 topLeftOnScreen = position.getLocationOnScreen();
133 } catch (RuntimeException e) {
134 throw BugReport.intercept(e).put("position", position).put("parent", position::getParent);
135 }
136 }
137
138 private MapViewState(Projecting projecting, MapViewState mapViewState) {
139 this.projecting = projecting;
140 this.scale = mapViewState.scale;
141 this.topLeft = mapViewState.topLeft;
142
143 viewWidth = mapViewState.viewWidth;
144 viewHeight = mapViewState.viewHeight;
145 topLeftInWindow = mapViewState.topLeftInWindow;
146 topLeftOnScreen = mapViewState.topLeftOnScreen;
147 }
148
149 /**
150 * The scale in east/north units per pixel.
151 * @return The scale.
152 */
153 public double getScale() {
154 return scale;
155 }
156
157 /**
158 * Gets the MapViewPoint representation for a position in view coordinates.
159 * @param x The x coordinate inside the view.
160 * @param y The y coordinate inside the view.
161 * @return The MapViewPoint.
162 */
163 public MapViewPoint getForView(double x, double y) {
164 return new MapViewViewPoint(x, y);
165 }
166
167 /**
168 * Gets the {@link MapViewPoint} for the given {@link EastNorth} coordinate.
169 * @param eastNorth the position.
170 * @return The point for that position.
171 */
172 public MapViewPoint getPointFor(EastNorth eastNorth) {
173 return new MapViewEastNorthPoint(eastNorth);
174 }
175
176 /**
177 * Gets the {@link MapViewPoint} for the given {@link LatLon} coordinate.
178 * @param latlon the position
179 * @return The point for that position.
180 * @since 10651
181 */
182 public MapViewPoint getPointFor(LatLon latlon) {
183 return getPointFor(getProjection().latlon2eastNorth(latlon));
184 }
185
186 /**
187 * Gets the {@link MapViewPoint} for the given node. This is faster than {@link #getPointFor(LatLon)} because it uses the node east/north
188 * cache.
189 * @param node The node
190 * @return The position of that node.
191 * @since 10827
192 */
193 public MapViewPoint getPointFor(Node node) {
194 return getPointFor(node.getEastNorth(getProjection()));
195 }
196
197 /**
198 * Gets a rectangle representing the whole view area.
199 * @return The rectangle.
200 */
201 public MapViewRectangle getViewArea() {
202 return getForView(0, 0).rectTo(getForView(viewWidth, viewHeight));
203 }
204
205 /**
206 * Gets a rectangle of the view as map view area.
207 * @param rectangle The rectangle to get.
208 * @return The view area.
209 * @since 10827
210 */
211 public MapViewRectangle getViewArea(Rectangle2D rectangle) {
212 return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY()));
213 }
214
215 /**
216 * Gets the center of the view.
217 * @return The center position.
218 */
219 public MapViewPoint getCenter() {
220 return getForView(viewWidth / 2.0, viewHeight / 2.0);
221 }
222
223 /**
224 * Gets the center of the view, rounded to a pixel coordinate
225 * @return The center position.
226 * @since 10856
227 */
228 public MapViewPoint getCenterAtPixel() {
229 return getForView(viewWidth / 2, viewHeight / 2);
230 }
231
232 /**
233 * Gets the width of the view on the Screen;
234 * @return The width of the view component in screen pixel.
235 */
236 public double getViewWidth() {
237 return viewWidth;
238 }
239
240 /**
241 * Gets the height of the view on the Screen;
242 * @return The height of the view component in screen pixel.
243 */
244 public double getViewHeight() {
245 return viewHeight;
246 }
247
248 /**
249 * Gets the current projection used for the MapView.
250 * @return The projection.
251 */
252 public Projection getProjection() {
253 return projecting.getBaseProjection();
254 }
255
256 /**
257 * Creates an affine transform that is used to convert the east/north coordinates to view coordinates.
258 * @return The affine transform. It should not be changed.
259 * @since 10375
260 */
261 public AffineTransform getAffineTransform() {
262 return new AffineTransform(1.0 / scale, 0.0, 0.0, -1.0 / scale, -topLeft.east() / scale,
263 topLeft.north() / scale);
264 }
265
266 public Area getArea(Bounds bounds) {
267 Path2D area = new Path2D.Double();
268 bounds.visitEdge(getProjection(), latlon -> {
269 MapViewPoint point = getPointFor(latlon);
270 if (area.getCurrentPoint() == null) {
271 area.moveTo(point.getInViewX(), point.getInViewY());
272 } else {
273 area.lineTo(point.getInViewX(), point.getInViewY());
274 }
275 });
276 area.closePath();
277 return new Area(area);
278 }
279
280 /**
281 * Creates a new state that is the same as the current state except for that it is using a new center.
282 * @param newCenter The new center coordinate.
283 * @return The new state.
284 * @since 10375
285 */
286 public MapViewState usingCenter(EastNorth newCenter) {
287 return movedTo(getCenter(), newCenter);
288 }
289
290 /**
291 * @param mapViewPoint The reference point.
292 * @param newEastNorthThere The east/north coordinate that should be there.
293 * @return The new state.
294 * @since 10375
295 */
296 public MapViewState movedTo(MapViewPoint mapViewPoint, EastNorth newEastNorthThere) {
297 EastNorth delta = newEastNorthThere.subtract(mapViewPoint.getEastNorth());
298 if (delta.distanceSq(0, 0) < .1e-20) {
299 return this;
300 } else {
301 return new MapViewState(topLeft.add(delta), this);
302 }
303 }
304
305 /**
306 * Creates a new state that is the same as the current state except for that it is using a new scale.
307 * @param newScale The new scale to use.
308 * @return The new state.
309 * @since 10375
310 */
311 public MapViewState usingScale(double newScale) {
312 return new MapViewState(newScale, this);
313 }
314
315 /**
316 * Creates a new state that is the same as the current state except for that it is using the location of the given component.
317 * <p>
318 * The view is moved so that the center is the same as the old center.
319 * @param positon The new location to use.
320 * @return The new state.
321 * @since 10375
322 */
323 public MapViewState usingLocation(JComponent positon) {
324 EastNorth center = this.getCenter().getEastNorth();
325 return new MapViewState(positon, this).usingCenter(center);
326 }
327
328 /**
329 * Creates a state that uses the projection.
330 * @param projection The projection to use.
331 * @return The new state.
332 * @since 10486
333 */
334 public MapViewState usingProjection(Projection projection) {
335 if (projection.equals(this.projecting)) {
336 return this;
337 } else {
338 return new MapViewState(projection, this);
339 }
340 }
341
342 /**
343 * Create the default {@link MapViewState} object for the given map view. The screen position won't be set so that this method can be used
344 * before the view was added to the hirarchy.
345 * @param width The view width
346 * @param height The view height
347 * @return The state
348 * @since 10375
349 */
350 public static MapViewState createDefaultState(int width, int height) {
351 Projection projection = Main.getProjection();
352 double scale = projection.getDefaultZoomInPPD();
353 MapViewState state = new MapViewState(projection, width, height, scale, new EastNorth(0, 0));
354 EastNorth center = calculateDefaultCenter();
355 return state.movedTo(state.getCenter(), center);
356 }
357
358 private static EastNorth calculateDefaultCenter() {
359 Bounds b = DownloadDialog.getSavedDownloadBounds();
360 if (b == null) {
361 b = Main.getProjection().getWorldBoundsLatLon();
362 }
363 return Main.getProjection().latlon2eastNorth(b.getCenter());
364 }
365
366 /**
367 * A class representing a point in the map view. It allows to convert between the different coordinate systems.
368 * @author Michael Zangl
369 */
370 public abstract class MapViewPoint {
371
372 /**
373 * Get this point in view coordinates.
374 * @return The point in view coordinates.
375 */
376 public Point2D getInView() {
377 return new Point2D.Double(getInViewX(), getInViewY());
378 }
379
380 /**
381 * Get the x coordinate in view space without creating an intermediate object.
382 * @return The x coordinate
383 * @since 10827
384 */
385 public abstract double getInViewX();
386
387 /**
388 * Get the y coordinate in view space without creating an intermediate object.
389 * @return The y coordinate
390 * @since 10827
391 */
392 public abstract double getInViewY();
393
394 /**
395 * Convert this point to window coordinates.
396 * @return The point in window coordinates.
397 */
398 public Point2D getInWindow() {
399 return getUsingCorner(topLeftInWindow);
400 }
401
402 /**
403 * Convert this point to screen coordinates.
404 * @return The point in screen coordinates.
405 */
406 public Point2D getOnScreen() {
407 return getUsingCorner(topLeftOnScreen);
408 }
409
410 private Double getUsingCorner(Point corner) {
411 return new Point2D.Double(corner.getX() + getInViewX(), corner.getY() + getInViewY());
412 }
413
414 /**
415 * Gets the {@link EastNorth} coordinate of this point.
416 * @return The east/north coordinate.
417 */
418 public EastNorth getEastNorth() {
419 return new EastNorth(topLeft.east() + getInViewX() * scale, topLeft.north() - getInViewY() * scale);
420 }
421
422 /**
423 * Create a rectangle from this to the other point.
424 * @param other The other point. Needs to be of the same {@link MapViewState}
425 * @return A rectangle.
426 */
427 public MapViewRectangle rectTo(MapViewPoint other) {
428 return new MapViewRectangle(this, other);
429 }
430
431 /**
432 * Gets the current position in LatLon coordinates according to the current projection.
433 * @return The positon as LatLon.
434 * @see #getLatLonClamped()
435 */
436 public LatLon getLatLon() {
437 return projecting.getBaseProjection().eastNorth2latlon(getEastNorth());
438 }
439
440 /**
441 * Gets the latlon coordinate clamped to the current world area.
442 * @return The lat/lon coordinate
443 * @since 10805
444 */
445 public LatLon getLatLonClamped() {
446 return projecting.eastNorth2latlonClamped(getEastNorth());
447 }
448
449 /**
450 * Add the given offset to this point
451 * @param en The offset in east/north space.
452 * @return The new point
453 * @since 10651
454 */
455 public MapViewPoint add(EastNorth en) {
456 return new MapViewEastNorthPoint(getEastNorth().add(en));
457 }
458
459 /**
460 * Check if this point is inside the view bounds.
461 *
462 * This is the case iff <code>getOutsideRectangleFlags(getViewArea())</code> returns no flags
463 * @return true if it is.
464 * @since 10827
465 */
466 public boolean isInView() {
467 return inRange(getInViewX(), 0, getViewWidth()) && inRange(getInViewY(), 0, getViewHeight());
468 }
469
470 private boolean inRange(double val, int min, double max) {
471 return val >= min && val < max;
472 }
473
474 /**
475 * Gets the direction in which this point is outside of the given view rectangle.
476 * @param rect The rectangle to check agains.
477 * @return The direction in which it is outside of the view, as OUTSIDE_... flags.
478 * @since 10827
479 */
480 public int getOutsideRectangleFlags(MapViewRectangle rect) {
481 Rectangle2D bounds = rect.getInView();
482 int flags = 0;
483 if (getInViewX() < bounds.getMinX()) {
484 flags |= OUTSIDE_LEFT;
485 } else if (getInViewX() > bounds.getMaxX()) {
486 flags |= OUTSIDE_RIGHT;
487 }
488 if (getInViewY() < bounds.getMinY()) {
489 flags |= OUTSIDE_TOP;
490 } else if (getInViewY() > bounds.getMaxY()) {
491 flags |= OUTSIDE_BOTTOM;
492 }
493
494 return flags;
495 }
496
497 /**
498 * Gets the sum of the x/y view distances between the points. |x1 - x2| + |y1 - y2|
499 * @param p2 The other point
500 * @return The norm
501 * @since 10827
502 */
503 public double oneNormInView(MapViewPoint p2) {
504 return Math.abs(getInViewX() - p2.getInViewX()) + Math.abs(getInViewY() - p2.getInViewY());
505 }
506
507 /**
508 * Gets the squared distance between this point and an other point.
509 * @param p2 The other point
510 * @return The squared distance.
511 * @since 10827
512 */
513 public double distanceToInViewSq(MapViewPoint p2) {
514 double dx = getInViewX() - p2.getInViewX();
515 double dy = getInViewY() - p2.getInViewY();
516 return dx * dx + dy * dy;
517 }
518
519 /**
520 * Gets the distance between this point and an other point.
521 * @param p2 The other point
522 * @return The distance.
523 * @since 10827
524 */
525 public double distanceToInView(MapViewPoint p2) {
526 return Math.sqrt(distanceToInViewSq(p2));
527 }
528
529 /**
530 * Do a linear interpolation to the other point
531 * @param p1 The other point
532 * @param i The interpolation factor. 0 is at the current point, 1 at the other point.
533 * @return The new point
534 * @since 10874
535 */
536 public MapViewPoint interpolate(MapViewPoint p1, int i) {
537 return new MapViewViewPoint((1 - i) * getInViewX() + i * p1.getInViewX(), (1 - i) * getInViewY() + i * p1.getInViewY());
538 }
539 }
540
541 private class MapViewViewPoint extends MapViewPoint {
542 private final double x;
543 private final double y;
544
545 MapViewViewPoint(double x, double y) {
546 this.x = x;
547 this.y = y;
548 }
549
550 @Override
551 public double getInViewX() {
552 return x;
553 }
554
555 @Override
556 public double getInViewY() {
557 return y;
558 }
559
560 @Override
561 public String toString() {
562 return "MapViewViewPoint [x=" + x + ", y=" + y + ']';
563 }
564 }
565
566 private class MapViewEastNorthPoint extends MapViewPoint {
567
568 private final EastNorth eastNorth;
569
570 MapViewEastNorthPoint(EastNorth eastNorth) {
571 this.eastNorth = eastNorth;
572 }
573
574 @Override
575 public double getInViewX() {
576 return (eastNorth.east() - topLeft.east()) / scale;
577 }
578
579 @Override
580 public double getInViewY() {
581 return (topLeft.north() - eastNorth.north()) / scale;
582 }
583
584 @Override
585 public EastNorth getEastNorth() {
586 return eastNorth;
587 }
588
589 @Override
590 public String toString() {
591 return "MapViewEastNorthPoint [eastNorth=" + eastNorth + ']';
592 }
593 }
594
595 /**
596 * A rectangle on the MapView. It is rectangular in screen / EastNorth space.
597 * @author Michael Zangl
598 */
599 public class MapViewRectangle {
600 private final MapViewPoint p1;
601 private final MapViewPoint p2;
602
603 /**
604 * Create a new MapViewRectangle
605 * @param p1 The first point to use
606 * @param p2 The second point to use.
607 */
608 MapViewRectangle(MapViewPoint p1, MapViewPoint p2) {
609 this.p1 = p1;
610 this.p2 = p2;
611 }
612
613 /**
614 * Gets the projection bounds for this rectangle.
615 * @return The projection bounds.
616 */
617 public ProjectionBounds getProjectionBounds() {
618 ProjectionBounds b = new ProjectionBounds(p1.getEastNorth());
619 b.extend(p2.getEastNorth());
620 return b;
621 }
622
623 /**
624 * Gets a rough estimate of the bounds by assuming lat/lon are parallel to x/y.
625 * @return The bounds computed by converting the corners of this rectangle.
626 * @see #getLatLonBoundsBox()
627 */
628 public Bounds getCornerBounds() {
629 Bounds b = new Bounds(p1.getLatLon());
630 b.extend(p2.getLatLon());
631 return b;
632 }
633
634 /**
635 * Gets the real bounds that enclose this rectangle.
636 * This is computed respecting that the borders of this rectangle may not be a straignt line in latlon coordinates.
637 * @return The bounds.
638 * @since 10458
639 */
640 public Bounds getLatLonBoundsBox() {
641 // TODO @michael2402: Use hillclimb.
642 return projecting.getBaseProjection().getLatLonBoundsBox(getProjectionBounds());
643 }
644
645 /**
646 * Gets this rectangle on the screen.
647 * @return The rectangle.
648 * @since 10651
649 */
650 public Rectangle2D getInView() {
651 double x1 = p1.getInViewX();
652 double y1 = p1.getInViewY();
653 double x2 = p2.getInViewX();
654 double y2 = p2.getInViewY();
655 return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
656 }
657
658 /**
659 * Check if the rectangle intersects the map view area.
660 * @return <code>true</code> if it intersects.
661 * @since 10827
662 */
663 public boolean isInView() {
664 return getInView().intersects(getViewArea().getInView());
665 }
666 }
667
668}
Note: See TracBrowser for help on using the repository browser.