19 package org.sleuthkit.autopsy.geolocation;
21 import java.awt.AlphaComposite;
22 import java.awt.BasicStroke;
23 import java.awt.Color;
24 import java.awt.Dimension;
25 import java.awt.Graphics2D;
26 import java.awt.Point;
27 import java.awt.Rectangle;
28 import java.awt.RenderingHints;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.awt.event.ComponentAdapter;
32 import java.awt.event.ComponentEvent;
33 import java.awt.geom.Point2D;
34 import java.awt.image.BufferedImage;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
47 import java.util.logging.Level;
48 import java.util.prefs.PreferenceChangeEvent;
49 import java.util.prefs.PreferenceChangeListener;
50 import javax.swing.JMenuItem;
51 import javax.swing.JOptionPane;
52 import javax.swing.JPopupMenu;
53 import javax.swing.JSeparator;
54 import javax.swing.Popup;
55 import javax.swing.PopupFactory;
56 import javax.swing.Timer;
57 import javax.swing.event.MouseInputListener;
58 import org.jxmapviewer.JXMapViewer;
59 import org.jxmapviewer.OSMTileFactoryInfo;
60 import org.jxmapviewer.VirtualEarthTileFactoryInfo;
61 import org.jxmapviewer.input.CenterMapListener;
62 import org.jxmapviewer.input.ZoomMouseWheelListenerCursor;
63 import org.jxmapviewer.viewer.DefaultTileFactory;
64 import org.jxmapviewer.viewer.GeoPosition;
65 import org.jxmapviewer.viewer.TileFactory;
66 import org.jxmapviewer.viewer.TileFactoryInfo;
67 import org.jxmapviewer.viewer.WaypointPainter;
68 import org.jxmapviewer.viewer.WaypointRenderer;
69 import org.openide.util.NbBundle.Messages;
75 import javax.imageio.ImageIO;
76 import javax.swing.SwingUtilities;
77 import org.jxmapviewer.painter.CompoundPainter;
78 import org.jxmapviewer.painter.Painter;
79 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
84 @SuppressWarnings(
"deprecation")
85 final public class
MapPanel extends javax.swing.JPanel {
87 static final String CURRENT_MOUSE_GEOPOSITION =
"CURRENT_MOUSE_GEOPOSITION";
91 private static final long serialVersionUID = 1L;
92 private static final Set<Integer> DOT_WAYPOINT_TYPES =
new HashSet<>();
93 private static final int DOT_SIZE = 12;
98 private List<Set<MapWaypoint>> tracks =
new ArrayList<>();
103 private static final int POPUP_WIDTH = 300;
104 private static final int POPUP_HEIGHT = 200;
105 private static final int POPUP_MARGIN = 10;
114 DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID());
115 DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID());
116 DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID());
123 "MapPanel_connection_failure_message=Failed to connect to new geolocation map tile source.",
124 "MapPanel_connection_failure_message_title=Connection Failure"
129 zoomChanging =
false;
131 popupFactory =
new PopupFactory();
134 whiteWaypointImage = ImageIO.read(getClass().getResource(
"/org/sleuthkit/autopsy/images/waypoint_white.png"));
135 transparentWaypointImage = ImageIO.read(getClass().getResource(
"/org/sleuthkit/autopsy/images/waypoint_transparent.png"));
136 }
catch (IOException ex) {
137 logger.log(Level.WARNING,
"Unable to load geolocation waypoint images", ex);
146 public void resizeTimedOut() {
153 public void preferenceChange(PreferenceChangeEvent evt) {
156 mapViewer.getTileFactory().dispose();
158 mapViewer.setTileFactory(getTileFactory());
159 initializeZoomSlider();
161 logger.log(Level.SEVERE,
"Failed to connect to new geolocation tile server.", ex);
162 JOptionPane.showMessageDialog(
MapPanel.this,
163 Bundle.MapPanel_connection_failure_message(),
164 Bundle.MapPanel_connection_failure_message_title(),
165 JOptionPane.ERROR_MESSAGE);
167 Bundle.MapPanel_connection_failure_message_title(),
168 Bundle.MapPanel_connection_failure_message());
179 List<MapWaypoint> getVisibleWaypoints() {
181 Rectangle viewport = mapViewer.getViewportBounds();
182 List<MapWaypoint> waypoints =
new ArrayList<>();
184 Iterator<MapWaypoint> iterator = waypointTree.
iterator();
185 while (iterator.hasNext()) {
186 MapWaypoint waypoint = iterator.next();
187 if (viewport.contains(mapViewer.getTileFactory().geoToPixel(waypoint.getPosition(), mapViewer.getZoom()))) {
188 waypoints.add(waypoint);
198 void initMap() throws GeoLocationDataException {
200 TileFactory tileFactory = getTileFactory();
201 mapViewer.setTileFactory(tileFactory);
204 MouseInputListener mia =
new MapPanMouseInputListener(mapViewer);
205 mapViewer.addMouseListener(mia);
206 mapViewer.addMouseMotionListener(mia);
208 mapViewer.addMouseListener(
new CenterMapListener(mapViewer));
209 mapViewer.addMouseWheelListener(
new ZoomMouseWheelListenerCursor(mapViewer));
212 mapViewer.addPropertyChangeListener(
"zoom",
new PropertyChangeListener() {
214 public void propertyChange(PropertyChangeEvent evt) {
215 zoomSlider.setValue(mapViewer.getZoom());
219 zoomSlider.setMinimum(tileFactory.getInfo().getMinimumZoomLevel());
220 zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
222 setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1);
224 mapViewer.setCenterPosition(
new GeoPosition(0, 0));
229 void initializePainter() {
231 WaypointPainter<MapWaypoint> waypointPainter =
new WaypointPainter<MapWaypoint>() {
233 public Set<MapWaypoint> getWaypoints() {
234 if (currentlySelectedWaypoint != null) {
235 waypointSet.remove(currentlySelectedWaypoint);
236 waypointSet.add(currentlySelectedWaypoint);
241 waypointPainter.setRenderer(
new MapWaypointRenderer());
243 ArrayList<Painter<JXMapViewer>> painters =
new ArrayList<>();
244 painters.add(
new MapTrackRenderer(tracks));
245 painters.add(waypointPainter);
247 CompoundPainter<JXMapViewer> compoundPainter =
new CompoundPainter<>(painters);
248 mapViewer.setOverlayPainter(compoundPainter);
254 void initializeZoomSlider() {
255 TileFactory tileFactory = mapViewer.getTileFactory();
256 zoomSlider.setMinimum(tileFactory.getInfo().getMinimumZoomLevel());
257 zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
259 zoomSlider.repaint();
260 zoomSlider.revalidate();
270 case ONLINE_USER_DEFINED_OSM_SERVER:
272 case OFFLINE_OSM_ZIP:
274 case OFFILE_MBTILES_FILE:
277 return new DefaultTileFactory(
new VirtualEarthTileFactoryInfo(VirtualEarthTileFactoryInfo.MAP));
291 if (address.isEmpty()) {
292 throw new GeoLocationDataException(
"Invalid user preference for OSM user define tile server. Address is an empty string.");
294 TileFactoryInfo info =
new OSMTileFactoryInfo(
"User Defined Server", address);
309 if (path.isEmpty()) {
310 throw new GeoLocationDataException(
"Invalid OSM tile Zip file. User preference value is empty string.");
312 File file =
new File(path);
313 if (!file.exists() || !file.canRead()) {
314 throw new GeoLocationDataException(
"Invalid OSM tile zip file. Unable to read file: " + path);
317 String zipPath = path.replaceAll(
"\\\\",
"/");
319 return new OSMTileFactoryInfo(
"ZIP archive",
"jar:file:/" + zipPath +
"!");
328 void setWaypoints(Set<MapWaypoint> waypoints) {
330 this.waypointSet = waypoints;
331 for (MapWaypoint waypoint : waypoints) {
332 waypointTree.
add(waypoint);
342 void setTracks(List<Set<MapWaypoint>> tracks) {
343 this.tracks = tracks;
351 void setZoom(
int zoom) {
353 mapViewer.setZoom(zoom);
354 zoomSlider.setValue(zoom);
355 zoomChanging =
false;
361 void clearWaypoints() {
363 currentlySelectedWaypoint = null;
364 currentlySelectedTrack = null;
365 if (currentPopup != null) {
379 List<MapWaypoint> waypoints = findClosestWaypoint(point);
380 MapWaypoint waypoint = null;
381 if (waypoints.size() > 0) {
382 waypoint = waypoints.get(0);
384 showPopupMenu(waypoint, point);
387 if (waypoint != null && !waypoint.equals(currentlySelectedWaypoint)) {
388 currentlySelectedWaypoint = waypoint;
389 if (currentPopup != null) {
394 }
catch (TskCoreException ex) {
395 logger.log(Level.WARNING,
"Failed to show popup for waypoint", ex);
405 private void showPopupMenu(MapWaypoint waypoint, Point point)
throws TskCoreException {
406 if (waypoint == null) {
410 JMenuItem[] items = waypoint.getMenuItems();
411 JPopupMenu popupMenu =
new JPopupMenu();
412 for (JMenuItem menu : items) {
417 popupMenu.add(
new JSeparator());
420 popupMenu.show(mapViewer, point.x, point.y);
427 if (currentlySelectedWaypoint != null) {
428 if (currentPopup != null) {
432 WaypointDetailPanel detailPane =
new WaypointDetailPanel();
433 detailPane.setWaypoint(currentlySelectedWaypoint);
434 detailPane.setPreferredSize(
new Dimension(POPUP_WIDTH, POPUP_HEIGHT));
435 detailPane.addActionListener(
new ActionListener() {
437 public void actionPerformed(ActionEvent e) {
438 if (currentPopup != null) {
446 Point popupLocation = getLocationForDetailsPopup();
448 currentPopup = popupFactory.getPopup(
this, detailPane, popupLocation.x, popupLocation.y);
452 if (currentPopup != null) {
457 mapViewer.revalidate();
467 Point panelCorner = this.getLocationOnScreen();
468 int width = this.getWidth();
470 int popupX = panelCorner.x + width - POPUP_WIDTH - POPUP_MARGIN;
471 int popupY = panelCorner.y + POPUP_MARGIN;
473 return new Point(popupX, popupY);
485 if (waypointTree == null) {
486 return new ArrayList<>();
490 GeoPosition geopos = mapViewer.convertPointToGeoPosition(clickPoint);
493 Collection<MapWaypoint> waypoints = waypointTree.
nearestNeighbourSearch(1, MapWaypoint.getDummyWaypoint(geopos));
495 if (waypoints == null || waypoints.isEmpty()) {
499 Iterator<MapWaypoint> iterator = waypoints.iterator();
503 List<MapWaypoint> closestPoints =
new ArrayList<>();
504 while (iterator.hasNext()) {
505 MapWaypoint nextWaypoint = iterator.next();
506 Point2D point = mapViewer.convertGeoPositionToPoint(nextWaypoint.getPosition());
507 int pointX = (int) point.getX();
508 int pointY = (int) point.getY();
510 if (DOT_WAYPOINT_TYPES.contains(nextWaypoint.getArtifactTypeID())) {
511 rect =
new Rectangle(
512 pointX - (DOT_SIZE / 2),
513 pointY - (DOT_SIZE / 2),
518 rect =
new Rectangle(
519 pointX - (whiteWaypointImage.getWidth() / 2),
520 pointY - whiteWaypointImage.getHeight(),
521 whiteWaypointImage.getWidth(),
522 whiteWaypointImage.getHeight()
526 if (rect.contains(clickPoint)) {
527 closestPoints.add(nextWaypoint);
531 return closestPoints;
544 extends ComponentAdapter
545 implements ActionListener {
548 private static final int DEFAULT_TIMEOUT = 200;
555 this(DEFAULT_TIMEOUT);
564 timer =
new Timer(delayMS,
this);
565 timer.setRepeats(
false);
566 timer.setCoalesce(
false);
582 public abstract void resizeTimedOut();
590 @SuppressWarnings(
"unchecked")
592 private
void initComponents() {
593 java.awt.GridBagConstraints gridBagConstraints;
595 mapViewer =
new org.jxmapviewer.JXMapViewer();
596 zoomPanel =
new javax.swing.JPanel();
597 zoomSlider =
new javax.swing.JSlider();
598 javax.swing.JButton zoomInBtn =
new javax.swing.JButton();
599 javax.swing.JButton zoomOutBtn =
new javax.swing.JButton();
602 setLayout(
new java.awt.BorderLayout());
604 mapViewer.addMouseMotionListener(
new java.awt.event.MouseMotionAdapter() {
605 public void mouseMoved(java.awt.event.MouseEvent evt) {
606 mapViewerMouseMoved(evt);
609 mapViewer.addMouseListener(
new java.awt.event.MouseAdapter() {
610 public void mouseClicked(java.awt.event.MouseEvent evt) {
611 mapViewerMouseClicked(evt);
613 public void mousePressed(java.awt.event.MouseEvent evt) {
614 mapViewerMousePressed(evt);
616 public void mouseReleased(java.awt.event.MouseEvent evt) {
617 mapViewerMouseReleased(evt);
620 mapViewer.setLayout(
new java.awt.GridBagLayout());
622 zoomPanel.setFocusable(
false);
623 zoomPanel.setOpaque(
false);
624 zoomPanel.setRequestFocusEnabled(
false);
625 zoomPanel.setLayout(
new java.awt.GridBagLayout());
627 zoomSlider.setMaximum(15);
628 zoomSlider.setMinimum(10);
629 zoomSlider.setMinorTickSpacing(1);
630 zoomSlider.setOrientation(javax.swing.JSlider.VERTICAL);
631 zoomSlider.setPaintTicks(
true);
632 zoomSlider.setSnapToTicks(
true);
633 zoomSlider.setInverted(
true);
634 zoomSlider.setMinimumSize(
new java.awt.Dimension(35, 100));
635 zoomSlider.setOpaque(
false);
636 zoomSlider.setPreferredSize(
new java.awt.Dimension(35, 190));
637 zoomSlider.addChangeListener(
new javax.swing.event.ChangeListener() {
638 public void stateChanged(javax.swing.event.ChangeEvent evt) {
639 zoomSliderStateChanged(evt);
642 gridBagConstraints =
new java.awt.GridBagConstraints();
643 gridBagConstraints.gridx = 0;
644 gridBagConstraints.gridy = 1;
645 zoomPanel.add(zoomSlider, gridBagConstraints);
647 zoomInBtn.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/images/plus-grey.png")));
648 org.openide.awt.Mnemonics.setLocalizedText(zoomInBtn,
org.openide.util.NbBundle.getMessage(
MapPanel.class,
"MapPanel.zoomInBtn.text"));
649 zoomInBtn.setBorder(null);
650 zoomInBtn.setBorderPainted(
false);
651 zoomInBtn.setFocusPainted(
false);
652 zoomInBtn.setRequestFocusEnabled(
false);
653 zoomInBtn.setRolloverEnabled(
false);
654 zoomInBtn.addActionListener(
new java.awt.event.ActionListener() {
655 public void actionPerformed(java.awt.event.ActionEvent evt) {
656 zoomInBtnActionPerformed(evt);
659 gridBagConstraints =
new java.awt.GridBagConstraints();
660 gridBagConstraints.gridx = 0;
661 gridBagConstraints.gridy = 0;
662 zoomPanel.add(zoomInBtn, gridBagConstraints);
664 zoomOutBtn.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/images/minus-grey.png")));
665 org.openide.awt.Mnemonics.setLocalizedText(zoomOutBtn,
org.openide.util.NbBundle.getMessage(
MapPanel.class,
"MapPanel.zoomOutBtn.text"));
666 zoomOutBtn.setBorder(null);
667 zoomOutBtn.setBorderPainted(
false);
668 zoomOutBtn.setFocusPainted(
false);
669 zoomOutBtn.setRequestFocusEnabled(
false);
670 zoomOutBtn.setRolloverEnabled(
false);
671 zoomOutBtn.addActionListener(
new java.awt.event.ActionListener() {
672 public void actionPerformed(java.awt.event.ActionEvent evt) {
673 zoomOutBtnActionPerformed(evt);
676 gridBagConstraints =
new java.awt.GridBagConstraints();
677 gridBagConstraints.gridx = 0;
678 gridBagConstraints.gridy = 2;
679 zoomPanel.add(zoomOutBtn, gridBagConstraints);
681 gridBagConstraints =
new java.awt.GridBagConstraints();
682 gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
683 gridBagConstraints.weightx = 1.0;
684 gridBagConstraints.weighty = 1.0;
685 gridBagConstraints.insets =
new java.awt.Insets(4, 4, 4, 4);
686 mapViewer.add(zoomPanel, gridBagConstraints);
688 add(mapViewer, java.awt.BorderLayout.CENTER);
693 setZoom(zoomSlider.getValue());
698 if (evt.isPopupTrigger()) {
699 showPopupMenu(evt.getPoint());
704 if (evt.isPopupTrigger()) {
705 showPopupMenu(evt.getPoint());
710 GeoPosition geopos = mapViewer.convertPointToGeoPosition(evt.getPoint());
711 firePropertyChange(CURRENT_MOUSE_GEOPOSITION, null, geopos);
715 if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt)) {
716 List<MapWaypoint> waypoints = findClosestWaypoint(evt.getPoint());
717 if (waypoints.size() > 0) {
718 MapWaypoint selection = waypoints.get(0);
719 currentlySelectedWaypoint = selection;
720 currentlySelectedTrack = null;
721 for (Set<MapWaypoint> track : tracks) {
722 if (track.contains(selection)) {
723 currentlySelectedTrack = track;
728 currentlySelectedWaypoint = null;
729 currentlySelectedTrack = null;
736 int currentValue = mapViewer.getZoom();
737 setZoom(currentValue - 1);
741 int currentValue = mapViewer.getZoom();
742 setZoom(currentValue + 1);
757 private final Map<Color, BufferedImage> dotImageCache =
new HashMap<>();
758 private final Map<Color, BufferedImage> waypointImageCache =
new HashMap<>();
767 Color baseColor = waypoint.getColor();
768 if (waypoint.equals(currentlySelectedWaypoint)
769 || (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) {
787 BufferedImage ret =
new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
788 Graphics2D g = ret.createGraphics();
789 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
791 g.fillOval(1, 1, s - 2, s - 2);
792 g.setColor(Color.BLACK);
793 g.setStroke(
new BasicStroke(1));
794 g.drawOval(1, 1, s - 2, s - 2);
807 int w = whiteWaypointImage.getWidth();
808 int h = whiteWaypointImage.getHeight();
810 BufferedImage ret =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
812 Graphics2D g = ret.createGraphics();
813 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
814 g.drawImage(whiteWaypointImage, 0, 0, null);
815 g.setComposite(AlphaComposite.SrcIn);
817 g.fillRect(0, 0, w, h);
818 g.setComposite(AlphaComposite.SrcAtop);
819 g.drawImage(transparentWaypointImage, 0, 0, null);
825 public void paintWaypoint(Graphics2D g, JXMapViewer map, MapWaypoint waypoint) {
826 Color color = getColor(waypoint);
828 Point2D point = map.getTileFactory().geoToPixel(waypoint.getPosition(), map.getZoom());
829 int x = (int) point.getX();
830 int y = (int) point.getY();
832 if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
833 image = dotImageCache.computeIfAbsent(color, k -> {
834 return createTrackDotImage(color);
837 y -= image.getHeight() / 2;
839 image = waypointImageCache.computeIfAbsent(color, k -> {
840 return createWaypointImage(color);
843 y -= image.getHeight();
846 x -= image.getWidth() / 2;
848 Graphics2D g2d = (Graphics2D) g.create();
849 g2d.drawImage(image, x, y, null);
859 private final List<Set<MapWaypoint>>
tracks;
862 this.tracks = tracks;
865 private void drawRoute(Set<MapWaypoint> track, Graphics2D g, JXMapViewer map) {
869 boolean first =
true;
871 for (MapWaypoint wp : track) {
872 Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom());
873 int thisX = (int) p.getX();
874 int thisY = (int) p.getY();
879 g.drawLine(lastX, lastY, thisX, thisY);
888 public void paint(Graphics2D g, JXMapViewer map,
int w,
int h) {
889 Graphics2D g2d = (Graphics2D) g.create();
891 Rectangle bounds = map.getViewportBounds();
892 g2d.translate(-bounds.x, -bounds.y);
894 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
896 g2d.setColor(Color.BLACK);
897 g2d.setStroke(
new BasicStroke(2));
899 for (Set<MapWaypoint> track : tracks) {
900 drawRoute(track, g2d, map);
final List< Set< MapWaypoint > > tracks
void zoomInBtnActionPerformed(java.awt.event.ActionEvent evt)
static String getGeolocationOsmZipPath()
final PopupFactory popupFactory
void showPopupMenu(MapWaypoint waypoint, Point point)
BufferedImage createWaypointImage(Color color)
void zoomSliderStateChanged(javax.swing.event.ChangeEvent evt)
void componentResized(ComponentEvent e)
Set< MapWaypoint > currentlySelectedTrack
void mapViewerMouseReleased(java.awt.event.MouseEvent evt)
Collection< T > nearestNeighbourSearch(int numNeighbors, T value)
ComponentResizeEndListener()
javax.swing.JPanel zoomPanel
KdTree< MapWaypoint > waypointTree
static String getGeolocationMBTilesFilePath()
TileFactoryInfo createOnlineOSMFactory(String address)
BufferedImage whiteWaypointImage
javax.swing.JSlider zoomSlider
void mapViewerMouseClicked(java.awt.event.MouseEvent evt)
void mapViewerMousePressed(java.awt.event.MouseEvent evt)
void mapViewerMouseMoved(java.awt.event.MouseEvent evt)
void drawRoute(Set< MapWaypoint > track, Graphics2D g, JXMapViewer map)
Color getColor(MapWaypoint waypoint)
List< MapWaypoint > findClosestWaypoint(Point clickPoint)
static int getGeolocationtTileOption()
Point getLocationForDetailsPopup()
void actionPerformed(ActionEvent e)
BufferedImage createTrackDotImage(Color color)
TileFactoryInfo createOSMZipFactory(String path)
static String getGeolocationOsmServerAddress()
void paintWaypoint(Graphics2D g, JXMapViewer map, MapWaypoint waypoint)
static void error(String title, String message)
TileFactory getTileFactory()
synchronized static Logger getLogger(String name)
void zoomOutBtnActionPerformed(java.awt.event.ActionEvent evt)
static void addChangeListener(PreferenceChangeListener listener)
ComponentResizeEndListener(int delayMS)
void paint(Graphics2D g, JXMapViewer map, int w, int h)
BufferedImage transparentWaypointImage
Set< MapWaypoint > waypointSet
org.jxmapviewer.JXMapViewer mapViewer
void showPopupMenu(Point point)
MapWaypoint currentlySelectedWaypoint