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
RevLine 
[10343]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
[10375]4import java.awt.Container;
[10343]5import java.awt.Point;
[10458]6import java.awt.Rectangle;
[10375]7import java.awt.geom.AffineTransform;
[10343]8import java.awt.geom.Point2D;
9import java.awt.geom.Point2D.Double;
[10651]10import java.awt.geom.Rectangle2D;
[10343]11
[10375]12import javax.swing.JComponent;
[10343]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;
[10805]19import org.openstreetmap.josm.data.projection.Projecting;
[10343]20import org.openstreetmap.josm.data.projection.Projection;
[10375]21import org.openstreetmap.josm.gui.download.DownloadDialog;
[10586]22import org.openstreetmap.josm.tools.bugreport.BugReport;
[10343]23
24/**
25 * This class represents a state of the {@link MapView}.
26 * @author Michael Zangl
27 * @since 10343
28 */
[10375]29public final class MapViewState {
[10343]30
[10805]31 private final Projecting projecting;
[10343]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
[10375]43 private final Point topLeftOnScreen;
44 private final Point topLeftInWindow;
[10343]45
46 /**
[10375]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.
[10343]53 */
[10805]54 private MapViewState(Projecting projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
55 this.projecting = projection;
[10375]56 this.scale = scale;
57 this.topLeft = topLeft;
[10343]58
[10375]59 this.viewWidth = viewWidth;
60 this.viewHeight = viewHeight;
61 topLeftInWindow = new Point(0, 0);
62 topLeftOnScreen = new Point(0, 0);
[10343]63 }
64
[10375]65 private MapViewState(EastNorth topLeft, MapViewState mapViewState) {
[10805]66 this.projecting = mapViewState.projecting;
[10375]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) {
[10805]77 this.projecting = mapViewState.projecting;
[10375]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) {
[10805]88 this.projecting = mapViewState.projecting;
[10375]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 }
[10586]102 try {
103 topLeftOnScreen = position.getLocationOnScreen();
104 } catch (RuntimeException e) {
[10596]105 throw BugReport.intercept(e).put("position", position).put("parent", position::getParent);
[10586]106 }
[10375]107 }
108
[10805]109 private MapViewState(Projecting projecting, MapViewState mapViewState) {
110 this.projecting = projecting;
[10486]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
[10343]120 /**
[10375]121 * The scale in east/north units per pixel.
122 * @return The scale.
123 */
124 public double getScale() {
125 return scale;
126 }
127
128 /**
[10343]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 /**
[10651]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 /**
[10343]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 /**
[10458]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 /**
[10343]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() {
[10805]204 return projecting.getBaseProjection();
[10343]205 }
206
207 /**
[10375]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.
[10399]210 * @since 10375
[10375]211 */
212 public AffineTransform getAffineTransform() {
[10398]213 return new AffineTransform(1.0 / scale, 0.0, 0.0, -1.0 / scale, -topLeft.east() / scale,
[10375]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.
[10399]221 * @since 10375
[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.
[10399]231 * @since 10375
[10375]232 */
233 public MapViewState movedTo(MapViewPoint mapViewPoint, EastNorth newEastNorthThere) {
234 EastNorth delta = newEastNorthThere.subtract(mapViewPoint.getEastNorth());
[10458]235 if (delta.distanceSq(0, 0) < .1e-20) {
[10375]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.
[10399]246 * @since 10375
[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.
[10399]258 * @since 10375
[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 /**
[10486]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) {
[10805]272 if (projection.equals(this.projecting)) {
[10486]273 return this;
274 } else {
275 return new MapViewState(projection, this);
276 }
277 }
278
279 /**
[10375]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
[10399]285 * @since 10375
[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 /**
[10343]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() {
[10375]326 return getUsingCorner(topLeftInWindow);
[10343]327 }
328
329 /**
330 * Convert this point to screen coordinates.
331 * @return The point in screen coordinates.
332 */
333 public Point2D getOnScreen() {
[10375]334 return getUsingCorner(topLeftOnScreen);
[10343]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.
[10805]361 * @see #getLatLonClamped()
[10343]362 */
363 public LatLon getLatLon() {
[10805]364 return projecting.getBaseProjection().eastNorth2latlon(getEastNorth());
[10343]365 }
[10651]366
367 /**
[10805]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 /**
[10651]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 }
[10343]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() {
[10462]408 return "MapViewViewPoint [x=" + x + ", y=" + y + ']';
[10343]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() {
[10462]437 return "MapViewEastNorthPoint [eastNorth=" + eastNorth + ']';
[10343]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.
[10458]472 * @see #getLatLonBoundsBox()
[10343]473 */
474 public Bounds getCornerBounds() {
475 Bounds b = new Bounds(p1.getLatLon());
476 b.extend(p2.getLatLon());
477 return b;
478 }
[10458]479
480 /**
[10462]481 * Gets the real bounds that enclose this rectangle.
[10458]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() {
[10805]487 // TODO @michael2402: Use hillclimb.
488 return projecting.getBaseProjection().getLatLonBoundsBox(getProjectionBounds());
[10458]489 }
[10651]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 }
[10343]503 }
[10375]504
[10343]505}
Note: See TracBrowser for help on using the repository browser.