3a7d794aa5bc28aa47685968cacad5fe7d837049
[public/netxms.git] / webui / webapp / OSM / src / org / netxms / ui / eclipse / osm / widgets / GeoMapViewer.java
1 /**
2 * NetXMS - open source network management system
3 * Copyright (C) 2003-2014 Victor Kirhenshtein
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 package org.netxms.ui.eclipse.osm.widgets;
20
21 import java.awt.Polygon;
22 import java.text.DateFormat;
23 import java.util.ArrayList;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.jobs.Job;
31 import org.eclipse.jface.resource.JFaceResources;
32 import org.eclipse.jface.viewers.ILabelProvider;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.dnd.DND;
35 import org.eclipse.swt.dnd.DragSource;
36 import org.eclipse.swt.dnd.DragSourceEvent;
37 import org.eclipse.swt.dnd.DragSourceListener;
38 import org.eclipse.swt.events.DisposeEvent;
39 import org.eclipse.swt.events.DisposeListener;
40 import org.eclipse.swt.events.MouseEvent;
41 import org.eclipse.swt.events.MouseListener;
42 import org.eclipse.swt.events.MouseMoveListener;
43 import org.eclipse.swt.events.PaintEvent;
44 import org.eclipse.swt.events.PaintListener;
45 import org.eclipse.swt.graphics.Color;
46 import org.eclipse.swt.graphics.Font;
47 import org.eclipse.swt.graphics.GC;
48 import org.eclipse.swt.graphics.Image;
49 import org.eclipse.swt.graphics.Point;
50 import org.eclipse.swt.graphics.RGB;
51 import org.eclipse.swt.graphics.Rectangle;
52 import org.eclipse.swt.widgets.Canvas;
53 import org.eclipse.swt.widgets.Composite;
54 import org.eclipse.swt.widgets.Display;
55 import org.eclipse.swt.widgets.Event;
56 import org.eclipse.swt.widgets.Listener;
57 import org.eclipse.swt.widgets.ToolTip;
58 import org.eclipse.swt.widgets.Widget;
59 import org.eclipse.ui.IViewPart;
60 import org.eclipse.ui.model.WorkbenchLabelProvider;
61 import org.eclipse.ui.presentations.PresentationUtil;
62 import org.netxms.base.GeoLocation;
63 import org.netxms.client.NXCSession;
64 import org.netxms.client.TimePeriod;
65 import org.netxms.client.datacollection.GraphSettings;
66 import org.netxms.client.objects.AbstractObject;
67 import org.netxms.ui.eclipse.console.resources.RegionalSettings;
68 import org.netxms.ui.eclipse.console.resources.SharedColors;
69 import org.netxms.ui.eclipse.console.resources.SharedIcons;
70 import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
71 import org.netxms.ui.eclipse.jobs.ConsoleJob;
72 import org.netxms.ui.eclipse.osm.Activator;
73 import org.netxms.ui.eclipse.osm.GeoLocationCache;
74 import org.netxms.ui.eclipse.osm.GeoLocationCacheListener;
75 import org.netxms.ui.eclipse.osm.Messages;
76 import org.netxms.ui.eclipse.osm.tools.Area;
77 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
78 import org.netxms.ui.eclipse.osm.tools.MapLoader;
79 import org.netxms.ui.eclipse.osm.tools.QuadTree;
80 import org.netxms.ui.eclipse.osm.tools.Tile;
81 import org.netxms.ui.eclipse.osm.tools.TileSet;
82 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
83 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
84 import org.netxms.ui.eclipse.tools.ColorCache;
85 import org.netxms.ui.eclipse.tools.ColorConverter;
86
87 /**
88 * This widget shows map retrieved via OpenStreetMap Static Map API
89 */
90 public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCacheListener, MouseListener, MouseMoveListener
91 {
92 private static final int START = 1;
93 private static final int END = 2;
94 private final String pointInformation[] = {Messages.get().GeoMapViewer_Start, Messages.get().GeoMapViewer_End};
95
96 private final Color MAP_BACKGROUND = new Color(Display.getCurrent(), 255, 255, 255);
97 private final Color INFO_BLOCK_BACKGROUND = new Color(Display.getCurrent(), 0, 0, 0);
98 private final Color INFO_BLOCK_TEXT = new Color(Display.getCurrent(), 255, 255, 255);
99 private final Color LABEL_BACKGROUND = new Color(Display.getCurrent(), 240, 254, 192);
100 private final Color LABEL_TEXT = new Color(Display.getCurrent(), 0, 0, 0);
101 private final Color BORDER_COLOR = new Color(Display.getCurrent(), 0, 0, 0);
102 private final Color ACTIVE_BORDER_COLOR = new Color(Display.getCurrent(), 255, 255, 255);
103 private final Color SELECTION_COLOR = new Color(Display.getCurrent(), 0, 0, 255);
104 private final Color TRACK_COLOR = new Color(Display.getCurrent(), 163, 73, 164);
105
106 private static final Font TITLE_FONT = new Font(Display.getCurrent(), "Verdana", 10, SWT.BOLD); //$NON-NLS-1$
107
108 private static final int LABEL_ARROW_HEIGHT = 5;
109 private static final int LABEL_ARROW_OFFSET = 10;
110 private static final int LABEL_X_MARGIN = 4;
111 private static final int LABEL_Y_MARGIN = 4;
112 private static final int LABEL_SPACING = 4;
113
114 private static final int DRAG_JITTER = 8;
115
116 private ILabelProvider labelProvider;
117 private Area coverage = new Area(0, 0, 0, 0);
118 private List<AbstractObject> objects = new ArrayList<AbstractObject>();
119 private AbstractObject currentObject = null;
120 private MapAccessor accessor;
121 private MapLoader mapLoader;
122 private IViewPart viewPart = null;
123 private Point currentPoint;
124 private Point dragStartPoint = null;
125 private Point selectionStartPoint = null;
126 private Point selectionEndPoint = null;
127 private Set<GeoMapListener> mapListeners = new HashSet<GeoMapListener>(0);
128 private String title = null;
129 private int offsetX;
130 private int offsetY;
131 private TileSet currentTileSet = null;
132 private RAPDragTracker tracker;
133 private Image imageZoomIn;
134 private Image imageZoomOut;
135 private Rectangle zoomControlRect = null;
136 private boolean historicalData;
137 private List<GeoLocation> history = new ArrayList<GeoLocation>();
138 private QuadTree<GeoLocation> locationTree = new QuadTree<GeoLocation>();
139 private AbstractObject historyObject = null;
140 private TimePeriod timePeriod = new TimePeriod();
141 private int highlightobjectID = -1;
142 private ToolTip toolTip;
143 private ColorCache colorCache;
144 private List<ObjectIcon> objectIcons = new ArrayList<ObjectIcon>();
145
146 /**
147 * @param parent
148 * @param style
149 */
150 public GeoMapViewer(Composite parent, int style, final boolean historicalData, AbstractObject historyObject)
151 {
152 super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
153 this.historicalData = historicalData;
154 if (historicalData)
155 {
156 this.historyObject = historyObject;
157 }
158
159 colorCache = new ColorCache(this);
160
161 imageZoomIn = Activator.getImageDescriptor("icons/map_zoom_in.png").createImage(); //$NON-NLS-1$
162 imageZoomOut = Activator.getImageDescriptor("icons/map_zoom_out.png").createImage(); //$NON-NLS-1$
163
164 labelProvider = WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider();
165 mapLoader = new MapLoader(getDisplay());
166
167 setBackground(MAP_BACKGROUND);
168 addPaintListener(this);
169
170 final Runnable timer = new Runnable() {
171 @Override
172 public void run()
173 {
174 if (isDisposed())
175 return;
176
177 reloadMap();
178 }
179 };
180
181 addListener(SWT.Resize, new Listener() {
182 @Override
183 public void handleEvent(Event event)
184 {
185 getDisplay().timerExec(-1, timer);
186 getDisplay().timerExec(1000, timer);
187 }
188 });
189
190 addDisposeListener(new DisposeListener() {
191 @Override
192 public void widgetDisposed(DisposeEvent e)
193 {
194 labelProvider.dispose();
195 GeoLocationCache.getInstance().removeListener(GeoMapViewer.this);
196 mapLoader.dispose();
197 if (currentTileSet != null)
198 {
199 currentTileSet.dispose();
200 currentTileSet = null;
201 }
202 imageZoomIn.dispose();
203 imageZoomOut.dispose();
204 }
205 });
206
207 addMouseListener(this);
208
209 /* the following code is a hack for adding
210 * mouse drag support in RAP. Copied from draw2d
211 * with slight modifications
212 */
213 PresentationUtil.addDragListener(this, new Listener() {
214 private static final long serialVersionUID = 1L;
215
216 @Override
217 public void handleEvent(Event event)
218 {
219 if (event.type == SWT.DragDetect)
220 {
221 MouseEvent me = new MouseEvent(event);
222 me.stateMask = SWT.BUTTON1;
223 GeoMapViewer.this.mouseDown(me);
224 }
225 }
226 });
227 DragSource dragSource = new DragSource(this, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
228 final DragSourceListener listener = new DragSourceListener() {
229 private static final long serialVersionUID = 1L;
230
231 public void dragStart(DragSourceEvent event)
232 {
233 tracker = new RAPDragTracker(GeoMapViewer.this, GeoMapViewer.this);
234 tracker.open();
235 }
236
237 public void dragSetData(DragSourceEvent event)
238 {
239 }
240
241 public void dragFinished(DragSourceEvent event)
242 {
243 if (tracker != null)
244 {
245 tracker.close();
246 tracker = null;
247 }
248 }
249 };
250 dragSource.addDragListener(listener);
251 /* end of mouse drag hack */
252
253 GeoLocationCache.getInstance().addListener(this);
254
255 toolTip = new ToolTip(getShell(), SWT.BALLOON);
256 }
257
258 /**
259 * Add zoom level change listener
260 *
261 * @param listener
262 */
263 public void addMapListener(GeoMapListener listener)
264 {
265 mapListeners.add(listener);
266 }
267
268 /**
269 * Remove previously registered zoom change listener
270 *
271 * @param listener
272 */
273 public void removeMapListener(GeoMapListener listener)
274 {
275 mapListeners.remove(listener);
276 }
277
278 /**
279 * Notify all listeners about zoom level change
280 */
281 private void notifyOnZoomChange()
282 {
283 for(GeoMapListener listener : mapListeners)
284 listener.onZoom(accessor.getZoom());
285 }
286
287 /**
288 * Notify all listeners about position change
289 */
290 private void notifyOnPositionChange()
291 {
292 for(GeoMapListener listener : mapListeners)
293 listener.onPan(accessor.getCenterPoint());
294 }
295
296 /**
297 * Show given map
298 *
299 * @param accessor
300 */
301 public void showMap(MapAccessor accessor)
302 {
303 this.accessor = new MapAccessor(accessor);
304 reloadMap();
305 }
306
307 /**
308 * @param lat
309 * @param lon
310 * @param zoom
311 */
312 public void showMap(double lat, double lon, int zoom)
313 {
314 showMap(new MapAccessor(lat, lon, zoom));
315 }
316
317 /**
318 * Reload current map
319 */
320 private void reloadMap()
321 {
322 Rectangle rect = this.getClientArea();
323 accessor.setMapWidth(rect.width);
324 accessor.setMapHeight(rect.height);
325
326 if (!accessor.isValid())
327 return;
328
329 final Point mapSize = new Point(accessor.getMapWidth(), accessor.getMapHeight());
330 final GeoLocation centerPoint = accessor.getCenterPoint();
331 ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
332 @Override
333 protected void runInternal(IProgressMonitor monitor) throws Exception
334 {
335 final TileSet tiles = mapLoader.getAllTiles(mapSize, centerPoint, MapLoader.CENTER, accessor.getZoom(), true);
336 runInUIThread(new Runnable() {
337 @Override
338 public void run()
339 {
340 if (isDisposed())
341 return;
342
343 if (currentTileSet != null)
344 currentTileSet.dispose();
345 currentTileSet = tiles;
346 if ((tiles != null) && (tiles.missingTiles > 0))
347 loadMissingTiles(tiles);
348
349 Rectangle clientArea = getClientArea();
350 Point mapSize = new Point(clientArea.width, clientArea.height);
351 coverage = GeoLocationCache.calculateCoverage(mapSize, accessor.getCenterPoint(), GeoLocationCache.CENTER, accessor.getZoom());
352 if (!historicalData)
353 {
354 objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
355 GeoMapViewer.this.redraw();
356 }
357 else
358 {
359 GeoMapViewer.this.updateHistory();
360 }
361 }
362 });
363 }
364
365 @Override
366 protected String getErrorMessage()
367 {
368 return Messages.get().GeoMapViewer_DownloadError;
369 }
370 };
371 job.setUser(false);
372 job.start();
373 }
374
375 /**
376 * Load missing tiles in tile set
377 *
378 * @param tiles
379 */
380 private void loadMissingTiles(final TileSet tiles)
381 {
382 ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_LoadMissingJob_Title, viewPart, Activator.PLUGIN_ID, null) {
383 @Override
384 protected void runInternal(IProgressMonitor monitor) throws Exception
385 {
386 mapLoader.loadMissingTiles(tiles, new Runnable() {
387 @Override
388 public void run()
389 {
390 if (!GeoMapViewer.this.isDisposed() && (currentTileSet == tiles))
391 {
392 GeoMapViewer.this.redraw();
393 }
394 }
395 });
396 }
397
398
399 @Override
400 protected IStatus createFailureStatus(Exception e)
401 {
402 e.printStackTrace();
403 return super.createFailureStatus(e);
404 }
405 @Override
406 protected String getErrorMessage()
407 {
408 return Messages.get().GeoMapViewer_DownloadError;
409 }
410 };
411 job.setUser(false);
412 job.start();
413 }
414
415 /**
416 * @param tiles
417 */
418 private void drawTiles(GC gc, TileSet tileSet)
419 {
420 final Tile[][] tiles = tileSet.tiles;
421
422 Point size = getSize();
423
424 int x = tileSet.xOffset;
425 int y = tileSet.yOffset;
426 for(int i = 0; i < tiles.length; i++)
427 {
428 for(int j = 0; j < tiles[i].length; j++)
429 {
430 gc.drawImage(tiles[i][j].getImage(), x, y);
431 x += 256;
432 if (x >= size.x)
433 {
434 x = tileSet.xOffset;
435 y += 256;
436 }
437 }
438 }
439 }
440
441 /*
442 * (non-Javadoc)
443 * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
444 */
445 @Override
446 public void paintControl(PaintEvent e)
447 {
448 final GC gc = e.gc;
449 gc.setAntialias(SWT.ON);
450 gc.setTextAntialias(SWT.ON);
451
452 objectIcons.clear();
453
454 if (currentTileSet != null)
455 drawTiles(gc, currentTileSet);
456
457 GeoLocation currentLocation;
458
459 // Draw objects and decorations if user is not dragging map
460 // and map is not currently loading
461 if (dragStartPoint == null)
462 {
463 currentLocation = accessor.getCenterPoint();
464
465 Rectangle rect = getClientArea();
466
467 final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
468 if (!historicalData)
469 {
470 for(AbstractObject object : objects)
471 {
472 final Point virtualXY = GeoLocationCache.coordinateToDisplay(object.getGeolocation(), accessor.getZoom());
473 final int dx = virtualXY.x - centerXY.x;
474 final int dy = virtualXY.y - centerXY.y;
475 drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, object);
476 }
477 }
478 else
479 {
480 int nextX = 0;
481 int nextY = 0;
482 for(int i = 0; i < history.size(); i++)
483 {
484 final Point virtualXY = GeoLocationCache.coordinateToDisplay(history.get(i), accessor.getZoom());
485 final int dx = virtualXY.x - centerXY.x;
486 final int dy = virtualXY.y - centerXY.y;
487
488 if (i != history.size() - 1)
489 {
490 final Point virtualXY2 = GeoLocationCache.coordinateToDisplay(history.get(i+1), accessor.getZoom());
491 nextX = rect.width / 2 + (virtualXY2.x - centerXY.x);
492 nextY = rect.height / 2 + (virtualXY2.y - centerXY.y);
493 }
494
495 int color = SWT.COLOR_RED;
496 if (i == highlightobjectID)
497 {
498 color = SWT.COLOR_GREEN;
499 DateFormat df = RegionalSettings.getDateTimeFormat();
500 toolTip.setText(String.format("%s\r\n%s - %s",
501 history.get(i), df.format(history.get(i).getTimestamp()), df.format(history.get(i).getEndTimestamp())));
502 toolTip.setVisible(true);
503 }
504
505 if (i == 0)
506 {
507 if (i == history.size() - 1)
508 {
509 nextX = rect.width / 2 + dx;
510 nextY = rect.height / 2 + dy;
511 }
512 drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, GeoMapViewer.START, nextX, nextY, color);
513 continue;
514 }
515
516 if (i == history.size() - 1)
517 {
518 drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, GeoMapViewer.END, nextX, nextY, color);
519 continue;
520 }
521
522 drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, 0, nextX, nextY, color);
523 }
524 }
525 }
526 else
527 {
528 Point cp = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
529 cp.x += offsetX;
530 cp.y += offsetY;
531 currentLocation = GeoLocationCache.displayToCoordinates(cp, accessor.getZoom());
532 }
533
534 // Draw selection rectangle
535 if ((selectionStartPoint != null) && (selectionEndPoint != null))
536 {
537 int x = Math.min(selectionStartPoint.x, selectionEndPoint.x);
538 int y = Math.min(selectionStartPoint.y, selectionEndPoint.y);
539 int w = Math.abs(selectionStartPoint.x - selectionEndPoint.x);
540 int h = Math.abs(selectionStartPoint.y - selectionEndPoint.y);
541 gc.setBackground(SELECTION_COLOR);
542 gc.setForeground(SELECTION_COLOR);
543 gc.setAlpha(64);
544 gc.fillRectangle(x, y, w, h);
545 gc.setAlpha(255);
546 gc.setLineWidth(2);
547 gc.drawRectangle(x, y, w, h);
548 }
549
550 // Draw current location info
551 String text = currentLocation.toString();
552 Point textSize = gc.textExtent(text);
553
554 Rectangle rect = getClientArea();
555 rect.x = rect.width - textSize.x - 20;
556 rect.y += 10;
557 rect.width = textSize.x + 10;
558 rect.height = textSize.y + 8;
559
560 gc.setBackground(INFO_BLOCK_BACKGROUND);
561 gc.setAlpha(128);
562 gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
563 gc.setAlpha(255);
564
565 gc.setForeground(INFO_BLOCK_TEXT);
566 gc.drawText(text, rect.x + 5, rect.y + 4, true);
567
568 // Draw title
569 if ((title != null) && !title.isEmpty())
570 {
571 gc.setFont(TITLE_FONT);
572 rect = getClientArea();
573 int x = (rect.width - gc.textExtent(title).x) / 2;
574 gc.setForeground(SharedColors.getColor(SharedColors.GEOMAP_TITLE, getDisplay()));
575 gc.drawText(title, x, 10, true);
576 }
577
578 // Draw zoom control
579 gc.setFont(JFaceResources.getHeaderFont());
580 text = Integer.toString(accessor.getZoom());
581 textSize = gc.textExtent(text);
582
583 rect = getClientArea();
584 rect.x = 10;
585 rect.y = 10;
586 rect.width = 80;
587 rect.height = 47 + textSize.y;
588
589 gc.setBackground(INFO_BLOCK_BACKGROUND);
590 gc.setAlpha(128);
591 gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
592 gc.setAlpha(255);
593
594 gc.drawText(text, rect.x + rect.width / 2 - textSize.x / 2, rect.y + 5, true);
595 gc.drawImage(imageZoomIn, rect.x + 5, rect.y + rect.height - 37);
596 gc.drawImage(imageZoomOut, rect.x + 42, rect.y + rect.height - 37);
597
598 zoomControlRect = rect;
599 }
600
601 /**
602 * Draw object on map
603 *
604 * @param gc
605 * @param x
606 * @param y
607 * @param object
608 */
609 private void drawObject(GC gc, int x, int y, AbstractObject object)
610 {
611 boolean selected = (currentObject != null) && (currentObject.getObjectId() == object.getObjectId());
612
613 Image image = labelProvider.getImage(object);
614 if (image == null)
615 image = SharedIcons.IMG_UNKNOWN_OBJECT;
616
617 int w = image.getImageData().width + LABEL_X_MARGIN * 2;
618 int h = image.getImageData().height+ LABEL_Y_MARGIN * 2;
619 Rectangle rect = new Rectangle(x - w / 2 - 1, y - LABEL_ARROW_HEIGHT - h, w, h);
620
621 Color bgColor = ColorConverter.adjustColor(StatusDisplayInfo.getStatusColor(object.getStatus()), new RGB(255, 255, 255), 0.5f, colorCache);
622
623 gc.setBackground(bgColor);
624 gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
625 if (selected)
626 {
627 gc.setLineWidth(3);
628 gc.setForeground(ACTIVE_BORDER_COLOR);
629 gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
630 }
631 gc.setLineWidth(1);
632 gc.setForeground(BORDER_COLOR);
633 gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
634
635 final int[] arrow = new int[] { x - 4, rect.y + rect.height, x, y, x + 4, rect.y + rect.height };
636
637 gc.fillPolygon(arrow);
638 gc.setForeground(bgColor);
639 gc.drawPolygon(arrow);
640 if (selected)
641 {
642 gc.drawLine(arrow[0], arrow[1] - 1, arrow[4], arrow[5] - 1);
643 gc.setLineWidth(3);
644 gc.setForeground(ACTIVE_BORDER_COLOR);
645 gc.drawPolyline(arrow);
646 }
647 gc.setLineWidth(1);
648 gc.setForeground(BORDER_COLOR);
649 gc.drawPolyline(arrow);
650
651 gc.drawImage(image, rect.x + LABEL_X_MARGIN, rect.y + LABEL_Y_MARGIN);
652
653 objectIcons.add(new ObjectIcon(object, rect, x, y));
654 }
655
656 /**
657 * Draw object on map
658 *
659 * @param gc
660 * @param x
661 * @param y
662 * @param object
663 */
664 private void drawObject(GC gc, int x, int y, int flag, int prevX, int prevY, int color)
665 {
666 if (flag == GeoMapViewer.START || flag == GeoMapViewer.END)
667 {
668 if (flag == GeoMapViewer.START)
669 {
670 gc.setForeground(TRACK_COLOR);
671 gc.setLineWidth(3);
672 gc.drawLine(x, y, prevX, prevY);
673 }
674
675 gc.setBackground(Display.getCurrent().getSystemColor(color));
676 gc.fillOval(x - 5, y -5, 10, 10);
677
678 final String text = pointInformation[flag -1];
679 final Point textSize = gc.textExtent(text);
680
681 Rectangle rect = new Rectangle(x - LABEL_ARROW_OFFSET, y - LABEL_ARROW_HEIGHT - textSize.y, textSize.x
682 + LABEL_X_MARGIN * 2 + LABEL_SPACING, textSize.y + LABEL_Y_MARGIN * 2);
683
684 gc.setBackground(LABEL_BACKGROUND);
685
686 gc.setForeground(BORDER_COLOR);
687 gc.setLineWidth(4);
688 gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
689 gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
690
691 gc.setLineWidth(2);
692 gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
693 gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
694 final int[] arrow = new int[] { rect.x + LABEL_ARROW_OFFSET - 4, rect.y + rect.height, x, y, rect.x + LABEL_ARROW_OFFSET + 4,
695 rect.y + rect.height };
696
697 gc.setLineWidth(4);
698 gc.setForeground(BORDER_COLOR);
699 gc.drawPolyline(arrow);
700
701 gc.fillPolygon(arrow);
702 gc.setForeground(LABEL_BACKGROUND);
703 gc.setLineWidth(2);
704 gc.drawLine(arrow[0], arrow[1], arrow[4], arrow[5]);
705 gc.drawPolyline(arrow);
706
707 gc.setForeground(LABEL_TEXT);
708 gc.drawText(text, rect.x + LABEL_X_MARGIN + LABEL_SPACING, rect.y + LABEL_Y_MARGIN);
709 }
710 else
711 {
712 gc.setForeground(TRACK_COLOR);
713 gc.setLineWidth(3);
714 gc.drawLine(x, y, prevX, prevY);
715 gc.setBackground(Display.getCurrent().getSystemColor(color));
716 gc.fillOval(x - 5, y -5, 10, 10);
717 }
718 }
719
720 /*
721 * (non-Javadoc)
722 *
723 * @see
724 * org.netxms.ui.eclipse.osm.GeoLocationCacheListener#geoLocationCacheChanged
725 * (org.netxms.client.objects.AbstractObject, org.netxms.client.GeoLocation)
726 */
727 @Override
728 public void geoLocationCacheChanged(final AbstractObject object, final GeoLocation prevLocation)
729 {
730 getDisplay().asyncExec(new Runnable() {
731 @Override
732 public void run()
733 {
734 if (!historicalData)
735 {
736 onCacheChange(object, prevLocation);
737 }
738 else
739 {
740 if (object.getObjectId() == historyObject.getObjectId())
741 onCacheChange(object, prevLocation);
742 }
743 }
744 });
745 }
746
747 /**
748 * Real handler for geolocation cache changes. Must be run in UI thread.
749 *
750 * @param object
751 * @param prevLocation
752 */
753 private void onCacheChange(final AbstractObject object, final GeoLocation prevLocation)
754 {
755 GeoLocation currLocation = object.getGeolocation();
756 if (((currLocation.getType() != GeoLocation.UNSET) &&
757 coverage.contains(currLocation.getLatitude(), currLocation.getLongitude()))
758 || ((prevLocation != null) && (prevLocation.getType() != GeoLocation.UNSET) &&
759 coverage.contains(prevLocation.getLatitude(), prevLocation.getLongitude())))
760 {
761 if (!historicalData)
762 {
763 objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
764 redraw();
765 }
766 else
767 {
768 updateHistory();
769 }
770 }
771 }
772
773 /* (non-Javadoc)
774 * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
775 */
776 @Override
777 public void mouseDoubleClick(MouseEvent e)
778 {
779 }
780
781 /* (non-Javadoc)
782 * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
783 */
784 @Override
785 public void mouseDown(MouseEvent e)
786 {
787 if (e.button == 1) // left button, ignore if map is currently loading
788 {
789 if (zoomControlRect.contains(e.x, e.y))
790 {
791 Rectangle r = new Rectangle(zoomControlRect.x + 5, zoomControlRect.y + zoomControlRect.height - 37, 32, 32);
792 int zoom = accessor.getZoom();
793 if (r.contains(e.x, e.y))
794 {
795 if (zoom < 18)
796 zoom++;
797 }
798 else
799 {
800 r.x += 37;
801 if (r.contains(e.x, e.y))
802 {
803 if (zoom > 1)
804 zoom--;
805 }
806 }
807
808 if (zoom != accessor.getZoom())
809 {
810 accessor.setZoom(zoom);
811 reloadMap();
812 notifyOnZoomChange();
813 }
814 }
815 else if ((e.stateMask & SWT.SHIFT) != 0)
816 {
817 if (accessor.getZoom() < 18)
818 selectionStartPoint = new Point(e.x, e.y);
819 }
820 else
821 {
822 dragStartPoint = new Point(e.x, e.y);
823 setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL));
824 }
825 }
826
827 currentPoint = new Point(e.x, e.y);
828 if (e.button != 1)
829 {
830 AbstractObject object = getObjectAtPoint(currentPoint);
831 if (object != currentObject)
832 setCurrentObject(object);
833 }
834 }
835
836 /* (non-Javadoc)
837 * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
838 */
839 @Override
840 public void mouseUp(MouseEvent e)
841 {
842 if ((e.button == 1) && (dragStartPoint != null))
843 {
844 if (Math.abs(offsetX) > DRAG_JITTER || Math.abs(offsetY) > DRAG_JITTER)
845 {
846 final Point centerXY = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
847 centerXY.x += offsetX;
848 centerXY.y += offsetY;
849 final GeoLocation geoLocation = GeoLocationCache.displayToCoordinates(centerXY, accessor.getZoom());
850 accessor.setLatitude(geoLocation.getLatitude());
851 accessor.setLongitude(geoLocation.getLongitude());
852 reloadMap();
853 notifyOnPositionChange();
854 }
855 offsetX = 0;
856 offsetY = 0;
857 dragStartPoint = null;
858 setCursor(null);
859 }
860 if ((e.button == 1) && (selectionStartPoint != null))
861 {
862 if (selectionEndPoint != null)
863 {
864 int x1 = Math.min(selectionStartPoint.x, selectionEndPoint.x);
865 int x2 = Math.max(selectionStartPoint.x, selectionEndPoint.x);
866 int y1 = Math.min(selectionStartPoint.y, selectionEndPoint.y);
867 int y2 = Math.max(selectionStartPoint.y, selectionEndPoint.y);
868
869 final GeoLocation l1 = getLocationAtPoint(new Point(x1, y1));
870 final GeoLocation l2 = getLocationAtPoint(new Point(x2, y2));
871 final GeoLocation lc = getLocationAtPoint(new Point(x2 - (x2 - x1) / 2, y2 - (y2 - y1) / 2));
872
873 int zoom = accessor.getZoom();
874 while(zoom < 18)
875 {
876 zoom++;
877 final Area area = GeoLocationCache.calculateCoverage(getSize(), lc, GeoLocationCache.CENTER, zoom);
878 if (!area.contains(l1.getLatitude(), l1.getLongitude()) ||
879 !area.contains(l2.getLatitude(), l2.getLongitude()))
880 {
881 zoom--;
882 break;
883 }
884 }
885
886 if (zoom != accessor.getZoom())
887 {
888 accessor.setZoom(zoom);
889 accessor.setLatitude(lc.getLatitude());
890 accessor.setLongitude(lc.getLongitude());
891 reloadMap();
892 notifyOnPositionChange();
893 notifyOnZoomChange();
894 }
895 }
896 selectionStartPoint = null;
897 selectionEndPoint = null;
898 redraw();
899 }
900 }
901
902 /* (non-Javadoc)
903 * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
904 */
905 @Override
906 public void mouseMove(MouseEvent e)
907 {
908 if (dragStartPoint != null)
909 {
910 int deltaX = dragStartPoint.x - e.x;
911 int deltaY = dragStartPoint.y - e.y;
912 if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
913 {
914 offsetX = deltaX;
915 offsetY = deltaY;
916 redraw();
917 }
918 }
919 if (selectionStartPoint != null)
920 {
921 int deltaX = selectionStartPoint.x - e.x;
922 int deltaY = selectionStartPoint.y - e.y;
923 if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
924 {
925 selectionEndPoint = new Point(e.x, e.y);
926 redraw();
927 }
928 }
929 }
930
931 /**
932 * @return the currentPoint
933 */
934 public Point getCurrentPoint()
935 {
936 return new Point(currentPoint.x, currentPoint.y);
937 }
938
939 /**
940 * Get location at given point within widget
941 *
942 * @param p widget-related coordinates
943 * @return location (latitude/longitude) at given point
944 */
945 public GeoLocation getLocationAtPoint(Point p)
946 {
947 Point cp = GeoLocationCache.coordinateToDisplay(new GeoLocation(coverage.getxHigh(), coverage.getyLow()), accessor.getZoom());
948 return GeoLocationCache.displayToCoordinates(new Point(cp.x + p.x, cp.y + p.y), accessor.getZoom());
949 }
950
951 /**
952 * Get object at given point within widget
953 *
954 * @param p widget-related coordinates
955 * @return object at given point or null
956 */
957 public AbstractObject getObjectAtPoint(Point p)
958 {
959 for(int i = objectIcons.size() - 1; i >= 0; i--)
960 {
961 ObjectIcon icon = objectIcons.get(i);
962 if (icon.contains(p))
963 return icon.object;
964 }
965 return null;
966 }
967
968 /**
969 * @return the viewPart
970 */
971 public IViewPart getViewPart()
972 {
973 return viewPart;
974 }
975
976 /**
977 * @param viewPart the viewPart to set
978 */
979 public void setViewPart(IViewPart viewPart)
980 {
981 this.viewPart = viewPart;
982 }
983
984 /**
985 * @return the title
986 */
987 public String getTitle()
988 {
989 return title;
990 }
991
992 /**
993 * @param title the title to set
994 */
995 public void setTitle(String title)
996 {
997 this.title = title;
998 }
999
1000 /**
1001 * Drag tracker - copied from draw2d
1002 *
1003 */
1004 protected class RAPDragTracker
1005 {
1006 public boolean cancelled;
1007 public boolean tracking;
1008 private final MouseMoveListener listener;
1009 private final Widget widget;
1010
1011 public RAPDragTracker(final MouseMoveListener listener, final Widget widget)
1012 {
1013 this.listener = listener;
1014 this.widget = widget;
1015 }
1016
1017 public void open()
1018 {
1019 Job dragJob = new Job("Drag-Job") {
1020 protected IStatus run(IProgressMonitor monitor)
1021 {
1022 // Run tracker until mouse up occurs or escape key pressed.
1023 final Display display = widget.getDisplay();
1024 cancelled = false;
1025 tracking = true;
1026
1027 try
1028 {
1029 long timeout = 0;
1030 long refreshRate = 200;
1031 while(tracking && !cancelled)
1032 {
1033 if (!display.isDisposed())
1034 {
1035 display.syncExec(new Runnable()
1036 {
1037 public void run()
1038 {
1039 if (GeoMapViewer.this.isDisposed())
1040 {
1041 tracking = false;
1042 cancelled = true;
1043 return;
1044 }
1045 Event ev = new Event();
1046 ev.display = display;
1047 Point loc = GeoMapViewer.this.toControl(display.getCursorLocation());
1048 ev.type = SWT.DragDetect;
1049 ev.widget = widget;
1050 ev.button = 1;
1051 ev.x = loc.x;
1052 ev.y = loc.y;
1053 MouseEvent me = new MouseEvent(ev);
1054 me.stateMask = SWT.BUTTON1;
1055 listener.mouseMove(me);
1056 }
1057 });
1058 timeout += refreshRate;
1059 if (timeout >= 60000)
1060 {
1061 cancelled = true;
1062 }
1063 Thread.sleep(refreshRate);
1064 }
1065
1066 }
1067 }
1068 catch(InterruptedException e)
1069 {
1070 e.printStackTrace();
1071 }
1072 finally
1073 {
1074 display.syncExec(new Runnable()
1075 {
1076 public void run()
1077 {
1078 close();
1079 }
1080 });
1081 }
1082 return Status.OK_STATUS;
1083 }
1084 };
1085 dragJob.setSystem(true);
1086 dragJob.schedule();
1087 }
1088
1089 public void close()
1090 {
1091 tracking = false;
1092 }
1093 }
1094
1095 /**
1096 * Updates points for historical view
1097 */
1098 private void updateHistory()
1099 {
1100 final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
1101 ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
1102 @Override
1103 protected void runInternal(IProgressMonitor monitor) throws Exception
1104 {
1105 history = session.getLocationHistory(historyObject.getObjectId(), timePeriod.getPeriodStart(), timePeriod.getPeriodEnd());
1106 for(int i = 0; i < history.size(); i++)
1107 locationTree.insert(history.get(i).getLatitude(), history.get(i).getLongitude(), history.get(i));
1108
1109 runInUIThread(new Runnable() {
1110 @Override
1111 public void run()
1112 {
1113 GeoMapViewer.this.redraw();
1114 }
1115 });
1116 }
1117
1118 @Override
1119 protected String getErrorMessage()
1120 {
1121 return Messages.get().GeoMapViewer_DownloadError;
1122 }
1123 };
1124 job.setUser(false);
1125 job.start();
1126 }
1127
1128 /**
1129 * Sets new time period
1130 */
1131 public void setTimePeriod(TimePeriod timePeriod)
1132 {
1133 this.timePeriod = timePeriod;
1134 updateHistory();
1135 }
1136
1137 /**
1138 * Gets time period
1139 */
1140 public TimePeriod getTimePeriod()
1141 {
1142 return timePeriod;
1143 }
1144
1145 /**
1146 * @param value
1147 * @param unit
1148 */
1149 public void changeTimePeriod(int value, int unit)
1150 {
1151 timePeriod.setTimeFrameType(GraphSettings.TIME_FRAME_BACK_FROM_NOW);
1152 timePeriod.setTimeRangeValue(value);
1153 timePeriod.setTimeUnitValue(unit);
1154 updateHistory();
1155 }
1156
1157 /**
1158 * Set current object
1159 *
1160 * @param object
1161 */
1162 private void setCurrentObject(AbstractObject object)
1163 {
1164 currentObject = object;
1165 if (currentObject != null)
1166 {
1167 int idx = objects.indexOf(currentObject);
1168 objects.remove(idx);
1169 objects.add(currentObject);
1170 }
1171 redraw();
1172 }
1173
1174 /**
1175 * Object icon on map
1176 */
1177 private class ObjectIcon
1178 {
1179 public Rectangle rect;
1180 public Polygon arrow;
1181 public AbstractObject object;
1182
1183 public ObjectIcon(AbstractObject object, Rectangle rect, int x, int y)
1184 {
1185 this.rect = rect;
1186 this.object = object;
1187 arrow = new Polygon();
1188 arrow.addPoint(x, y);
1189 arrow.addPoint(x - 4, rect.y + rect.height);
1190 arrow.addPoint(x + 4, rect.y + rect.height);
1191 }
1192
1193 public boolean contains(Point p)
1194 {
1195 return rect.contains(p) || arrow.contains(p.x, p.y);
1196 }
1197 }
1198 }