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