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

Last change on this file since 5965 was 5952, checked in by Don-vip, 11 years ago

fix #8610 - wrong area computation, new preference system_of_measurement.use_only_custom_area_unit(patch by Preferred)

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