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

Last change on this file since 13633 was 13387, checked in by Don-vip, 6 years ago

see #15880 - nicer exception

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