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

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

fix #13210 - Start extracting coordinate conversion out of tile source (patch by michael2402, modified) - gsoc-core

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