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

Last change on this file was 17581, checked in by simon04, 3 years ago

see #20613 - Avoid heap allocations in MapViewState.getOutsideRectangleFlags

4.74% in AbstractMapRendererPerformanceTestParent#testCity amount to Rectangle2D.Double from MapViewState

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