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

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

fix #13287 - Projection updates to support multiple projections (patch by michael2402) - gsoc-core

  • Property svn:eol-style set to native
File size: 15.9 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.Rectangle;
7import java.awt.geom.AffineTransform;
8import java.awt.geom.Point2D;
9import java.awt.geom.Point2D.Double;
10import java.awt.geom.Rectangle2D;
11
12import javax.swing.JComponent;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.Bounds;
16import org.openstreetmap.josm.data.ProjectionBounds;
17import org.openstreetmap.josm.data.coor.EastNorth;
18import org.openstreetmap.josm.data.coor.LatLon;
19import org.openstreetmap.josm.data.projection.Projecting;
20import org.openstreetmap.josm.data.projection.Projection;
21import org.openstreetmap.josm.gui.download.DownloadDialog;
22import org.openstreetmap.josm.tools.bugreport.BugReport;
23
24/**
25 * This class represents a state of the {@link MapView}.
26 * @author Michael Zangl
27 * @since 10343
28 */
29public final class MapViewState {
30
31 private final Projecting projecting;
32
33 private final int viewWidth;
34 private final int viewHeight;
35
36 private final double scale;
37
38 /**
39 * Top left {@link EastNorth} coordinate of the view.
40 */
41 private final EastNorth topLeft;
42
43 private final Point topLeftOnScreen;
44 private final Point topLeftInWindow;
45
46 /**
47 * Create a new {@link MapViewState}
48 * @param projection The projection to use.
49 * @param viewWidth The view width
50 * @param viewHeight The view height
51 * @param scale The scale to use
52 * @param topLeft The top left corner in east/north space.
53 */
54 private MapViewState(Projecting projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
55 this.projecting = projection;
56 this.scale = scale;
57 this.topLeft = topLeft;
58
59 this.viewWidth = viewWidth;
60 this.viewHeight = viewHeight;
61 topLeftInWindow = new Point(0, 0);
62 topLeftOnScreen = new Point(0, 0);
63 }
64
65 private MapViewState(EastNorth topLeft, MapViewState mapViewState) {
66 this.projecting = mapViewState.projecting;
67 this.scale = mapViewState.scale;
68 this.topLeft = topLeft;
69
70 viewWidth = mapViewState.viewWidth;
71 viewHeight = mapViewState.viewHeight;
72 topLeftInWindow = mapViewState.topLeftInWindow;
73 topLeftOnScreen = mapViewState.topLeftOnScreen;
74 }
75
76 private MapViewState(double scale, MapViewState mapViewState) {
77 this.projecting = mapViewState.projecting;
78 this.scale = scale;
79 this.topLeft = mapViewState.topLeft;
80
81 viewWidth = mapViewState.viewWidth;
82 viewHeight = mapViewState.viewHeight;
83 topLeftInWindow = mapViewState.topLeftInWindow;
84 topLeftOnScreen = mapViewState.topLeftOnScreen;
85 }
86
87 private MapViewState(JComponent position, MapViewState mapViewState) {
88 this.projecting = mapViewState.projecting;
89 this.scale = mapViewState.scale;
90 this.topLeft = mapViewState.topLeft;
91
92 viewWidth = position.getWidth();
93 viewHeight = position.getHeight();
94 topLeftInWindow = new Point();
95 // better than using swing utils, since this allows us to use the mehtod if no screen is present.
96 Container component = position;
97 while (component != null) {
98 topLeftInWindow.x += component.getX();
99 topLeftInWindow.y += component.getY();
100 component = component.getParent();
101 }
102 try {
103 topLeftOnScreen = position.getLocationOnScreen();
104 } catch (RuntimeException e) {
105 throw BugReport.intercept(e).put("position", position).put("parent", position::getParent);
106 }
107 }
108
109 private MapViewState(Projecting projecting, MapViewState mapViewState) {
110 this.projecting = projecting;
111 this.scale = mapViewState.scale;
112 this.topLeft = mapViewState.topLeft;
113
114 viewWidth = mapViewState.viewWidth;
115 viewHeight = mapViewState.viewHeight;
116 topLeftInWindow = mapViewState.topLeftInWindow;
117 topLeftOnScreen = mapViewState.topLeftOnScreen;
118 }
119
120 /**
121 * The scale in east/north units per pixel.
122 * @return The scale.
123 */
124 public double getScale() {
125 return scale;
126 }
127
128 /**
129 * Gets the MapViewPoint representation for a position in view coordinates.
130 * @param x The x coordinate inside the view.
131 * @param y The y coordinate inside the view.
132 * @return The MapViewPoint.
133 */
134 public MapViewPoint getForView(double x, double y) {
135 return new MapViewViewPoint(x, y);
136 }
137
138 /**
139 * Gets the {@link MapViewPoint} for the given {@link EastNorth} coordinate.
140 * @param eastNorth the position.
141 * @return The point for that position.
142 */
143 public MapViewPoint getPointFor(EastNorth eastNorth) {
144 return new MapViewEastNorthPoint(eastNorth);
145 }
146
147 /**
148 * Gets the {@link MapViewPoint} for the given {@link LatLon} coordinate.
149 * @param latlon the position
150 * @return The point for that position.
151 * @since 10651
152 */
153 public MapViewPoint getPointFor(LatLon latlon) {
154 return getPointFor(getProjection().latlon2eastNorth(latlon));
155 }
156
157 /**
158 * Gets a rectangle representing the whole view area.
159 * @return The rectangle.
160 */
161 public MapViewRectangle getViewArea() {
162 return getForView(0, 0).rectTo(getForView(viewWidth, viewHeight));
163 }
164
165 /**
166 * Gets a rectangle of the view as map view area.
167 * @param rectangle The rectangle to get.
168 * @return The view area.
169 * @since 10458
170 */
171 public MapViewRectangle getViewArea(Rectangle rectangle) {
172 return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY()));
173 }
174
175 /**
176 * Gets the center of the view.
177 * @return The center position.
178 */
179 public MapViewPoint getCenter() {
180 return getForView(viewWidth / 2.0, viewHeight / 2.0);
181 }
182
183 /**
184 * Gets the width of the view on the Screen;
185 * @return The width of the view component in screen pixel.
186 */
187 public double getViewWidth() {
188 return viewWidth;
189 }
190
191 /**
192 * Gets the height of the view on the Screen;
193 * @return The height of the view component in screen pixel.
194 */
195 public double getViewHeight() {
196 return viewHeight;
197 }
198
199 /**
200 * Gets the current projection used for the MapView.
201 * @return The projection.
202 */
203 public Projection getProjection() {
204 return projecting.getBaseProjection();
205 }
206
207 /**
208 * Creates an affine transform that is used to convert the east/north coordinates to view coordinates.
209 * @return The affine transform. It should not be changed.
210 * @since 10375
211 */
212 public AffineTransform getAffineTransform() {
213 return new AffineTransform(1.0 / scale, 0.0, 0.0, -1.0 / scale, -topLeft.east() / scale,
214 topLeft.north() / scale);
215 }
216
217 /**
218 * Creates a new state that is the same as the current state except for that it is using a new center.
219 * @param newCenter The new center coordinate.
220 * @return The new state.
221 * @since 10375
222 */
223 public MapViewState usingCenter(EastNorth newCenter) {
224 return movedTo(getCenter(), newCenter);
225 }
226
227 /**
228 * @param mapViewPoint The reference point.
229 * @param newEastNorthThere The east/north coordinate that should be there.
230 * @return The new state.
231 * @since 10375
232 */
233 public MapViewState movedTo(MapViewPoint mapViewPoint, EastNorth newEastNorthThere) {
234 EastNorth delta = newEastNorthThere.subtract(mapViewPoint.getEastNorth());
235 if (delta.distanceSq(0, 0) < .1e-20) {
236 return this;
237 } else {
238 return new MapViewState(topLeft.add(delta), this);
239 }
240 }
241
242 /**
243 * Creates a new state that is the same as the current state except for that it is using a new scale.
244 * @param newScale The new scale to use.
245 * @return The new state.
246 * @since 10375
247 */
248 public MapViewState usingScale(double newScale) {
249 return new MapViewState(newScale, this);
250 }
251
252 /**
253 * Creates a new state that is the same as the current state except for that it is using the location of the given component.
254 * <p>
255 * The view is moved so that the center is the same as the old center.
256 * @param positon The new location to use.
257 * @return The new state.
258 * @since 10375
259 */
260 public MapViewState usingLocation(JComponent positon) {
261 EastNorth center = this.getCenter().getEastNorth();
262 return new MapViewState(positon, this).usingCenter(center);
263 }
264
265 /**
266 * Creates a state that uses the projection.
267 * @param projection The projection to use.
268 * @return The new state.
269 * @since 10486
270 */
271 public MapViewState usingProjection(Projection projection) {
272 if (projection.equals(this.projecting)) {
273 return this;
274 } else {
275 return new MapViewState(projection, this);
276 }
277 }
278
279 /**
280 * 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
281 * before the view was added to the hirarchy.
282 * @param width The view width
283 * @param height The view height
284 * @return The state
285 * @since 10375
286 */
287 public static MapViewState createDefaultState(int width, int height) {
288 Projection projection = Main.getProjection();
289 double scale = projection.getDefaultZoomInPPD();
290 MapViewState state = new MapViewState(projection, width, height, scale, new EastNorth(0, 0));
291 EastNorth center = calculateDefaultCenter();
292 return state.movedTo(state.getCenter(), center);
293 }
294
295 private static EastNorth calculateDefaultCenter() {
296 Bounds b = DownloadDialog.getSavedDownloadBounds();
297 if (b == null) {
298 b = Main.getProjection().getWorldBoundsLatLon();
299 }
300 return Main.getProjection().latlon2eastNorth(b.getCenter());
301 }
302
303 /**
304 * A class representing a point in the map view. It allows to convert between the different coordinate systems.
305 * @author Michael Zangl
306 */
307 public abstract class MapViewPoint {
308
309 /**
310 * Get this point in view coordinates.
311 * @return The point in view coordinates.
312 */
313 public Point2D getInView() {
314 return new Point2D.Double(getInViewX(), getInViewY());
315 }
316
317 protected abstract double getInViewX();
318
319 protected abstract double getInViewY();
320
321 /**
322 * Convert this point to window coordinates.
323 * @return The point in window coordinates.
324 */
325 public Point2D getInWindow() {
326 return getUsingCorner(topLeftInWindow);
327 }
328
329 /**
330 * Convert this point to screen coordinates.
331 * @return The point in screen coordinates.
332 */
333 public Point2D getOnScreen() {
334 return getUsingCorner(topLeftOnScreen);
335 }
336
337 private Double getUsingCorner(Point corner) {
338 return new Point2D.Double(corner.getX() + getInViewX(), corner.getY() + getInViewY());
339 }
340
341 /**
342 * Gets the {@link EastNorth} coordinate of this point.
343 * @return The east/north coordinate.
344 */
345 public EastNorth getEastNorth() {
346 return new EastNorth(topLeft.east() + getInViewX() * scale, topLeft.north() - getInViewY() * scale);
347 }
348
349 /**
350 * Create a rectangle from this to the other point.
351 * @param other The other point. Needs to be of the same {@link MapViewState}
352 * @return A rectangle.
353 */
354 public MapViewRectangle rectTo(MapViewPoint other) {
355 return new MapViewRectangle(this, other);
356 }
357
358 /**
359 * Gets the current position in LatLon coordinates according to the current projection.
360 * @return The positon as LatLon.
361 * @see #getLatLonClamped()
362 */
363 public LatLon getLatLon() {
364 return projecting.getBaseProjection().eastNorth2latlon(getEastNorth());
365 }
366
367 /**
368 * Gets the latlon coordinate clamped to the current world area.
369 * @return The lat/lon coordinate
370 * @since 10805
371 */
372 public LatLon getLatLonClamped() {
373 return projecting.eastNorth2latlonClamped(getEastNorth());
374 }
375
376 /**
377 * Add the given offset to this point
378 * @param en The offset in east/north space.
379 * @return The new point
380 * @since 10651
381 */
382 public MapViewPoint add(EastNorth en) {
383 return new MapViewEastNorthPoint(getEastNorth().add(en));
384 }
385 }
386
387 private class MapViewViewPoint extends MapViewPoint {
388 private final double x;
389 private final double y;
390
391 MapViewViewPoint(double x, double y) {
392 this.x = x;
393 this.y = y;
394 }
395
396 @Override
397 protected double getInViewX() {
398 return x;
399 }
400
401 @Override
402 protected double getInViewY() {
403 return y;
404 }
405
406 @Override
407 public String toString() {
408 return "MapViewViewPoint [x=" + x + ", y=" + y + ']';
409 }
410 }
411
412 private class MapViewEastNorthPoint extends MapViewPoint {
413
414 private final EastNorth eastNorth;
415
416 MapViewEastNorthPoint(EastNorth eastNorth) {
417 this.eastNorth = eastNorth;
418 }
419
420 @Override
421 protected double getInViewX() {
422 return (eastNorth.east() - topLeft.east()) / scale;
423 }
424
425 @Override
426 protected double getInViewY() {
427 return (topLeft.north() - eastNorth.north()) / scale;
428 }
429
430 @Override
431 public EastNorth getEastNorth() {
432 return eastNorth;
433 }
434
435 @Override
436 public String toString() {
437 return "MapViewEastNorthPoint [eastNorth=" + eastNorth + ']';
438 }
439 }
440
441 /**
442 * A rectangle on the MapView. It is rectangular in screen / EastNorth space.
443 * @author Michael Zangl
444 */
445 public class MapViewRectangle {
446 private final MapViewPoint p1;
447 private final MapViewPoint p2;
448
449 /**
450 * Create a new MapViewRectangle
451 * @param p1 The first point to use
452 * @param p2 The second point to use.
453 */
454 MapViewRectangle(MapViewPoint p1, MapViewPoint p2) {
455 this.p1 = p1;
456 this.p2 = p2;
457 }
458
459 /**
460 * Gets the projection bounds for this rectangle.
461 * @return The projection bounds.
462 */
463 public ProjectionBounds getProjectionBounds() {
464 ProjectionBounds b = new ProjectionBounds(p1.getEastNorth());
465 b.extend(p2.getEastNorth());
466 return b;
467 }
468
469 /**
470 * Gets a rough estimate of the bounds by assuming lat/lon are parallel to x/y.
471 * @return The bounds computed by converting the corners of this rectangle.
472 * @see #getLatLonBoundsBox()
473 */
474 public Bounds getCornerBounds() {
475 Bounds b = new Bounds(p1.getLatLon());
476 b.extend(p2.getLatLon());
477 return b;
478 }
479
480 /**
481 * Gets the real bounds that enclose this rectangle.
482 * This is computed respecting that the borders of this rectangle may not be a straignt line in latlon coordinates.
483 * @return The bounds.
484 * @since 10458
485 */
486 public Bounds getLatLonBoundsBox() {
487 // TODO @michael2402: Use hillclimb.
488 return projecting.getBaseProjection().getLatLonBoundsBox(getProjectionBounds());
489 }
490
491 /**
492 * Gets this rectangle on the screen.
493 * @return The rectangle.
494 * @since 10651
495 */
496 public Rectangle2D getInView() {
497 double x1 = p1.getInViewX();
498 double y1 = p1.getInViewY();
499 double x2 = p2.getInViewX();
500 double y2 = p2.getInViewY();
501 return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
502 }
503 }
504
505}
Note: See TracBrowser for help on using the repository browser.