source: josm/trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java@ 6995

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

fix #9906 - fix reliance on default encoding

  • Property svn:eol-style set to native
File size: 54.6 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import java.awt.Cursor;
5import java.awt.Graphics;
6import java.awt.Point;
7import java.awt.Polygon;
8import java.awt.Rectangle;
9import java.awt.geom.AffineTransform;
10import java.awt.geom.Point2D;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Date;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Map;
19import java.util.Map.Entry;
20import java.util.Set;
21import java.util.Stack;
22import java.util.TreeMap;
23import java.util.concurrent.CopyOnWriteArrayList;
24import java.util.zip.CRC32;
25
26import javax.swing.JComponent;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.data.Bounds;
30import org.openstreetmap.josm.data.ProjectionBounds;
31import org.openstreetmap.josm.data.SystemOfMeasurement;
32import org.openstreetmap.josm.data.coor.CachedLatLon;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.coor.LatLon;
35import org.openstreetmap.josm.data.osm.BBox;
36import org.openstreetmap.josm.data.osm.DataSet;
37import org.openstreetmap.josm.data.osm.Node;
38import org.openstreetmap.josm.data.osm.OsmPrimitive;
39import org.openstreetmap.josm.data.osm.Relation;
40import org.openstreetmap.josm.data.osm.Way;
41import org.openstreetmap.josm.data.osm.WaySegment;
42import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
43import org.openstreetmap.josm.data.preferences.IntegerProperty;
44import org.openstreetmap.josm.data.projection.Projection;
45import org.openstreetmap.josm.data.projection.Projections;
46import org.openstreetmap.josm.gui.download.DownloadDialog;
47import org.openstreetmap.josm.gui.help.Helpful;
48import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
49import org.openstreetmap.josm.tools.Predicate;
50import org.openstreetmap.josm.tools.Utils;
51
52/**
53 * A component that can be navigated by a {@link MapMover}. Used as map view and for the
54 * zoomer in the download dialog.
55 *
56 * @author imi
57 * @since 41
58 */
59public class NavigatableComponent extends JComponent implements Helpful {
60
61 /**
62 * Interface to notify listeners of the change of the zoom area.
63 */
64 public interface ZoomChangeListener {
65 /**
66 * Method called when the zoom area has changed.
67 */
68 void zoomChanged();
69 }
70
71 /**
72 * Interface to notify listeners of the change of the system of measurement.
73 * @since 6056
74 */
75 public interface SoMChangeListener {
76 /**
77 * The current SoM has changed.
78 * @param oldSoM The old system of measurement
79 * @param newSoM The new (current) system of measurement
80 */
81 void systemOfMeasurementChanged(String oldSoM, String newSoM);
82 }
83
84 public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
85
86 public static final String PROPNAME_CENTER = "center";
87 public static final String PROPNAME_SCALE = "scale";
88
89 /**
90 * the zoom listeners
91 */
92 private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
93
94 /**
95 * Removes a zoom change listener
96 *
97 * @param listener the listener. Ignored if null or already absent
98 */
99 public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
100 zoomChangeListeners.remove(listener);
101 }
102
103 /**
104 * Adds a zoom change listener
105 *
106 * @param listener the listener. Ignored if null or already registered.
107 */
108 public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
109 if (listener != null) {
110 zoomChangeListeners.addIfAbsent(listener);
111 }
112 }
113
114 protected static void fireZoomChanged() {
115 for (ZoomChangeListener l : zoomChangeListeners) {
116 l.zoomChanged();
117 }
118 }
119
120 private static final CopyOnWriteArrayList<SoMChangeListener> somChangeListeners = new CopyOnWriteArrayList<SoMChangeListener>();
121
122 /**
123 * Removes a SoM change listener
124 *
125 * @param listener the listener. Ignored if null or already absent
126 * @since 6056
127 */
128 public static void removeSoMChangeListener(NavigatableComponent.SoMChangeListener listener) {
129 somChangeListeners.remove(listener);
130 }
131
132 /**
133 * Adds a SoM change listener
134 *
135 * @param listener the listener. Ignored if null or already registered.
136 * @since 6056
137 */
138 public static void addSoMChangeListener(NavigatableComponent.SoMChangeListener listener) {
139 if (listener != null) {
140 somChangeListeners.addIfAbsent(listener);
141 }
142 }
143
144 protected static void fireSoMChanged(String oldSoM, String newSoM) {
145 for (SoMChangeListener l : somChangeListeners) {
146 l.systemOfMeasurementChanged(oldSoM, newSoM);
147 }
148 }
149
150 /**
151 * The scale factor in x or y-units per pixel. This means, if scale = 10,
152 * every physical pixel on screen are 10 x or 10 y units in the
153 * northing/easting space of the projection.
154 */
155 private double scale = Main.getProjection().getDefaultZoomInPPD();
156
157 /**
158 * Center n/e coordinate of the desired screen center.
159 */
160 protected EastNorth center = calculateDefaultCenter();
161
162 private final Object paintRequestLock = new Object();
163 private Rectangle paintRect = null;
164 private Polygon paintPoly = null;
165
166 /**
167 * Constructs a new {@code NavigatableComponent}.
168 */
169 public NavigatableComponent() {
170 setLayout(null);
171 }
172
173 protected DataSet getCurrentDataSet() {
174 return Main.main.getCurrentDataSet();
175 }
176
177 private EastNorth calculateDefaultCenter() {
178 Bounds b = DownloadDialog.getSavedDownloadBounds();
179 if (b == null) {
180 b = Main.getProjection().getWorldBoundsLatLon();
181 }
182 return Main.getProjection().latlon2eastNorth(b.getCenter());
183 }
184
185 /**
186 * Returns the text describing the given distance in the current system of measurement.
187 * @param dist The distance in metres.
188 * @return the text describing the given distance in the current system of measurement.
189 * @since 3406
190 */
191 public static String getDistText(double dist) {
192 return getSystemOfMeasurement().getDistText(dist);
193 }
194
195 /**
196 * Returns the text describing the given area in the current system of measurement.
197 * @param area The distance in square metres.
198 * @return the text describing the given area in the current system of measurement.
199 * @since 5560
200 */
201 public static String getAreaText(double area) {
202 return getSystemOfMeasurement().getAreaText(area);
203 }
204
205 public String getDist100PixelText() {
206 return getDistText(getDist100Pixel());
207 }
208
209 public double getDist100Pixel() {
210 int w = getWidth()/2;
211 int h = getHeight()/2;
212 LatLon ll1 = getLatLon(w-50,h);
213 LatLon ll2 = getLatLon(w+50,h);
214 return ll1.greatCircleDistance(ll2);
215 }
216
217 /**
218 * @return Returns the center point. A copy is returned, so users cannot
219 * change the center by accessing the return value. Use zoomTo instead.
220 */
221 public EastNorth getCenter() {
222 return center;
223 }
224
225 public double getScale() {
226 return scale;
227 }
228
229 /**
230 * @param x X-Pixelposition to get coordinate from
231 * @param y Y-Pixelposition to get coordinate from
232 *
233 * @return Geographic coordinates from a specific pixel coordination on the screen.
234 */
235 public EastNorth getEastNorth(int x, int y) {
236 return new EastNorth(
237 center.east() + (x - getWidth()/2.0)*scale,
238 center.north() - (y - getHeight()/2.0)*scale);
239 }
240
241 public ProjectionBounds getProjectionBounds() {
242 return new ProjectionBounds(
243 new EastNorth(
244 center.east() - getWidth()/2.0*scale,
245 center.north() - getHeight()/2.0*scale),
246 new EastNorth(
247 center.east() + getWidth()/2.0*scale,
248 center.north() + getHeight()/2.0*scale));
249 }
250
251 /* FIXME: replace with better method - used by MapSlider */
252 public ProjectionBounds getMaxProjectionBounds() {
253 Bounds b = getProjection().getWorldBoundsLatLon();
254 return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
255 getProjection().latlon2eastNorth(b.getMax()));
256 }
257
258 /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
259 public Bounds getRealBounds() {
260 return new Bounds(
261 getProjection().eastNorth2latlon(new EastNorth(
262 center.east() - getWidth()/2.0*scale,
263 center.north() - getHeight()/2.0*scale)),
264 getProjection().eastNorth2latlon(new EastNorth(
265 center.east() + getWidth()/2.0*scale,
266 center.north() + getHeight()/2.0*scale)));
267 }
268
269 /**
270 * @param x X-Pixelposition to get coordinate from
271 * @param y Y-Pixelposition to get coordinate from
272 *
273 * @return Geographic unprojected coordinates from a specific pixel coordination
274 * on the screen.
275 */
276 public LatLon getLatLon(int x, int y) {
277 return getProjection().eastNorth2latlon(getEastNorth(x, y));
278 }
279
280 public LatLon getLatLon(double x, double y) {
281 return getLatLon((int)x, (int)y);
282 }
283
284 /**
285 * @param r
286 * @return Minimum bounds that will cover rectangle
287 */
288 public Bounds getLatLonBounds(Rectangle r) {
289 // TODO Maybe this should be (optional) method of Projection implementation
290 EastNorth p1 = getEastNorth(r.x, r.y);
291 EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
292
293 Bounds result = new Bounds(Main.getProjection().eastNorth2latlon(p1));
294
295 double eastMin = Math.min(p1.east(), p2.east());
296 double eastMax = Math.max(p1.east(), p2.east());
297 double northMin = Math.min(p1.north(), p2.north());
298 double northMax = Math.max(p1.north(), p2.north());
299 double deltaEast = (eastMax - eastMin) / 10;
300 double deltaNorth = (northMax - northMin) / 10;
301
302 for (int i=0; i < 10; i++) {
303 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
304 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
305 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin, northMin + i * deltaNorth)));
306 result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMax, northMin + i * deltaNorth)));
307 }
308
309 return result;
310 }
311
312 public AffineTransform getAffineTransform() {
313 return new AffineTransform(
314 1.0/scale, 0.0, 0.0, -1.0/scale, getWidth()/2.0 - center.east()/scale, getHeight()/2.0 + center.north()/scale);
315 }
316
317 /**
318 * Return the point on the screen where this Coordinate would be.
319 * @param p The point, where this geopoint would be drawn.
320 * @return The point on screen where "point" would be drawn, relative
321 * to the own top/left.
322 */
323 public Point2D getPoint2D(EastNorth p) {
324 if (null == p)
325 return new Point();
326 double x = (p.east()-center.east())/scale + getWidth()/2;
327 double y = (center.north()-p.north())/scale + getHeight()/2;
328 return new Point2D.Double(x, y);
329 }
330
331 public Point2D getPoint2D(LatLon latlon) {
332 if (latlon == null)
333 return new Point();
334 else if (latlon instanceof CachedLatLon)
335 return getPoint2D(((CachedLatLon)latlon).getEastNorth());
336 else
337 return getPoint2D(getProjection().latlon2eastNorth(latlon));
338 }
339
340 public Point2D getPoint2D(Node n) {
341 return getPoint2D(n.getEastNorth());
342 }
343
344 // looses precision, may overflow (depends on p and current scale)
345 //@Deprecated
346 public Point getPoint(EastNorth p) {
347 Point2D d = getPoint2D(p);
348 return new Point((int) d.getX(), (int) d.getY());
349 }
350
351 // looses precision, may overflow (depends on p and current scale)
352 //@Deprecated
353 public Point getPoint(LatLon latlon) {
354 Point2D d = getPoint2D(latlon);
355 return new Point((int) d.getX(), (int) d.getY());
356 }
357
358 // looses precision, may overflow (depends on p and current scale)
359 //@Deprecated
360 public Point getPoint(Node n) {
361 Point2D d = getPoint2D(n);
362 return new Point((int) d.getX(), (int) d.getY());
363 }
364
365 /**
366 * Zoom to the given coordinate.
367 * @param newCenter The center x-value (easting) to zoom to.
368 * @param newScale The scale to use.
369 */
370 public void zoomTo(EastNorth newCenter, double newScale) {
371 Bounds b = getProjection().getWorldBoundsLatLon();
372 LatLon cl = Projections.inverseProject(newCenter);
373 boolean changed = false;
374 double lat = cl.lat();
375 double lon = cl.lon();
376 if(lat < b.getMinLat()) {changed = true; lat = b.getMinLat(); }
377 else if(lat > b.getMaxLat()) {changed = true; lat = b.getMaxLat(); }
378 if(lon < b.getMinLon()) {changed = true; lon = b.getMinLon(); }
379 else if(lon > b.getMaxLon()) {changed = true; lon = b.getMaxLon(); }
380 if(changed) {
381 newCenter = Projections.project(new LatLon(lat,lon));
382 }
383 int width = getWidth()/2;
384 int height = getHeight()/2;
385 LatLon l1 = new LatLon(b.getMinLat(), lon);
386 LatLon l2 = new LatLon(b.getMaxLat(), lon);
387 EastNorth e1 = getProjection().latlon2eastNorth(l1);
388 EastNorth e2 = getProjection().latlon2eastNorth(l2);
389 double d = e2.north() - e1.north();
390 if (height > 0 && d < height*newScale) {
391 double newScaleH = d/height;
392 e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMinLon()));
393 e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMaxLon()));
394 d = e2.east() - e1.east();
395 if (width > 0 && d < width*newScale) {
396 newScale = Math.max(newScaleH, d/width);
397 }
398 } else if (height > 0) {
399 d = d/(l1.greatCircleDistance(l2)*height*10);
400 if (newScale < d) {
401 newScale = d;
402 }
403 }
404
405 if (!newCenter.equals(center) || (scale != newScale)) {
406 pushZoomUndo(center, scale);
407 zoomNoUndoTo(newCenter, newScale);
408 }
409 }
410
411 /**
412 * Zoom to the given coordinate without adding to the zoom undo buffer.
413 * @param newCenter The center x-value (easting) to zoom to.
414 * @param newScale The scale to use.
415 */
416 private void zoomNoUndoTo(EastNorth newCenter, double newScale) {
417 if (!newCenter.equals(center)) {
418 EastNorth oldCenter = center;
419 center = newCenter;
420 firePropertyChange(PROPNAME_CENTER, oldCenter, newCenter);
421 }
422 if (scale != newScale) {
423 double oldScale = scale;
424 scale = newScale;
425 firePropertyChange(PROPNAME_SCALE, oldScale, newScale);
426 }
427
428 repaint();
429 fireZoomChanged();
430 }
431
432 public void zoomTo(EastNorth newCenter) {
433 zoomTo(newCenter, scale);
434 }
435
436 public void zoomTo(LatLon newCenter) {
437 zoomTo(Projections.project(newCenter));
438 }
439
440 public void smoothScrollTo(LatLon newCenter) {
441 smoothScrollTo(Projections.project(newCenter));
442 }
443
444 /**
445 * Create a thread that moves the viewport to the given center in an
446 * animated fashion.
447 */
448 public void smoothScrollTo(EastNorth newCenter) {
449 // FIXME make these configurable.
450 final int fps = 20; // animation frames per second
451 final int speed = 1500; // milliseconds for full-screen-width pan
452 if (!newCenter.equals(center)) {
453 final EastNorth oldCenter = center;
454 final double distance = newCenter.distance(oldCenter) / scale;
455 final double milliseconds = distance / getWidth() * speed;
456 final double frames = milliseconds * fps / 1000;
457 final EastNorth finalNewCenter = newCenter;
458
459 new Thread(){
460 @Override
461 public void run() {
462 for (int i=0; i<frames; i++) {
463 // FIXME - not use zoom history here
464 zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
465 try {
466 Thread.sleep(1000 / fps);
467 } catch (InterruptedException ex) {
468 Main.warn("InterruptedException in "+NavigatableComponent.class.getSimpleName()+" during smooth scrolling");
469 }
470 }
471 }
472 }.start();
473 }
474 }
475
476 public void zoomToFactor(double x, double y, double factor) {
477 double newScale = scale*factor;
478 // New center position so that point under the mouse pointer stays the same place as it was before zooming
479 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
480 zoomTo(new EastNorth(
481 center.east() - (x - getWidth()/2.0) * (newScale - scale),
482 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
483 newScale);
484 }
485
486 public void zoomToFactor(EastNorth newCenter, double factor) {
487 zoomTo(newCenter, scale*factor);
488 }
489
490 public void zoomToFactor(double factor) {
491 zoomTo(center, scale*factor);
492 }
493
494 public void zoomTo(ProjectionBounds box) {
495 // -20 to leave some border
496 int w = getWidth()-20;
497 if (w < 20) {
498 w = 20;
499 }
500 int h = getHeight()-20;
501 if (h < 20) {
502 h = 20;
503 }
504
505 double scaleX = (box.maxEast-box.minEast)/w;
506 double scaleY = (box.maxNorth-box.minNorth)/h;
507 double newScale = Math.max(scaleX, scaleY);
508
509 zoomTo(box.getCenter(), newScale);
510 }
511
512 public void zoomTo(Bounds box) {
513 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
514 getProjection().latlon2eastNorth(box.getMax())));
515 }
516
517 private class ZoomData {
518 final LatLon center;
519 final double scale;
520
521 public ZoomData(EastNorth center, double scale) {
522 this.center = Projections.inverseProject(center);
523 this.scale = scale;
524 }
525
526 public EastNorth getCenterEastNorth() {
527 return getProjection().latlon2eastNorth(center);
528 }
529
530 public double getScale() {
531 return scale;
532 }
533 }
534
535 private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>();
536 private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>();
537 private Date zoomTimestamp = new Date();
538
539 private void pushZoomUndo(EastNorth center, double scale) {
540 Date now = new Date();
541 if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
542 zoomUndoBuffer.push(new ZoomData(center, scale));
543 if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
544 zoomUndoBuffer.remove(0);
545 }
546 zoomRedoBuffer.clear();
547 }
548 zoomTimestamp = now;
549 }
550
551 public void zoomPrevious() {
552 if (!zoomUndoBuffer.isEmpty()) {
553 ZoomData zoom = zoomUndoBuffer.pop();
554 zoomRedoBuffer.push(new ZoomData(center, scale));
555 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
556 }
557 }
558
559 public void zoomNext() {
560 if (!zoomRedoBuffer.isEmpty()) {
561 ZoomData zoom = zoomRedoBuffer.pop();
562 zoomUndoBuffer.push(new ZoomData(center, scale));
563 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
564 }
565 }
566
567 public boolean hasZoomUndoEntries() {
568 return !zoomUndoBuffer.isEmpty();
569 }
570
571 public boolean hasZoomRedoEntries() {
572 return !zoomRedoBuffer.isEmpty();
573 }
574
575 private BBox getBBox(Point p, int snapDistance) {
576 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
577 getLatLon(p.x + snapDistance, p.y + snapDistance));
578 }
579
580 /**
581 * The *result* does not depend on the current map selection state,
582 * neither does the result *order*.
583 * It solely depends on the distance to point p.
584 *
585 * @return a sorted map with the keys representing the distance of
586 * their associated nodes to point p.
587 */
588 private Map<Double, List<Node>> getNearestNodesImpl(Point p,
589 Predicate<OsmPrimitive> predicate) {
590 TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
591 DataSet ds = getCurrentDataSet();
592
593 if (ds != null) {
594 double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
595 snapDistanceSq *= snapDistanceSq;
596
597 for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
598 if (predicate.evaluate(n)
599 && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
600 {
601 List<Node> nlist;
602 if (nearestMap.containsKey(dist)) {
603 nlist = nearestMap.get(dist);
604 } else {
605 nlist = new LinkedList<Node>();
606 nearestMap.put(dist, nlist);
607 }
608 nlist.add(n);
609 }
610 }
611 }
612
613 return nearestMap;
614 }
615
616 /**
617 * The *result* does not depend on the current map selection state,
618 * neither does the result *order*.
619 * It solely depends on the distance to point p.
620 *
621 * @return All nodes nearest to point p that are in a belt from
622 * dist(nearest) to dist(nearest)+4px around p and
623 * that are not in ignore.
624 *
625 * @param p the point for which to search the nearest segment.
626 * @param ignore a collection of nodes which are not to be returned.
627 * @param predicate the returned objects have to fulfill certain properties.
628 */
629 public final List<Node> getNearestNodes(Point p,
630 Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
631 List<Node> nearestList = Collections.emptyList();
632
633 if (ignore == null) {
634 ignore = Collections.emptySet();
635 }
636
637 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
638 if (!nlists.isEmpty()) {
639 Double minDistSq = null;
640 for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
641 Double distSq = entry.getKey();
642 List<Node> nlist = entry.getValue();
643
644 // filter nodes to be ignored before determining minDistSq..
645 nlist.removeAll(ignore);
646 if (minDistSq == null) {
647 if (!nlist.isEmpty()) {
648 minDistSq = distSq;
649 nearestList = new ArrayList<Node>();
650 nearestList.addAll(nlist);
651 }
652 } else {
653 if (distSq-minDistSq < (4)*(4)) {
654 nearestList.addAll(nlist);
655 }
656 }
657 }
658 }
659
660 return nearestList;
661 }
662
663 /**
664 * The *result* does not depend on the current map selection state,
665 * neither does the result *order*.
666 * It solely depends on the distance to point p.
667 *
668 * @return All nodes nearest to point p that are in a belt from
669 * dist(nearest) to dist(nearest)+4px around p.
670 * @see #getNearestNodes(Point, Collection, Predicate)
671 *
672 * @param p the point for which to search the nearest segment.
673 * @param predicate the returned objects have to fulfill certain properties.
674 */
675 public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
676 return getNearestNodes(p, null, predicate);
677 }
678
679 /**
680 * The *result* depends on the current map selection state IF use_selected is true.
681 *
682 * If more than one node within node.snap-distance pixels is found,
683 * the nearest node selected is returned IF use_selected is true.
684 *
685 * Else the nearest new/id=0 node within about the same distance
686 * as the true nearest node is returned.
687 *
688 * If no such node is found either, the true nearest
689 * node to p is returned.
690 *
691 * Finally, if a node is not found at all, null is returned.
692 *
693 * @return A node within snap-distance to point p,
694 * that is chosen by the algorithm described.
695 *
696 * @param p the screen point
697 * @param predicate this parameter imposes a condition on the returned object, e.g.
698 * give the nearest node that is tagged.
699 */
700 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
701 return getNearestNode(p, predicate, useSelected, null);
702 }
703
704 /**
705 * The *result* depends on the current map selection state IF use_selected is true
706 *
707 * If more than one node within node.snap-distance pixels is found,
708 * the nearest node selected is returned IF use_selected is true.
709 *
710 * If there are no selected nodes near that point, the node that is related to some of the preferredRefs
711 *
712 * Else the nearest new/id=0 node within about the same distance
713 * as the true nearest node is returned.
714 *
715 * If no such node is found either, the true nearest
716 * node to p is returned.
717 *
718 * Finally, if a node is not found at all, null is returned.
719 * @since 6065
720 * @return A node within snap-distance to point p,
721 * that is chosen by the algorithm described.
722 *
723 * @param p the screen point
724 * @param predicate this parameter imposes a condition on the returned object, e.g.
725 * give the nearest node that is tagged.
726 * @param preferredRefs primitives, whose nodes we prefer
727 */
728 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate,
729 boolean useSelected, Collection<OsmPrimitive> preferredRefs) {
730
731 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
732 if (nlists.isEmpty()) return null;
733
734 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
735 Node ntsel = null, ntnew = null, ntref = null;
736 boolean useNtsel = useSelected;
737 double minDistSq = nlists.keySet().iterator().next();
738
739 for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
740 Double distSq = entry.getKey();
741 for (Node nd : entry.getValue()) {
742 // find the nearest selected node
743 if (ntsel == null && nd.isSelected()) {
744 ntsel = nd;
745 // if there are multiple nearest nodes, prefer the one
746 // that is selected. This is required in order to drag
747 // the selected node if multiple nodes have the same
748 // coordinates (e.g. after unglue)
749 useNtsel |= (distSq == minDistSq);
750 }
751 if (ntref == null && preferredRefs != null && distSq == minDistSq) {
752 List<OsmPrimitive> ndRefs = nd.getReferrers();
753 for (OsmPrimitive ref: preferredRefs) {
754 if (ndRefs.contains(ref)) {
755 ntref = nd;
756 break;
757 }
758 }
759 }
760 // find the nearest newest node that is within about the same
761 // distance as the true nearest node
762 if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
763 ntnew = nd;
764 }
765 }
766 }
767
768 // take nearest selected, nearest new or true nearest node to p, in that order
769 if (ntsel != null && useNtsel)
770 return ntsel;
771 if (ntref != null)
772 return ntref;
773 if (ntnew != null)
774 return ntnew;
775 return nlists.values().iterator().next().get(0);
776 }
777
778 /**
779 * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
780 * @param p the screen point
781 * @param predicate this parameter imposes a condition on the returned object, e.g.
782 * give the nearest node that is tagged.
783 *
784 * @return The nearest node to point p.
785 */
786 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
787 return getNearestNode(p, predicate, true);
788 }
789
790 /**
791 * The *result* does not depend on the current map selection state,
792 * neither does the result *order*.
793 * It solely depends on the distance to point p.
794 *
795 * @return a sorted map with the keys representing the perpendicular
796 * distance of their associated way segments to point p.
797 */
798 private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
799 Predicate<OsmPrimitive> predicate) {
800 Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
801 DataSet ds = getCurrentDataSet();
802
803 if (ds != null) {
804 double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
805 snapDistanceSq *= snapDistanceSq;
806
807 for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
808 if (!predicate.evaluate(w)) {
809 continue;
810 }
811 Node lastN = null;
812 int i = -2;
813 for (Node n : w.getNodes()) {
814 i++;
815 if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
816 continue;
817 }
818 if (lastN == null) {
819 lastN = n;
820 continue;
821 }
822
823 Point2D A = getPoint2D(lastN);
824 Point2D B = getPoint2D(n);
825 double c = A.distanceSq(B);
826 double a = p.distanceSq(B);
827 double b = p.distanceSq(A);
828
829 /* perpendicular distance squared
830 * loose some precision to account for possible deviations in the calculation above
831 * e.g. if identical (A and B) come about reversed in another way, values may differ
832 * -- zero out least significant 32 dual digits of mantissa..
833 */
834 double perDistSq = Double.longBitsToDouble(
835 Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
836 >> 32 << 32); // resolution in numbers with large exponent not needed here..
837
838 if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
839 List<WaySegment> wslist;
840 if (nearestMap.containsKey(perDistSq)) {
841 wslist = nearestMap.get(perDistSq);
842 } else {
843 wslist = new LinkedList<WaySegment>();
844 nearestMap.put(perDistSq, wslist);
845 }
846 wslist.add(new WaySegment(w, i));
847 }
848
849 lastN = n;
850 }
851 }
852 }
853
854 return nearestMap;
855 }
856
857 /**
858 * The result *order* depends on the current map selection state.
859 * Segments within 10px of p are searched and sorted by their distance to @param p,
860 * then, within groups of equally distant segments, prefer those that are selected.
861 *
862 * @return all segments within 10px of p that are not in ignore,
863 * sorted by their perpendicular distance.
864 *
865 * @param p the point for which to search the nearest segments.
866 * @param ignore a collection of segments which are not to be returned.
867 * @param predicate the returned objects have to fulfill certain properties.
868 */
869 public final List<WaySegment> getNearestWaySegments(Point p,
870 Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
871 List<WaySegment> nearestList = new ArrayList<WaySegment>();
872 List<WaySegment> unselected = new LinkedList<WaySegment>();
873
874 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
875 // put selected waysegs within each distance group first
876 // makes the order of nearestList dependent on current selection state
877 for (WaySegment ws : wss) {
878 (ws.way.isSelected() ? nearestList : unselected).add(ws);
879 }
880 nearestList.addAll(unselected);
881 unselected.clear();
882 }
883 if (ignore != null) {
884 nearestList.removeAll(ignore);
885 }
886
887 return nearestList;
888 }
889
890 /**
891 * The result *order* depends on the current map selection state.
892 *
893 * @return all segments within 10px of p, sorted by their perpendicular distance.
894 * @see #getNearestWaySegments(Point, Collection, Predicate)
895 *
896 * @param p the point for which to search the nearest segments.
897 * @param predicate the returned objects have to fulfill certain properties.
898 */
899 public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
900 return getNearestWaySegments(p, null, predicate);
901 }
902
903 /**
904 * The *result* depends on the current map selection state IF use_selected is true.
905 *
906 * @return The nearest way segment to point p,
907 * and, depending on use_selected, prefers a selected way segment, if found.
908 * @see #getNearestWaySegments(Point, Collection, Predicate)
909 *
910 * @param p the point for which to search the nearest segment.
911 * @param predicate the returned object has to fulfill certain properties.
912 * @param useSelected whether selected way segments should be preferred.
913 */
914 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
915 WaySegment wayseg = null, ntsel = null;
916
917 for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
918 if (wayseg != null && ntsel != null) {
919 break;
920 }
921 for (WaySegment ws : wslist) {
922 if (wayseg == null) {
923 wayseg = ws;
924 }
925 if (ntsel == null && ws.way.isSelected()) {
926 ntsel = ws;
927 }
928 }
929 }
930
931 return (ntsel != null && useSelected) ? ntsel : wayseg;
932 }
933
934 /**
935 * The *result* depends on the current map selection state IF use_selected is true.
936 *
937 * @return The nearest way segment to point p,
938 * and, depending on use_selected, prefers a selected way segment, if found.
939 * Also prefers segments of ways that are related to one of preferredRefs primitives
940 * @see #getNearestWaySegments(Point, Collection, Predicate)
941 * @since 6065
942 * @param p the point for which to search the nearest segment.
943 * @param predicate the returned object has to fulfill certain properties.
944 * @param use_selected whether selected way segments should be preferred.
945 * @param preferredRefs - prefer segments related to these primitives, may be null
946 */
947 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate,
948 boolean use_selected, Collection<OsmPrimitive> preferredRefs) {
949 WaySegment wayseg = null, ntsel = null, ntref = null;
950 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
951
952 searchLoop: for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
953 for (WaySegment ws : wslist) {
954 if (wayseg == null) {
955 wayseg = ws;
956 }
957 if (ntsel == null && ws.way.isSelected()) {
958 ntsel = ws;
959 break searchLoop;
960 }
961 if (ntref == null && preferredRefs != null) {
962 // prefer ways containing given nodes
963 for (Node nd: ws.way.getNodes()) {
964 if (preferredRefs.contains(nd)) {
965 ntref = ws;
966 break searchLoop;
967 }
968 }
969 Collection<OsmPrimitive> wayRefs = ws.way.getReferrers();
970 // prefer member of the given relations
971 for (OsmPrimitive ref: preferredRefs) {
972 if (ref instanceof Relation && wayRefs.contains(ref)) {
973 ntref = ws;
974 break searchLoop;
975 }
976 }
977 }
978 }
979 }
980 if (ntsel != null && use_selected)
981 return ntsel;
982 if (ntref != null)
983 return ntref;
984 return wayseg;
985 }
986
987 /**
988 * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
989 * @param p the point for which to search the nearest segment.
990 * @param predicate the returned object has to fulfill certain properties.
991 *
992 * @return The nearest way segment to point p.
993 */
994 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
995 return getNearestWaySegment(p, predicate, true);
996 }
997
998 /**
999 * The *result* does not depend on the current map selection state,
1000 * neither does the result *order*.
1001 * It solely depends on the perpendicular distance to point p.
1002 *
1003 * @return all nearest ways to the screen point given that are not in ignore.
1004 * @see #getNearestWaySegments(Point, Collection, Predicate)
1005 *
1006 * @param p the point for which to search the nearest ways.
1007 * @param ignore a collection of ways which are not to be returned.
1008 * @param predicate the returned object has to fulfill certain properties.
1009 */
1010 public final List<Way> getNearestWays(Point p,
1011 Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
1012 List<Way> nearestList = new ArrayList<Way>();
1013 Set<Way> wset = new HashSet<Way>();
1014
1015 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1016 for (WaySegment ws : wss) {
1017 if (wset.add(ws.way)) {
1018 nearestList.add(ws.way);
1019 }
1020 }
1021 }
1022 if (ignore != null) {
1023 nearestList.removeAll(ignore);
1024 }
1025
1026 return nearestList;
1027 }
1028
1029 /**
1030 * The *result* does not depend on the current map selection state,
1031 * neither does the result *order*.
1032 * It solely depends on the perpendicular distance to point p.
1033 *
1034 * @return all nearest ways to the screen point given.
1035 * @see #getNearestWays(Point, Collection, Predicate)
1036 *
1037 * @param p the point for which to search the nearest ways.
1038 * @param predicate the returned object has to fulfill certain properties.
1039 */
1040 public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
1041 return getNearestWays(p, null, predicate);
1042 }
1043
1044 /**
1045 * The *result* depends on the current map selection state.
1046 *
1047 * @return The nearest way to point p,
1048 * prefer a selected way if there are multiple nearest.
1049 * @see #getNearestWaySegment(Point, Predicate)
1050 *
1051 * @param p the point for which to search the nearest segment.
1052 * @param predicate the returned object has to fulfill certain properties.
1053 */
1054 public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
1055 WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
1056 return (nearestWaySeg == null) ? null : nearestWaySeg.way;
1057 }
1058
1059 /**
1060 * The *result* does not depend on the current map selection state,
1061 * neither does the result *order*.
1062 * It solely depends on the distance to point p.
1063 *
1064 * First, nodes will be searched. If there are nodes within BBox found,
1065 * return a collection of those nodes only.
1066 *
1067 * If no nodes are found, search for nearest ways. If there are ways
1068 * within BBox found, return a collection of those ways only.
1069 *
1070 * If nothing is found, return an empty collection.
1071 *
1072 * @return Primitives nearest to the given screen point that are not in ignore.
1073 * @see #getNearestNodes(Point, Collection, Predicate)
1074 * @see #getNearestWays(Point, Collection, Predicate)
1075 *
1076 * @param p The point on screen.
1077 * @param ignore a collection of ways which are not to be returned.
1078 * @param predicate the returned object has to fulfill certain properties.
1079 */
1080 public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
1081 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1082 List<OsmPrimitive> nearestList = Collections.emptyList();
1083 OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
1084
1085 if (osm != null) {
1086 if (osm instanceof Node) {
1087 nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
1088 } else if (osm instanceof Way) {
1089 nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
1090 }
1091 if (ignore != null) {
1092 nearestList.removeAll(ignore);
1093 }
1094 }
1095
1096 return nearestList;
1097 }
1098
1099 /**
1100 * The *result* does not depend on the current map selection state,
1101 * neither does the result *order*.
1102 * It solely depends on the distance to point p.
1103 *
1104 * @return Primitives nearest to the given screen point.
1105 * @see #getNearestNodesOrWays(Point, Collection, Predicate)
1106 *
1107 * @param p The point on screen.
1108 * @param predicate the returned object has to fulfill certain properties.
1109 */
1110 public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
1111 return getNearestNodesOrWays(p, null, predicate);
1112 }
1113
1114 /**
1115 * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
1116 * It decides, whether to yield the node to be tested or look for further (way) candidates.
1117 *
1118 * @return true, if the node fulfills the properties of the function body
1119 *
1120 * @param osm node to check
1121 * @param p point clicked
1122 * @param use_selected whether to prefer selected nodes
1123 */
1124 private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
1125 if (osm != null) {
1126 if (!(p.distanceSq(getPoint2D(osm)) > (4)*(4))) return true;
1127 if (osm.isTagged()) return true;
1128 if (use_selected && osm.isSelected()) return true;
1129 }
1130 return false;
1131 }
1132
1133 /**
1134 * The *result* depends on the current map selection state IF use_selected is true.
1135 *
1136 * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
1137 * the nearest, selected node. If not found, try {@link #getNearestWaySegment(Point, Predicate)}
1138 * to find the nearest selected way.
1139 *
1140 * IF use_selected is false, or if no selected primitive was found, do the following.
1141 *
1142 * If the nearest node found is within 4px of p, simply take it.
1143 * Else, find the nearest way segment. Then, if p is closer to its
1144 * middle than to the node, take the way segment, else take the node.
1145 *
1146 * Finally, if no nearest primitive is found at all, return null.
1147 *
1148 * @return A primitive within snap-distance to point p,
1149 * that is chosen by the algorithm described.
1150 * @see #getNearestNode(Point, Predicate)
1151 * @see #getNearestWay(Point, Predicate)
1152 *
1153 * @param p The point on screen.
1154 * @param predicate the returned object has to fulfill certain properties.
1155 * @param use_selected whether to prefer primitives that are currently selected or referred by selected primitives
1156 */
1157 public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
1158 Collection<OsmPrimitive> sel;
1159 DataSet ds = getCurrentDataSet();
1160 if (use_selected && ds!=null) {
1161 sel = ds.getSelected();
1162 } else {
1163 sel = null;
1164 }
1165 OsmPrimitive osm = getNearestNode(p, predicate, use_selected, sel);
1166
1167 if (isPrecedenceNode((Node)osm, p, use_selected)) return osm;
1168 WaySegment ws;
1169 if (use_selected) {
1170 ws = getNearestWaySegment(p, predicate, use_selected, sel);
1171 } else {
1172 ws = getNearestWaySegment(p, predicate, use_selected);
1173 }
1174 if (ws == null) return osm;
1175
1176 if ((ws.way.isSelected() && use_selected) || osm == null) {
1177 // either (no _selected_ nearest node found, if desired) or no nearest node was found
1178 osm = ws.way;
1179 } else {
1180 int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1181 maxWaySegLenSq *= maxWaySegLenSq;
1182
1183 Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1184 Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1185
1186 // is wayseg shorter than maxWaySegLenSq and
1187 // is p closer to the middle of wayseg than to the nearest node?
1188 if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1189 p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
1190 osm = ws.way;
1191 }
1192 }
1193 return osm;
1194 }
1195
1196 public static double perDist(Point2D pt, Point2D a, Point2D b) {
1197 if (pt != null && a != null && b != null) {
1198 double pd = (
1199 (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1200 (a.getY()-pt.getY())*(b.getY()-a.getY()) );
1201 return Math.abs(pd) / a.distance(b);
1202 }
1203 return 0d;
1204 }
1205
1206 /**
1207 *
1208 * @param pt point to project onto (ab)
1209 * @param a root of vector
1210 * @param b vector
1211 * @return point of intersection of line given by (ab)
1212 * with its orthogonal line running through pt
1213 */
1214 public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1215 if (pt != null && a != null && b != null) {
1216 double r = ((
1217 (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1218 (pt.getY()-a.getY())*(b.getY()-a.getY()) )
1219 / a.distanceSq(b));
1220 return project(r, a, b);
1221 }
1222 return null;
1223 }
1224
1225 /**
1226 * if r = 0 returns a, if r=1 returns b,
1227 * if r = 0.5 returns center between a and b, etc..
1228 *
1229 * @param r scale value
1230 * @param a root of vector
1231 * @param b vector
1232 * @return new point at a + r*(ab)
1233 */
1234 public static Point2D project(double r, Point2D a, Point2D b) {
1235 Point2D ret = null;
1236
1237 if (a != null && b != null) {
1238 ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1239 a.getY() + r*(b.getY()-a.getY()));
1240 }
1241 return ret;
1242 }
1243
1244 /**
1245 * The *result* does not depend on the current map selection state,
1246 * neither does the result *order*.
1247 * It solely depends on the distance to point p.
1248 *
1249 * @return a list of all objects that are nearest to point p and
1250 * not in ignore or an empty list if nothing was found.
1251 *
1252 * @param p The point on screen.
1253 * @param ignore a collection of ways which are not to be returned.
1254 * @param predicate the returned object has to fulfill certain properties.
1255 */
1256 public final List<OsmPrimitive> getAllNearest(Point p,
1257 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1258 List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
1259 Set<Way> wset = new HashSet<Way>();
1260
1261 // add nearby ways
1262 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1263 for (WaySegment ws : wss) {
1264 if (wset.add(ws.way)) {
1265 nearestList.add(ws.way);
1266 }
1267 }
1268 }
1269
1270 // add nearby nodes
1271 for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1272 nearestList.addAll(nlist);
1273 }
1274
1275 // add parent relations of nearby nodes and ways
1276 Set<OsmPrimitive> parentRelations = new HashSet<OsmPrimitive>();
1277 for (OsmPrimitive o : nearestList) {
1278 for (OsmPrimitive r : o.getReferrers()) {
1279 if (r instanceof Relation && predicate.evaluate(r)) {
1280 parentRelations.add(r);
1281 }
1282 }
1283 }
1284 nearestList.addAll(parentRelations);
1285
1286 if (ignore != null) {
1287 nearestList.removeAll(ignore);
1288 }
1289
1290 return nearestList;
1291 }
1292
1293 /**
1294 * The *result* does not depend on the current map selection state,
1295 * neither does the result *order*.
1296 * It solely depends on the distance to point p.
1297 *
1298 * @return a list of all objects that are nearest to point p
1299 * or an empty list if nothing was found.
1300 * @see #getAllNearest(Point, Collection, Predicate)
1301 *
1302 * @param p The point on screen.
1303 * @param predicate the returned object has to fulfill certain properties.
1304 */
1305 public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1306 return getAllNearest(p, null, predicate);
1307 }
1308
1309 /**
1310 * @return The projection to be used in calculating stuff.
1311 */
1312 public Projection getProjection() {
1313 return Main.getProjection();
1314 }
1315
1316 @Override
1317 public String helpTopic() {
1318 String n = getClass().getName();
1319 return n.substring(n.lastIndexOf('.')+1);
1320 }
1321
1322 /**
1323 * Return a ID which is unique as long as viewport dimensions are the same
1324 * @return A unique ID, as long as viewport dimensions are the same
1325 */
1326 public int getViewID() {
1327 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
1328 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
1329 CRC32 id = new CRC32();
1330 id.update(x.getBytes(Utils.UTF_8));
1331 return (int)id.getValue();
1332 }
1333
1334 /**
1335 * Returns the current system of measurement.
1336 * @return The current system of measurement (metric system by default).
1337 * @since 3490
1338 */
1339 public static SystemOfMeasurement getSystemOfMeasurement() {
1340 SystemOfMeasurement som = SystemOfMeasurement.ALL_SYSTEMS.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
1341 if (som == null)
1342 return SystemOfMeasurement.METRIC;
1343 return som;
1344 }
1345
1346 /**
1347 * Sets the current system of measurement.
1348 * @param somKey The system of measurement key. Must be defined in {@link SystemOfMeasurement#ALL_SYSTEMS}.
1349 * @since 6056
1350 * @throws IllegalArgumentException if {@code somKey} is not known
1351 */
1352 public static void setSystemOfMeasurement(String somKey) {
1353 if (!SystemOfMeasurement.ALL_SYSTEMS.containsKey(somKey)) {
1354 throw new IllegalArgumentException("Invalid system of measurement: "+somKey);
1355 }
1356 String oldKey = ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get();
1357 if (ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.put(somKey)) {
1358 fireSoMChanged(oldKey, somKey);
1359 }
1360 }
1361
1362 private static class CursorInfo {
1363 final Cursor cursor;
1364 final Object object;
1365 public CursorInfo(Cursor c, Object o) {
1366 cursor = c;
1367 object = o;
1368 }
1369 }
1370
1371 private LinkedList<CursorInfo> cursors = new LinkedList<CursorInfo>();
1372
1373 /**
1374 * Set new cursor.
1375 */
1376 public void setNewCursor(Cursor cursor, Object reference) {
1377 if (!cursors.isEmpty()) {
1378 CursorInfo l = cursors.getLast();
1379 if(l != null && l.cursor == cursor && l.object == reference)
1380 return;
1381 stripCursors(reference);
1382 }
1383 cursors.add(new CursorInfo(cursor, reference));
1384 setCursor(cursor);
1385 }
1386
1387 public void setNewCursor(int cursor, Object reference) {
1388 setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1389 }
1390
1391 /**
1392 * Remove the new cursor and reset to previous
1393 */
1394 public void resetCursor(Object reference) {
1395 if (cursors.isEmpty()) {
1396 setCursor(null);
1397 return;
1398 }
1399 CursorInfo l = cursors.getLast();
1400 stripCursors(reference);
1401 if (l != null && l.object == reference) {
1402 if (cursors.isEmpty()) {
1403 setCursor(null);
1404 } else {
1405 setCursor(cursors.getLast().cursor);
1406 }
1407 }
1408 }
1409
1410 private void stripCursors(Object reference) {
1411 LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
1412 for(CursorInfo i : cursors) {
1413 if(i.object != reference) {
1414 c.add(i);
1415 }
1416 }
1417 cursors = c;
1418 }
1419
1420 @Override
1421 public void paint(Graphics g) {
1422 synchronized (paintRequestLock) {
1423 if (paintRect != null) {
1424 Graphics g2 = g.create();
1425 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1426 g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1427 g2.dispose();
1428 }
1429 if (paintPoly != null) {
1430 Graphics g2 = g.create();
1431 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1432 g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1433 g2.dispose();
1434 }
1435 }
1436 super.paint(g);
1437 }
1438
1439 /**
1440 * Requests to paint the given {@code Rectangle}.
1441 * @param r The Rectangle to draw
1442 * @see #requestClearRect
1443 * @since 5500
1444 */
1445 public void requestPaintRect(Rectangle r) {
1446 if (r != null) {
1447 synchronized (paintRequestLock) {
1448 paintRect = r;
1449 }
1450 repaint();
1451 }
1452 }
1453
1454 /**
1455 * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1456 * @param p The Polygon to draw
1457 * @see #requestClearPoly
1458 * @since 5500
1459 */
1460 public void requestPaintPoly(Polygon p) {
1461 if (p != null) {
1462 synchronized (paintRequestLock) {
1463 paintPoly = p;
1464 }
1465 repaint();
1466 }
1467 }
1468
1469 /**
1470 * Requests to clear the rectangled previously drawn.
1471 * @see #requestPaintRect
1472 * @since 5500
1473 */
1474 public void requestClearRect() {
1475 synchronized (paintRequestLock) {
1476 paintRect = null;
1477 }
1478 repaint();
1479 }
1480
1481 /**
1482 * Requests to clear the polyline previously drawn.
1483 * @see #requestPaintPoly
1484 * @since 5500
1485 */
1486 public void requestClearPoly() {
1487 synchronized (paintRequestLock) {
1488 paintPoly = null;
1489 }
1490 repaint();
1491 }
1492}
Note: See TracBrowser for help on using the repository browser.