Commit 64ce9ba6e52557317eff516fd836701e40d19ad9

Authored by Maksym Dudnik
1 parent d35d302a

Trip animation widget improvement:

-Added polygons
-Included polygon tooltip functional(Standalone text)
@@ -21,181 +21,181 @@ import tinycolor from "tinycolor2"; @@ -21,181 +21,181 @@ import tinycolor from "tinycolor2";
21 import {fillPatternWithActions, isNumber, padValue, processPattern} from "../widget-utils"; 21 import {fillPatternWithActions, isNumber, padValue, processPattern} from "../widget-utils";
22 22
23 (function () { 23 (function () {
24 - // save these original methods before they are overwritten  
25 - var proto_initIcon = L.Marker.prototype._initIcon;  
26 - var proto_setPos = L.Marker.prototype._setPos;  
27 -  
28 - var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');  
29 -  
30 - L.Marker.addInitHook(function () {  
31 - var iconOptions = this.options.icon && this.options.icon.options;  
32 - var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;  
33 - if (iconAnchor) {  
34 - iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');  
35 - }  
36 - this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom';  
37 - this.options.rotationAngle = this.options.rotationAngle || 0;  
38 -  
39 - // Ensure marker keeps rotated during dragging  
40 - this.on('drag', function (e) {  
41 - e.target._applyRotation();  
42 - });  
43 - });  
44 -  
45 - L.Marker.include({  
46 - _initIcon: function () {  
47 - proto_initIcon.call(this);  
48 - },  
49 -  
50 - _setPos: function (pos) {  
51 - proto_setPos.call(this, pos);  
52 - this._applyRotation();  
53 - },  
54 -  
55 - _applyRotation: function () {  
56 - if (this.options.rotationAngle) {  
57 - this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin;  
58 -  
59 - if (oldIE) {  
60 - // for IE 9, use the 2D rotation  
61 - this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';  
62 - } else {  
63 - // for modern browsers, prefer the 3D accelerated version  
64 - let rotation = ' rotateZ(' + this.options.rotationAngle + 'deg)';  
65 - if (!this._icon.style[L.DomUtil.TRANSFORM].includes(rotation)) {  
66 - this._icon.style[L.DomUtil.TRANSFORM] += rotation;  
67 - }  
68 - }  
69 - }  
70 - },  
71 -  
72 - setRotationAngle: function (angle) {  
73 - this.options.rotationAngle = angle;  
74 - this.update();  
75 - return this;  
76 - },  
77 -  
78 - setRotationOrigin: function (origin) {  
79 - this.options.rotationOrigin = origin;  
80 - this.update();  
81 - return this;  
82 - }  
83 - }); 24 + // save these original methods before they are overwritten
  25 + var proto_initIcon = L.Marker.prototype._initIcon;
  26 + var proto_setPos = L.Marker.prototype._setPos;
  27 +
  28 + var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
  29 +
  30 + L.Marker.addInitHook(function () {
  31 + var iconOptions = this.options.icon && this.options.icon.options;
  32 + var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
  33 + if (iconAnchor) {
  34 + iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
  35 + }
  36 + this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom';
  37 + this.options.rotationAngle = this.options.rotationAngle || 0;
  38 +
  39 + // Ensure marker keeps rotated during dragging
  40 + this.on('drag', function (e) {
  41 + e.target._applyRotation();
  42 + });
  43 + });
  44 +
  45 + L.Marker.include({
  46 + _initIcon: function () {
  47 + proto_initIcon.call(this);
  48 + },
  49 +
  50 + _setPos: function (pos) {
  51 + proto_setPos.call(this, pos);
  52 + this._applyRotation();
  53 + },
  54 +
  55 + _applyRotation: function () {
  56 + if (this.options.rotationAngle) {
  57 + this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin;
  58 +
  59 + if (oldIE) {
  60 + // for IE 9, use the 2D rotation
  61 + this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
  62 + } else {
  63 + // for modern browsers, prefer the 3D accelerated version
  64 + let rotation = ' rotateZ(' + this.options.rotationAngle + 'deg)';
  65 + if (!this._icon.style[L.DomUtil.TRANSFORM].includes(rotation)) {
  66 + this._icon.style[L.DomUtil.TRANSFORM] += rotation;
  67 + }
  68 + }
  69 + }
  70 + },
  71 +
  72 + setRotationAngle: function (angle) {
  73 + this.options.rotationAngle = angle;
  74 + this.update();
  75 + return this;
  76 + },
  77 +
  78 + setRotationOrigin: function (origin) {
  79 + this.options.rotationOrigin = origin;
  80 + this.update();
  81 + return this;
  82 + }
  83 + });
84 })(); 84 })();
85 85
86 86
87 export default angular.module('thingsboard.widgets.tripAnimation', []) 87 export default angular.module('thingsboard.widgets.tripAnimation', [])
88 - .directive('tripAnimation', tripAnimationWidget)  
89 - .filter('tripAnimation', function ($filter) {  
90 - return function (label) {  
91 - label = label.toString(); 88 + .directive('tripAnimation', tripAnimationWidget)
  89 + .filter('tripAnimation', function ($filter) {
  90 + return function (label) {
  91 + label = label.toString();
92 92
93 - let translateSelector = "widgets.tripAnimation." + label;  
94 - let translation = $filter('translate')(translateSelector); 93 + let translateSelector = "widgets.tripAnimation." + label;
  94 + let translation = $filter('translate')(translateSelector);
95 95
96 - if (translation !== translateSelector) {  
97 - return translation;  
98 - } 96 + if (translation !== translateSelector) {
  97 + return translation;
  98 + }
99 99
100 - return label;  
101 - }  
102 - })  
103 - .name; 100 + return label;
  101 + }
  102 + })
  103 + .name;
104 104
105 105
106 /*@ngInject*/ 106 /*@ngInject*/
107 function tripAnimationWidget() { 107 function tripAnimationWidget() {
108 - return {  
109 - restrict: "E",  
110 - scope: true,  
111 - bindToController: {  
112 - ctx: '=',  
113 - self: '='  
114 - },  
115 - controller: tripAnimationController,  
116 - controllerAs: 'vm',  
117 - templateUrl: template  
118 - }; 108 + return {
  109 + restrict: "E",
  110 + scope: true,
  111 + bindToController: {
  112 + ctx: '=',
  113 + self: '='
  114 + },
  115 + controller: tripAnimationController,
  116 + controllerAs: 'vm',
  117 + templateUrl: template
  118 + };
119 } 119 }
120 120
121 /*@ngInject*/ 121 /*@ngInject*/
122 -function tripAnimationController($document, $scope, $http, $timeout, $filter, $sce) {  
123 - let vm = this;  
124 -  
125 - vm.initBounds = true;  
126 -  
127 - vm.markers = [];  
128 - vm.index = 0;  
129 - vm.dsIndex = 0;  
130 - vm.minTime = 0;  
131 - vm.maxTime = 0;  
132 - vm.isPlaying = false;  
133 - vm.trackingLine = {  
134 - "type": "FeatureCollection",  
135 - features: []  
136 - };  
137 - vm.speeds = [1, 5, 10, 25];  
138 - vm.speed = 1;  
139 - vm.trips = [];  
140 - vm.activeTripIndex = 0;  
141 -  
142 - vm.showHideTooltip = showHideTooltip;  
143 - vm.recalculateTrips = recalculateTrips;  
144 -  
145 - $scope.$watch('vm.ctx', function () {  
146 - if (vm.ctx) {  
147 - vm.utils = vm.ctx.$scope.$injector.get('utils');  
148 - vm.settings = vm.ctx.settings;  
149 - vm.widgetConfig = vm.ctx.widgetConfig;  
150 - vm.data = vm.ctx.data;  
151 - vm.datasources = vm.ctx.datasources;  
152 - configureStaticSettings();  
153 - initialize();  
154 - initializeCallbacks();  
155 - }  
156 - });  
157 -  
158 -  
159 - function initializeCallbacks() {  
160 - vm.self.onDataUpdated = function () {  
161 - createUpdatePath(true);  
162 - };  
163 -  
164 - vm.self.onResize = function () {  
165 - resize();  
166 - };  
167 -  
168 - vm.self.typeParameters = function () {  
169 - return {  
170 - maxDatasources: 1, // Maximum allowed datasources for this widget, -1 - unlimited  
171 - maxDataKeys: -1 //Maximum allowed data keys for this widget, -1 - unlimited  
172 - }  
173 - };  
174 - return true;  
175 - }  
176 -  
177 -  
178 - function resize() {  
179 - if (vm.map) {  
180 - vm.map.invalidateSize();  
181 - }  
182 - }  
183 -  
184 - function initCallback() {  
185 - //createUpdatePath();  
186 - //resize();  
187 - }  
188 -  
189 - vm.playMove = function (play) {  
190 - if (play && vm.isPlaying) return;  
191 - if (play || vm.isPlaying) vm.isPlaying = true;  
192 - if (vm.isPlaying) { 122 +function tripAnimationController($document, $scope, $log, $http, $timeout, $filter, $sce) {
  123 + let vm = this;
  124 +
  125 + vm.initBounds = true;
  126 +
  127 + vm.markers = [];
  128 + vm.index = 0;
  129 + vm.dsIndex = 0;
  130 + vm.minTime = 0;
  131 + vm.maxTime = 0;
  132 + vm.isPlaying = false;
  133 + vm.trackingLine = {
  134 + "type": "FeatureCollection",
  135 + features: []
  136 + };
  137 + vm.speeds = [1, 5, 10, 25];
  138 + vm.speed = 1;
  139 + vm.trips = [];
  140 + vm.activeTripIndex = 0;
  141 +
  142 + vm.showHideTooltip = showHideTooltip;
  143 + vm.recalculateTrips = recalculateTrips;
  144 +
  145 + $scope.$watch('vm.ctx', function () {
  146 + if (vm.ctx) {
  147 + vm.utils = vm.ctx.$scope.$injector.get('utils');
  148 + vm.settings = vm.ctx.settings;
  149 + vm.widgetConfig = vm.ctx.widgetConfig;
  150 + vm.data = vm.ctx.data;
  151 + vm.datasources = vm.ctx.datasources;
  152 + configureStaticSettings();
  153 + initialize();
  154 + initializeCallbacks();
  155 + }
  156 + });
  157 +
  158 +
  159 + function initializeCallbacks() {
  160 + vm.self.onDataUpdated = function () {
  161 + createUpdatePath(true);
  162 + };
  163 +
  164 + vm.self.onResize = function () {
  165 + resize();
  166 + };
  167 +
  168 + vm.self.typeParameters = function () {
  169 + return {
  170 + maxDatasources: 1, // Maximum allowed datasources for this widget, -1 - unlimited
  171 + maxDataKeys: -1 //Maximum allowed data keys for this widget, -1 - unlimited
  172 + }
  173 + };
  174 + return true;
  175 + }
  176 +
  177 +
  178 + function resize() {
  179 + if (vm.map) {
  180 + vm.map.invalidateSize();
  181 + }
  182 + }
  183 +
  184 + function initCallback() {
  185 + //createUpdatePath();
  186 + //resize();
  187 + }
  188 +
  189 + vm.playMove = function (play) {
  190 + if (play && vm.isPlaying) return;
  191 + if (play || vm.isPlaying) vm.isPlaying = true;
  192 + if (vm.isPlaying) {
193 moveInc(1); 193 moveInc(1);
194 - vm.timeout = $timeout(function () {  
195 - vm.playMove();  
196 - }, 1000 / vm.speed)  
197 - }  
198 - }; 194 + vm.timeout = $timeout(function () {
  195 + vm.playMove();
  196 + }, 1000 / vm.speed)
  197 + }
  198 + };
199 199
200 vm.moveNext = function () { 200 vm.moveNext = function () {
201 vm.stopPlay(); 201 vm.stopPlay();
@@ -217,12 +217,12 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s @@ -217,12 +217,12 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s
217 moveToIndex(vm.maxTime); 217 moveToIndex(vm.maxTime);
218 } 218 }
219 219
220 - vm.stopPlay = function () { 220 + vm.stopPlay = function () {
221 if (vm.isPlaying) { 221 if (vm.isPlaying) {
222 vm.isPlaying = false; 222 vm.isPlaying = false;
223 $timeout.cancel(vm.timeout); 223 $timeout.cancel(vm.timeout);
224 } 224 }
225 - }; 225 + };
226 226
227 function moveInc(inc) { 227 function moveInc(inc) {
228 let newIndex = vm.index + inc; 228 let newIndex = vm.index + inc;
@@ -235,436 +235,504 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s @@ -235,436 +235,504 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s
235 recalculateTrips(); 235 recalculateTrips();
236 } 236 }
237 237
238 - function recalculateTrips() {  
239 - vm.trips.forEach(function (value) {  
240 - moveMarker(value);  
241 - })  
242 - }  
243 -  
244 - function findAngle(lat1, lng1, lat2, lng2) {  
245 - let angle = Math.atan2(0, 0) - Math.atan2(lat2 - lat1, lng2 - lng1);  
246 - angle = angle * 180 / Math.PI;  
247 - return parseInt(angle.toFixed(2));  
248 - }  
249 -  
250 - function initialize() {  
251 - $scope.currentDate = $filter('date')(0, "yyyy.MM.dd HH:mm:ss");  
252 -  
253 - vm.self.actionSources = [vm.searchAction];  
254 - vm.endpoint = vm.ctx.settings.endpointUrl;  
255 - $scope.title = vm.ctx.widgetConfig.title;  
256 - vm.utils = vm.self.ctx.$scope.$injector.get('utils');  
257 -  
258 - vm.showTimestamp = vm.settings.showTimestamp !== false;  
259 - vm.ctx.$element = angular.element("#trip-animation-map", vm.ctx.$container);  
260 - vm.defaultZoomLevel = 2;  
261 - if (vm.ctx.settings.defaultZoomLevel) {  
262 - if (vm.ctx.settings.defaultZoomLevel > 0 && vm.ctx.settings.defaultZoomLevel < 21) {  
263 - vm.defaultZoomLevel = Math.floor(vm.ctx.settings.defaultZoomLevel);  
264 - }  
265 - }  
266 - vm.dontFitMapBounds = vm.ctx.settings.fitMapBounds === false;  
267 - vm.map = new TbOpenStreetMap(vm.ctx.$element, vm.utils, initCallback, vm.defaultZoomLevel, vm.dontFitMapBounds, null, vm.staticSettings.mapProvider);  
268 - vm.map.bounds = vm.map.createBounds();  
269 - vm.map.invalidateSize(true);  
270 - vm.map.bounds = vm.map.createBounds();  
271 -  
272 - vm.tooltipActionsMap = {};  
273 - var descriptors = vm.ctx.actionsApi.getActionDescriptors('tooltipAction');  
274 - descriptors.forEach(function (descriptor) {  
275 - if (descriptor) vm.tooltipActionsMap[descriptor.name] = descriptor;  
276 - });  
277 - }  
278 -  
279 - function configureStaticSettings() {  
280 - let staticSettings = {};  
281 - vm.staticSettings = staticSettings;  
282 - //Calculate General Settings 238 + function recalculateTrips() {
  239 + vm.trips.forEach(function (value) {
  240 + moveMarker(value);
  241 + })
  242 + }
  243 +
  244 + function findAngle(lat1, lng1, lat2, lng2) {
  245 + let angle = Math.atan2(0, 0) - Math.atan2(lat2 - lat1, lng2 - lng1);
  246 + angle = angle * 180 / Math.PI;
  247 + return parseInt(angle.toFixed(2));
  248 + }
  249 +
  250 + function initialize() {
  251 + $scope.currentDate = $filter('date')(0, "yyyy.MM.dd HH:mm:ss");
  252 +
  253 + vm.self.actionSources = [vm.searchAction];
  254 + vm.endpoint = vm.ctx.settings.endpointUrl;
  255 + $scope.title = vm.ctx.widgetConfig.title;
  256 + vm.utils = vm.self.ctx.$scope.$injector.get('utils');
  257 +
  258 + vm.showTimestamp = vm.settings.showTimestamp !== false;
  259 + vm.ctx.$element = angular.element("#trip-animation-map", vm.ctx.$container);
  260 + vm.defaultZoomLevel = 2;
  261 + if (vm.ctx.settings.defaultZoomLevel) {
  262 + if (vm.ctx.settings.defaultZoomLevel > 0 && vm.ctx.settings.defaultZoomLevel < 21) {
  263 + vm.defaultZoomLevel = Math.floor(vm.ctx.settings.defaultZoomLevel);
  264 + }
  265 + }
  266 + vm.dontFitMapBounds = vm.ctx.settings.fitMapBounds === false;
  267 + vm.map = new TbOpenStreetMap(vm.ctx.$element, vm.utils, initCallback, vm.defaultZoomLevel, vm.dontFitMapBounds, null, vm.staticSettings.mapProvider);
  268 + vm.map.bounds = vm.map.createBounds();
  269 + vm.map.invalidateSize(true);
  270 + vm.map.bounds = vm.map.createBounds();
  271 +
  272 + vm.tooltipActionsMap = {};
  273 + var descriptors = vm.ctx.actionsApi.getActionDescriptors('tooltipAction');
  274 + descriptors.forEach(function (descriptor) {
  275 + if (descriptor) vm.tooltipActionsMap[descriptor.name] = descriptor;
  276 + });
  277 + }
  278 +
  279 + function configureStaticSettings() {
  280 + let staticSettings = {};
  281 + vm.staticSettings = staticSettings;
  282 + //Calculate General Settings
283 staticSettings.buttonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.54).toRgbString(); 283 staticSettings.buttonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.54).toRgbString();
284 staticSettings.disabledButtonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.3).toRgbString(); 284 staticSettings.disabledButtonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.3).toRgbString();
285 - staticSettings.mapProvider = vm.ctx.settings.mapProvider || "OpenStreetMap.Mapnik";  
286 - staticSettings.latKeyName = vm.ctx.settings.latKeyName || "latitude";  
287 - staticSettings.lngKeyName = vm.ctx.settings.lngKeyName || "longitude";  
288 - staticSettings.rotationAngle = vm.ctx.settings.rotationAngle || 0;  
289 - staticSettings.displayTooltip = vm.ctx.settings.showTooltip || false;  
290 - staticSettings.defaultZoomLevel = vm.ctx.settings.defaultZoomLevel || true;  
291 - staticSettings.showTooltip = false;  
292 - staticSettings.label = vm.ctx.settings.label || "${entityName}";  
293 - staticSettings.useLabelFunction = vm.ctx.settings.useLabelFunction || false;  
294 - staticSettings.showLabel = vm.ctx.settings.showLabel || false;  
295 - staticSettings.useTooltipFunction = vm.ctx.settings.useTooltipFunction || false;  
296 - staticSettings.tooltipPattern = vm.ctx.settings.tooltipPattern || "<span style=\"font-size: 26px; color: #666; font-weight: bold;\">${entityName}</span>\n" + 285 + staticSettings.polygonColor = tinycolor(vm.ctx.settings.polygonColor).toHexString();
  286 + staticSettings.polygonStrokeColor = tinycolor(vm.ctx.settings.polygonStrokeColor).toHexString();
  287 + staticSettings.mapProvider = vm.ctx.settings.mapProvider || "OpenStreetMap.Mapnik";
  288 + staticSettings.latKeyName = vm.ctx.settings.latKeyName || "latitude";
  289 + staticSettings.lngKeyName = vm.ctx.settings.lngKeyName || "longitude";
  290 + staticSettings.polKeyName = vm.ctx.settings.polKeyName || "coordinates";
  291 + staticSettings.rotationAngle = vm.ctx.settings.rotationAngle || 0;
  292 + staticSettings.polygonOpacity = vm.ctx.settings.polygonOpacity || 0.5;
  293 + staticSettings.polygonStrokeOpacity = vm.ctx.settings.polygonStrokeOpacity || 1;
  294 + staticSettings.polygonStrokeWeight = vm.ctx.settings.polygonStrokeWeight || 1;
  295 + staticSettings.showPolygon = vm.ctx.settings.showPolygon || false;
  296 + staticSettings.usePolygonColorFunction = vm.ctx.settings.usePolygonColorFunction || false;
  297 + staticSettings.usePolygonTooltipFunction = vm.ctx.settings.usePolygonTooltipFunction || false;
  298 + staticSettings.displayTooltip = vm.ctx.settings.showTooltip || false;
  299 + staticSettings.defaultZoomLevel = vm.ctx.settings.defaultZoomLevel || true;
  300 + staticSettings.showTooltip = false;
  301 + staticSettings.label = vm.ctx.settings.label || "${entityName}";
  302 + staticSettings.useLabelFunction = vm.ctx.settings.useLabelFunction || false;
  303 + staticSettings.showLabel = vm.ctx.settings.showLabel || false;
  304 + staticSettings.useTooltipFunction = vm.ctx.settings.useTooltipFunction || false;
  305 + staticSettings.tooltipPattern = vm.ctx.settings.tooltipPattern || "<span style=\"font-size: 26px; color: #666; font-weight: bold;\">${entityName}</span>\n" +
297 "<br/>\n" + 306 "<br/>\n" +
298 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Time:</span><span style=\"font-size: 12px;\"> ${formattedTs}</span>\n" + 307 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Time:</span><span style=\"font-size: 12px;\"> ${formattedTs}</span>\n" +
299 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Latitude:</span> ${latitude:7}\n" + 308 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Latitude:</span> ${latitude:7}\n" +
300 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Longitude:</span> ${longitude:7}"; 309 "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Longitude:</span> ${longitude:7}";
301 - staticSettings.tooltipOpacity = angular.isNumber(vm.ctx.settings.tooltipOpacity) ? vm.ctx.settings.tooltipOpacity : 1;  
302 - staticSettings.tooltipColor = vm.ctx.settings.tooltipColor ? tinycolor(vm.ctx.settings.tooltipColor).toRgbString() : "#ffffff";  
303 - staticSettings.tooltipFontColor = vm.ctx.settings.tooltipFontColor ? tinycolor(vm.ctx.settings.tooltipFontColor).toRgbString() : "#000000";  
304 - staticSettings.pathColor = vm.ctx.settings.color ? tinycolor(vm.ctx.settings.color).toHexString() : "#ff6300";  
305 - staticSettings.pathWeight = vm.ctx.settings.strokeWeight || 1;  
306 - staticSettings.pathOpacity = vm.ctx.settings.strokeOpacity || 1;  
307 - staticSettings.usePathColorFunction = vm.ctx.settings.useColorFunction || false;  
308 - staticSettings.showPoints = vm.ctx.settings.showPoints || false;  
309 - staticSettings.pointSize = vm.ctx.settings.pointSize || 1;  
310 - staticSettings.markerImageSize = vm.ctx.settings.markerImageSize || 20;  
311 - staticSettings.useMarkerImageFunction = vm.ctx.settings.useMarkerImageFunction || false;  
312 - staticSettings.pointColor = vm.ctx.settings.pointColor ? tinycolor(vm.ctx.settings.pointColor).toHexString() : "#ff6300";  
313 - staticSettings.markerImages = vm.ctx.settings.markerImages || [];  
314 - staticSettings.icon = L.icon({  
315 - iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKoAAACqCAYAAAA9dtSCAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY7LDcAwCEPvTNERCBA+40RVI3WDjl+iNMqp7wCWBZbheu4Ox6AggVRzDVVMJCSopXCcMGIhLGPnnHybSyraNjBNoeGGsg/l8xeV1bWbmGnVU0/KdLqY2HPmH4xUHDVih7S2Gv34q8ULVzos2Vmq5r4AAAoGaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMTcwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMTcwIgogICB0aWZmOkltYWdlV2lkdGg9IjE3MCIKICAgdGlmZjpJbWFnZUhlaWdodD0iMTcwIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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",  
316 - iconSize: [30, 30],  
317 - iconAnchor: [15, 15]  
318 - });  
319 - if (angular.isDefined(vm.ctx.settings.markerImage)) {  
320 - staticSettings.icon = L.icon({  
321 - iconUrl: vm.ctx.settings.markerImage,  
322 - iconSize: [staticSettings.markerImageSize, staticSettings.markerImageSize],  
323 - iconAnchor: [(staticSettings.markerImageSize / 2), (staticSettings.markerImageSize / 2)]  
324 - })  
325 - }  
326 -  
327 - if (staticSettings.usePathColorFunction && angular.isDefined(vm.ctx.settings.colorFunction)) {  
328 - staticSettings.colorFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.colorFunction);  
329 - }  
330 -  
331 - if (staticSettings.useLabelFunction && angular.isDefined(vm.ctx.settings.labelFunction)) {  
332 - staticSettings.labelFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.labelFunction);  
333 - }  
334 -  
335 - if (staticSettings.useTooltipFunction && angular.isDefined(vm.ctx.settings.tooltipFunction)) {  
336 - staticSettings.tooltipFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.tooltipFunction);  
337 - }  
338 -  
339 - if (staticSettings.useMarkerImageFunction && angular.isDefined(vm.ctx.settings.markerImageFunction)) {  
340 - staticSettings.markerImageFunction = new Function('data, images, dsData, dsIndex', vm.ctx.settings.markerImageFunction);  
341 - }  
342 -  
343 - if (!staticSettings.useMarkerImageFunction &&  
344 - angular.isDefined(vm.ctx.settings.markerImage) &&  
345 - vm.ctx.settings.markerImage.length > 0) {  
346 - staticSettings.useMarkerImage = true;  
347 - let url = vm.ctx.settings.markerImage;  
348 - let size = staticSettings.markerImageSize || 20;  
349 - staticSettings.currentImage = {  
350 - url: url,  
351 - size: size  
352 - };  
353 - vm.utils.loadImageAspect(staticSettings.currentImage.url).then(  
354 - (aspect) => {  
355 - if (aspect) {  
356 - let width;  
357 - let height;  
358 - if (aspect > 1) {  
359 - width = staticSettings.currentImage.size;  
360 - height = staticSettings.currentImage.size / aspect;  
361 - } else {  
362 - width = staticSettings.currentImage.size * aspect;  
363 - height = staticSettings.currentImage.size;  
364 - }  
365 - staticSettings.icon = L.icon({  
366 - iconUrl: staticSettings.currentImage.url,  
367 - iconSize: [width, height],  
368 - iconAnchor: [width / 2, height / 2]  
369 - });  
370 - }  
371 - if (vm.trips) {  
372 - vm.trips.forEach(function (trip) {  
373 - if (trip.marker) {  
374 - trip.marker.setIcon(staticSettings.icon);  
375 - }  
376 - });  
377 - }  
378 - }  
379 - )  
380 - }  
381 - }  
382 -  
383 - function configureTripSettings(trip, index, apply) {  
384 - trip.settings = {};  
385 - trip.settings.color = calculateColor(trip);  
386 - trip.settings.strokeWeight = vm.staticSettings.pathWeight;  
387 - trip.settings.strokeOpacity = vm.staticSettings.pathOpacity;  
388 - trip.settings.pointColor = vm.staticSettings.pointColor;  
389 - trip.settings.pointSize = vm.staticSettings.pointSize;  
390 - trip.settings.icon = calculateIcon(trip);  
391 - if (apply) { 310 + staticSettings.polygonTooltipPattern = vm.ctx.settings.polygonTooltipPattern || "<span style=\"font-size: 26px; color: #666; font-weight: bold;\">${entityName}</span>\n" +
  311 + "<br/>\n" +
  312 + "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Time:</span><span style=\"font-size: 12px;\"> ${formattedTs}</span>\n";
  313 + staticSettings.tooltipOpacity = angular.isNumber(vm.ctx.settings.tooltipOpacity) ? vm.ctx.settings.tooltipOpacity : 1;
  314 + staticSettings.tooltipColor = vm.ctx.settings.tooltipColor ? tinycolor(vm.ctx.settings.tooltipColor).toRgbString() : "#ffffff";
  315 + staticSettings.tooltipFontColor = vm.ctx.settings.tooltipFontColor ? tinycolor(vm.ctx.settings.tooltipFontColor).toRgbString() : "#000000";
  316 + staticSettings.pathColor = vm.ctx.settings.color ? tinycolor(vm.ctx.settings.color).toHexString() : "#ff6300";
  317 + staticSettings.pathWeight = vm.ctx.settings.strokeWeight || 1;
  318 + staticSettings.pathOpacity = vm.ctx.settings.strokeOpacity || 1;
  319 + staticSettings.usePathColorFunction = vm.ctx.settings.useColorFunction || false;
  320 + staticSettings.showPoints = vm.ctx.settings.showPoints || false;
  321 + staticSettings.pointSize = vm.ctx.settings.pointSize || 1;
  322 + staticSettings.markerImageSize = vm.ctx.settings.markerImageSize || 20;
  323 + staticSettings.useMarkerImageFunction = vm.ctx.settings.useMarkerImageFunction || false;
  324 + staticSettings.pointColor = vm.ctx.settings.pointColor ? tinycolor(vm.ctx.settings.pointColor).toHexString() : "#ff6300";
  325 + staticSettings.markerImages = vm.ctx.settings.markerImages || [];
  326 + staticSettings.icon = L.icon({
  327 + iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKoAAACqCAYAAAA9dtSCAAAAhnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadY7LDcAwCEPvTNERCBA+40RVI3WDjl+iNMqp7wCWBZbheu4Ox6AggVRzDVVMJCSopXCcMGIhLGPnnHybSyraNjBNoeGGsg/l8xeV1bWbmGnVU0/KdLqY2HPmH4xUHDVih7S2Gv34q8ULVzos2Vmq5r4AAAoGaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMTcwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMTcwIgogICB0aWZmOkltYWdlV2lkdGg9IjE3MCIKICAgdGlmZjpJbWFnZUhlaWdodD0iMTcwIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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",
  328 + iconSize: [30, 30],
  329 + iconAnchor: [15, 15]
  330 + });
  331 + if (angular.isDefined(vm.ctx.settings.markerImage)) {
  332 + staticSettings.icon = L.icon({
  333 + iconUrl: vm.ctx.settings.markerImage,
  334 + iconSize: [staticSettings.markerImageSize, staticSettings.markerImageSize],
  335 + iconAnchor: [(staticSettings.markerImageSize / 2), (staticSettings.markerImageSize / 2)]
  336 + })
  337 + }
  338 +
  339 + if (staticSettings.usePathColorFunction && angular.isDefined(vm.ctx.settings.colorFunction)) {
  340 + staticSettings.colorFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.colorFunction);
  341 + }
  342 +
  343 + if (staticSettings.usePolygonTooltipFunction && angular.isDefined(vm.ctx.settings.polygonTooltipFunction)) {
  344 + staticSettings.polygonTooltipFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.polygonTooltipFunction);
  345 + }
  346 +
  347 + if (staticSettings.useLabelFunction && angular.isDefined(vm.ctx.settings.labelFunction)) {
  348 + staticSettings.labelFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.labelFunction);
  349 + }
  350 +
  351 + if (staticSettings.useTooltipFunction && angular.isDefined(vm.ctx.settings.tooltipFunction)) {
  352 + staticSettings.tooltipFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.tooltipFunction);
  353 + }
  354 +
  355 + if (staticSettings.usePolygonColorFunction && angular.isDefined(vm.ctx.settings.polygonColorFunction)) {
  356 + staticSettings.polygonColorFunction = new Function('data, dsData, dsIndex', vm.ctx.settings.polygonColorFunction);
  357 + }
  358 +
  359 + if (staticSettings.useMarkerImageFunction && angular.isDefined(vm.ctx.settings.markerImageFunction)) {
  360 + staticSettings.markerImageFunction = new Function('data, images, dsData, dsIndex', vm.ctx.settings.markerImageFunction);
  361 + }
  362 +
  363 + if (!staticSettings.useMarkerImageFunction &&
  364 + angular.isDefined(vm.ctx.settings.markerImage) &&
  365 + vm.ctx.settings.markerImage.length > 0) {
  366 + staticSettings.useMarkerImage = true;
  367 + let url = vm.ctx.settings.markerImage;
  368 + let size = staticSettings.markerImageSize || 20;
  369 + staticSettings.currentImage = {
  370 + url: url,
  371 + size: size
  372 + };
  373 + vm.utils.loadImageAspect(staticSettings.currentImage.url).then(
  374 + (aspect) => {
  375 + if (aspect) {
  376 + let width;
  377 + let height;
  378 + if (aspect > 1) {
  379 + width = staticSettings.currentImage.size;
  380 + height = staticSettings.currentImage.size / aspect;
  381 + } else {
  382 + width = staticSettings.currentImage.size * aspect;
  383 + height = staticSettings.currentImage.size;
  384 + }
  385 + staticSettings.icon = L.icon({
  386 + iconUrl: staticSettings.currentImage.url,
  387 + iconSize: [width, height],
  388 + iconAnchor: [width / 2, height / 2]
  389 + });
  390 + }
  391 + if (vm.trips) {
  392 + vm.trips.forEach(function (trip) {
  393 + if (trip.marker) {
  394 + trip.marker.setIcon(staticSettings.icon);
  395 + }
  396 + });
  397 + }
  398 + }
  399 + )
  400 + }
  401 + }
  402 +
  403 + function configureTripSettings(trip, index, apply) {
  404 + trip.settings = {};
  405 + trip.settings.color = calculateColor(trip);
  406 + trip.settings.polygonColor = calculatePolygonColor(trip);
  407 + trip.settings.strokeWeight = vm.staticSettings.pathWeight;
  408 + trip.settings.strokeOpacity = vm.staticSettings.pathOpacity;
  409 + trip.settings.pointColor = vm.staticSettings.pointColor;
  410 + trip.settings.polygonStrokeColor = vm.staticSettings.polygonStrokeColor;
  411 + trip.settings.polygonStrokeOpacity = vm.staticSettings.polygonStrokeOpacity;
  412 + trip.settings.polygonOpacity = vm.staticSettings.polygonOpacity;
  413 + trip.settings.polygonStrokeWeight = vm.staticSettings.polygonStrokeWeight;
  414 + trip.settings.pointSize = vm.staticSettings.pointSize;
  415 + trip.settings.icon = calculateIcon(trip);
  416 + if (apply) {
392 $timeout(() => { 417 $timeout(() => {
393 trip.settings.labelText = calculateLabel(trip); 418 trip.settings.labelText = calculateLabel(trip);
394 trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip)); 419 trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip));
395 - },0,true); 420 + trip.settings.polygonTooltipText = $sce.trustAsHtml(calculatePolygonTooltip(trip));
  421 + }, 0, true);
396 } else { 422 } else {
397 trip.settings.labelText = calculateLabel(trip); 423 trip.settings.labelText = calculateLabel(trip);
398 trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip)); 424 trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip));
  425 + trip.settings.polygonTooltipText = $sce.trustAsHtml(calculatePolygonTooltip(trip));
  426 + }
  427 + }
  428 +
  429 + function calculateLabel(trip) {
  430 + let label = '';
  431 + if (vm.staticSettings.showLabel) {
  432 + let labelReplaceInfo;
  433 + let labelText = vm.staticSettings.label;
  434 + if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) {
  435 + try {
  436 + labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dsIndex);
  437 + } catch (e) {
  438 + labelText = null;
  439 + }
  440 + }
  441 + labelText = vm.utils.createLabelFromDatasource(trip.dataSource, labelText);
  442 + labelReplaceInfo = processPattern(labelText, vm.ctx.datasources, trip.dSIndex);
  443 + label = fillPattern(labelText, labelReplaceInfo, trip.timeRange[vm.index]);
  444 + if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) {
  445 + try {
  446 + labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);
  447 + } catch (e) {
  448 + labelText = null;
  449 + }
  450 + }
399 } 451 }
400 - }  
401 -  
402 - function calculateLabel(trip) {  
403 - let label = '';  
404 - if (vm.staticSettings.showLabel) {  
405 - let labelReplaceInfo;  
406 - let labelText = vm.staticSettings.label;  
407 - if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) {  
408 - try {  
409 - labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dsIndex);  
410 - } catch (e) {  
411 - labelText = null;  
412 - }  
413 - }  
414 - labelText = vm.utils.createLabelFromDatasource(trip.dataSource, labelText);  
415 - labelReplaceInfo = processPattern(labelText, vm.ctx.datasources, trip.dSIndex);  
416 - label = fillPattern(labelText, labelReplaceInfo, trip.timeRange[vm.index]);  
417 - if (vm.staticSettings.useLabelFunction && angular.isDefined(vm.staticSettings.labelFunction)) {  
418 - try {  
419 - labelText = vm.staticSettings.labelFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);  
420 - } catch (e) {  
421 - labelText = null;  
422 - }  
423 - }  
424 - }  
425 - return label;  
426 - }  
427 -  
428 - function calculateTooltip(trip) {  
429 - let tooltip = '';  
430 - if (vm.staticSettings.displayTooltip) {  
431 - let tooltipReplaceInfo;  
432 - let tooltipText = vm.staticSettings.tooltipPattern;  
433 - if (vm.staticSettings.useTooltipFunction && angular.isDefined(vm.staticSettings.tooltipFunction)) {  
434 - try {  
435 - tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);  
436 - } catch (e) {  
437 - tooltipText = null;  
438 - }  
439 - }  
440 - tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText);  
441 - tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex);  
442 - tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[vm.index]);  
443 - tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null);  
444 -  
445 - }  
446 - return tooltip;  
447 - }  
448 -  
449 - function calculateColor(trip) {  
450 - let color = vm.staticSettings.pathColor;  
451 - let colorFn;  
452 - if (vm.staticSettings.usePathColorFunction && angular.isDefined(vm.staticSettings.colorFunction)) {  
453 - try {  
454 - colorFn = vm.staticSettings.colorFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);  
455 - } catch (e) {  
456 - colorFn = null;  
457 - }  
458 - }  
459 - if (colorFn && colorFn !== color && trip.polyline) {  
460 - trip.polyline.setStyle({color: colorFn});  
461 - }  
462 - return colorFn || color;  
463 - }  
464 -  
465 - function calculateIcon(trip) {  
466 - let icon = vm.staticSettings.icon;  
467 - if (vm.staticSettings.useMarkerImageFunction && angular.isDefined(vm.staticSettings.markerImageFunction)) {  
468 - let rawIcon;  
469 - try {  
470 - rawIcon = vm.staticSettings.markerImageFunction(vm.ctx.data, vm.staticSettings.markerImages, trip.timeRange[vm.index], trip.dSIndex);  
471 - } catch (e) {  
472 - rawIcon = null;  
473 - }  
474 - if (rawIcon) {  
475 - vm.utils.loadImageAspect(rawIcon).then(  
476 - (aspect) => {  
477 - if (aspect) {  
478 - let width;  
479 - let height;  
480 - if (aspect > 1) {  
481 - width = rawIcon.size;  
482 - height = rawIcon.size / aspect;  
483 - } else {  
484 - width = rawIcon.size * aspect;  
485 - height = rawIcon.size;  
486 - }  
487 - icon = L.icon({  
488 - iconUrl: rawIcon,  
489 - iconSize: [width, height],  
490 - iconAnchor: [width / 2, height / 2]  
491 - });  
492 - }  
493 - if (trip.marker) {  
494 - trip.marker.setIcon(icon);  
495 - }  
496 - }  
497 - )  
498 - }  
499 - }  
500 - return icon;  
501 - }  
502 -  
503 - function createUpdatePath(apply) {  
504 - if (vm.trips && vm.map) {  
505 - vm.trips.forEach(function (trip) {  
506 - if (trip.marker) {  
507 - trip.marker.remove();  
508 - delete trip.marker;  
509 - }  
510 - if (trip.polyline) {  
511 - trip.polyline.remove();  
512 - delete trip.polyline;  
513 - }  
514 - if (trip.points && trip.points.length) {  
515 - trip.points.forEach(function (point) {  
516 - point.remove();  
517 - });  
518 - delete trip.points;  
519 - }  
520 - });  
521 - vm.initBounds = true;  
522 - }  
523 - let normalizedTimeRange = createNormalizedTime(vm.data, 1000);  
524 - createNormalizedTrips(normalizedTimeRange, vm.datasources);  
525 - createTripsOnMap(apply);  
526 - if (vm.initBounds && !vm.initTrips) {  
527 - vm.trips.forEach(function (trip) {  
528 - vm.map.extendBounds(vm.map.bounds, trip.polyline);  
529 - vm.initBounds = !vm.datasources.every(  
530 - function (ds) {  
531 - return ds.dataReceived === true;  
532 - });  
533 - vm.initTrips = vm.trips.every(function (trip) {  
534 - return angular.isDefined(trip.marker) && angular.isDefined(trip.polyline);  
535 - });  
536 - });  
537 -  
538 - vm.map.fitBounds(vm.map.bounds);  
539 - }  
540 - }  
541 -  
542 - function fillPattern(pattern, replaceInfo, currentNormalizedValue) {  
543 - let text = angular.copy(pattern);  
544 - let reg = /\$\{([^\}]*)\}/g;  
545 - if (replaceInfo) {  
546 - for (let v = 0; v < replaceInfo.variables.length; v++) {  
547 - let variableInfo = replaceInfo.variables[v];  
548 - let label = reg.exec(pattern)[1].split(":")[0];  
549 - let txtVal = '';  
550 - if (label.length > -1 && angular.isDefined(currentNormalizedValue[label])) {  
551 - let varData = currentNormalizedValue[label];  
552 - if (isNumber(varData)) {  
553 - txtVal = padValue(varData, variableInfo.valDec, 0);  
554 - } else {  
555 - txtVal = varData;  
556 - }  
557 - }  
558 - text = text.split(variableInfo.variable).join(txtVal);  
559 - }  
560 - }  
561 - return text;  
562 - }  
563 -  
564 - function createNormalizedTime(data, step) {  
565 - if (!step) step = 1000;  
566 - let max_time = null;  
567 - let min_time = null;  
568 - let normalizedArray = [];  
569 - if (data && data.length > 0) {  
570 - vm.data.forEach(function (data) {  
571 - if (data.data.length > 0) {  
572 - data.data.forEach(function (sData) {  
573 - if (max_time === null) {  
574 - max_time = sData[0];  
575 - } else if (max_time < sData[0]) {  
576 - max_time = sData[0]  
577 - }  
578 - if (min_time === null) {  
579 - min_time = sData[0];  
580 - } else if (min_time > sData[0]) {  
581 - min_time = sData[0];  
582 - }  
583 - })  
584 - }  
585 - });  
586 - for (let i = min_time; i < max_time; i += step) {  
587 - normalizedArray.push({ts: i, formattedTs: $filter('date')(i, 'medium')});  
588 -  
589 - }  
590 - if (normalizedArray[normalizedArray.length - 1] && normalizedArray[normalizedArray.length - 1].ts !== max_time) { 452 + return label;
  453 + }
  454 +
  455 + function calculateTooltip(trip) {
  456 + let tooltip = '';
  457 + if (vm.staticSettings.displayTooltip) {
  458 + let tooltipReplaceInfo;
  459 + let tooltipText = vm.staticSettings.tooltipPattern;
  460 + if (vm.staticSettings.useTooltipFunction && angular.isDefined(vm.staticSettings.tooltipFunction)) {
  461 + try {
  462 + tooltipText = vm.staticSettings.tooltipFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);
  463 + } catch (e) {
  464 + tooltipText = null;
  465 + }
  466 + }
  467 + tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText);
  468 + tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex);
  469 + tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[vm.index]);
  470 + tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null);
  471 +
  472 + }
  473 + return tooltip;
  474 + }
  475 +
  476 + function calculatePolygonTooltip(trip) {
  477 + let tooltip = '';
  478 + if (vm.staticSettings.displayTooltip) {
  479 + let tooltipReplaceInfo;
  480 + let tooltipText = vm.staticSettings.polygonTooltipPattern;
  481 + if (vm.staticSettings.usePolygonTooltipFunction && angular.isDefined(vm.staticSettings.polygonTooltipFunction)) {
  482 + try {
  483 + tooltipText = vm.staticSettings.polygonTooltipFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);
  484 + } catch (e) {
  485 + tooltipText = null;
  486 + }
  487 + }
  488 + tooltipText = vm.utils.createLabelFromDatasource(trip.dataSource, tooltipText);
  489 + tooltipReplaceInfo = processPattern(tooltipText, vm.ctx.datasources, trip.dSIndex);
  490 + tooltip = fillPattern(tooltipText, tooltipReplaceInfo, trip.timeRange[vm.index]);
  491 + tooltip = fillPatternWithActions(tooltip, 'onTooltipAction', null);
  492 +
  493 + }
  494 + return tooltip;
  495 + }
  496 +
  497 + function calculateColor(trip) {
  498 + let color = vm.staticSettings.pathColor;
  499 + let colorFn;
  500 + if (vm.staticSettings.usePathColorFunction && angular.isDefined(vm.staticSettings.colorFunction)) {
  501 + try {
  502 + colorFn = vm.staticSettings.colorFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);
  503 + } catch (e) {
  504 + colorFn = null;
  505 + }
  506 + }
  507 + if (colorFn && colorFn !== color && trip.polyline) {
  508 + trip.polyline.setStyle({color: colorFn});
  509 + }
  510 + return colorFn || color;
  511 + }
  512 +
  513 + function calculatePolygonColor(trip) {
  514 + let color = vm.staticSettings.polygonColor;
  515 + let colorFn;
  516 + if (vm.staticSettings.usePolygonColorFunction && angular.isDefined(vm.staticSettings.polygonColorFunction)) {
  517 + try {
  518 + colorFn = vm.staticSettings.polygonColorFunction(vm.ctx.data, trip.timeRange[vm.index], trip.dSIndex);
  519 + } catch (e) {
  520 + colorFn = null;
  521 + }
  522 + }
  523 + if (colorFn && colorFn !== color && trip.polygon) {
  524 + trip.polygon.setStyle({fillColor: colorFn});
  525 + }
  526 + return colorFn || color;
  527 + }
  528 +
  529 + function calculateIcon(trip) {
  530 + let icon = vm.staticSettings.icon;
  531 + if (vm.staticSettings.useMarkerImageFunction && angular.isDefined(vm.staticSettings.markerImageFunction)) {
  532 + let rawIcon;
  533 + try {
  534 + rawIcon = vm.staticSettings.markerImageFunction(vm.ctx.data, vm.staticSettings.markerImages, trip.timeRange[vm.index], trip.dSIndex);
  535 + } catch (e) {
  536 + rawIcon = null;
  537 + }
  538 + if (rawIcon) {
  539 + vm.utils.loadImageAspect(rawIcon).then(
  540 + (aspect) => {
  541 + if (aspect) {
  542 + let width;
  543 + let height;
  544 + if (aspect > 1) {
  545 + width = rawIcon.size;
  546 + height = rawIcon.size / aspect;
  547 + } else {
  548 + width = rawIcon.size * aspect;
  549 + height = rawIcon.size;
  550 + }
  551 + icon = L.icon({
  552 + iconUrl: rawIcon,
  553 + iconSize: [width, height],
  554 + iconAnchor: [width / 2, height / 2]
  555 + });
  556 + }
  557 + if (trip.marker) {
  558 + trip.marker.setIcon(icon);
  559 + }
  560 + }
  561 + )
  562 + }
  563 + }
  564 + return icon;
  565 + }
  566 +
  567 + function createUpdatePath(apply) {
  568 + if (vm.trips && vm.map) {
  569 + vm.trips.forEach(function (trip) {
  570 + if (trip.marker) {
  571 + trip.marker.remove();
  572 + delete trip.marker;
  573 + }
  574 + if (trip.polyline) {
  575 + trip.polyline.remove();
  576 + delete trip.polyline;
  577 + }
  578 + if (trip.polygon) {
  579 + trip.polygon.remove();
  580 + delete trip.polygon;
  581 + }
  582 + if (trip.points && trip.points.length) {
  583 + trip.points.forEach(function (point) {
  584 + point.remove();
  585 + });
  586 + delete trip.points;
  587 + }
  588 + });
  589 + vm.initBounds = true;
  590 + }
  591 + let normalizedTimeRange = createNormalizedTime(vm.data, 1000);
  592 + createNormalizedTrips(normalizedTimeRange, vm.datasources);
  593 + createTripsOnMap(apply);
  594 + if (vm.initBounds && !vm.initTrips) {
  595 + vm.trips.forEach(function (trip) {
  596 + vm.map.extendBounds(vm.map.bounds, trip.polyline);
  597 + vm.initBounds = !vm.datasources.every(
  598 + function (ds) {
  599 + return ds.dataReceived === true;
  600 + });
  601 + vm.initTrips = vm.trips.every(function (trip) {
  602 + return angular.isDefined(trip.marker) && angular.isDefined(trip.polyline);
  603 + });
  604 + });
  605 +
  606 + vm.map.fitBounds(vm.map.bounds);
  607 + }
  608 + }
  609 +
  610 + function fillPattern(pattern, replaceInfo, currentNormalizedValue) {
  611 + let text = angular.copy(pattern);
  612 + let reg = /\$\{([^\}]*)\}/g;
  613 + if (replaceInfo) {
  614 + for (let v = 0; v < replaceInfo.variables.length; v++) {
  615 + let variableInfo = replaceInfo.variables[v];
  616 + let label = reg.exec(pattern)[1].split(":")[0];
  617 + let txtVal = '';
  618 + if (label.length > -1 && angular.isDefined(currentNormalizedValue[label])) {
  619 + let varData = currentNormalizedValue[label];
  620 + if (isNumber(varData)) {
  621 + txtVal = padValue(varData, variableInfo.valDec, 0);
  622 + } else {
  623 + txtVal = varData;
  624 + }
  625 + }
  626 + text = text.split(variableInfo.variable).join(txtVal);
  627 + }
  628 + }
  629 + return text;
  630 + }
  631 +
  632 + function createNormalizedTime(data, step) {
  633 + if (!step) step = 1000;
  634 + let max_time = null;
  635 + let min_time = null;
  636 + let normalizedArray = [];
  637 + if (data && data.length > 0) {
  638 + vm.data.forEach(function (data) {
  639 + if (data.data.length > 0) {
  640 + data.data.forEach(function (sData) {
  641 + if (max_time === null) {
  642 + max_time = sData[0];
  643 + } else if (max_time < sData[0]) {
  644 + max_time = sData[0]
  645 + }
  646 + if (min_time === null) {
  647 + min_time = sData[0];
  648 + } else if (min_time > sData[0]) {
  649 + min_time = sData[0];
  650 + }
  651 + })
  652 + }
  653 + });
  654 + for (let i = min_time; i < max_time; i += step) {
  655 + normalizedArray.push({ts: i, formattedTs: $filter('date')(i, 'medium')});
  656 +
  657 + }
  658 + if (normalizedArray[normalizedArray.length - 1] && normalizedArray[normalizedArray.length - 1].ts !== max_time) {
591 normalizedArray.push({ts: max_time, formattedTs: $filter('date')(max_time, 'medium')}); 659 normalizedArray.push({ts: max_time, formattedTs: $filter('date')(max_time, 'medium')});
592 } 660 }
593 - }  
594 - vm.maxTime = normalizedArray.length - 1;  
595 - vm.minTime = vm.maxTime > 1 ? 1 : 0;  
596 - if (vm.index < vm.minTime) { 661 + }
  662 + vm.maxTime = normalizedArray.length - 1;
  663 + vm.minTime = vm.maxTime > 1 ? 1 : 0;
  664 + if (vm.index < vm.minTime) {
597 vm.index = vm.minTime; 665 vm.index = vm.minTime;
598 } else if (vm.index > vm.maxTime) { 666 } else if (vm.index > vm.maxTime) {
599 vm.index = vm.maxTime; 667 vm.index = vm.maxTime;
600 } 668 }
601 - return normalizedArray;  
602 - }  
603 -  
604 - function createNormalizedTrips(timeRange, dataSources) {  
605 - vm.trips = [];  
606 - if (timeRange && timeRange.length > 0 && dataSources && dataSources.length > 0 && vm.data && vm.data.length > 0) {  
607 - dataSources.forEach(function (dS, index) {  
608 - vm.trips.push({  
609 - dataSource: dS,  
610 - dSIndex: index,  
611 - timeRange: angular.copy(timeRange)  
612 - })  
613 - });  
614 -  
615 - vm.data.forEach(function (data) {  
616 - let ds = data.datasource;  
617 - let tripIndex = vm.trips.findIndex(function (el) {  
618 - return el.dataSource.entityId === ds.entityId;  
619 - });  
620 -  
621 - if (tripIndex > -1) {  
622 - createNormalizedValue(data.data, data.dataKey.label, vm.trips[tripIndex].timeRange);  
623 - }  
624 - })  
625 - }  
626 -  
627 - createNormalizedLatLngs();  
628 - }  
629 -  
630 - function createNormalizedValue(dataArray, dataKey, timeRangeArray) {  
631 - timeRangeArray.forEach(function (timeStamp) {  
632 - let targetTDiff = null;  
633 - let value = null;  
634 - for (let i = 0; i < dataArray.length; i++) {  
635 - let tDiff = dataArray[i][0] - timeStamp.ts;  
636 - if (targetTDiff === null || (tDiff <= 0 && targetTDiff < tDiff)) {  
637 - targetTDiff = tDiff;  
638 - value = dataArray[i][1];  
639 -  
640 - }  
641 - }  
642 - if (value !== null) timeStamp[dataKey] = value;  
643 - });  
644 - }  
645 -  
646 - function createNormalizedLatLngs() {  
647 - vm.trips.forEach(function (el) {  
648 - el.latLngs = [];  
649 - el.timeRange.forEach(function (data) {  
650 - let lat = data[vm.staticSettings.latKeyName];  
651 - let lng = data[vm.staticSettings.lngKeyName];  
652 - if (lat && lng && vm.map) {  
653 - data.latLng = vm.map.createLatLng(lat, lng);  
654 - }  
655 - el.latLngs.push(data.latLng);  
656 - });  
657 - addAngleForTrip(el);  
658 - })  
659 - }  
660 -  
661 - function addAngleForTrip(trip) {  
662 - if (trip.timeRange && trip.timeRange.length > 0) {  
663 - trip.timeRange.forEach(function (point, index) {  
664 - let nextPoint, prevPoint;  
665 - nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : trip.timeRange[index + 1];  
666 - prevPoint = index === 0 ? trip.timeRange[0] : trip.timeRange[index - 1];  
667 - let nextLatLng = { 669 + return normalizedArray;
  670 + }
  671 +
  672 + function createNormalizedTrips(timeRange, dataSources) {
  673 + vm.trips = [];
  674 + if (timeRange && timeRange.length > 0 && dataSources && dataSources.length > 0 && vm.data && vm.data.length > 0) {
  675 + dataSources.forEach(function (dS, index) {
  676 + vm.trips.push({
  677 + dataSource: dS,
  678 + dSIndex: index,
  679 + timeRange: angular.copy(timeRange)
  680 + })
  681 + });
  682 +
  683 + vm.data.forEach(function (data) {
  684 + let ds = data.datasource;
  685 + let tripIndex = vm.trips.findIndex(function (el) {
  686 + return el.dataSource.entityId === ds.entityId;
  687 + });
  688 +
  689 + if (tripIndex > -1) {
  690 + createNormalizedValue(data.data, data.dataKey.label, vm.trips[tripIndex].timeRange);
  691 + }
  692 + })
  693 + }
  694 +
  695 + createNormalizedLatLngs();
  696 + }
  697 +
  698 + function createNormalizedValue(dataArray, dataKey, timeRangeArray) {
  699 + timeRangeArray.forEach(function (timeStamp) {
  700 + let targetTDiff = null;
  701 + let value = null;
  702 + for (let i = 0; i < dataArray.length; i++) {
  703 + let tDiff = dataArray[i][0] - timeStamp.ts;
  704 + if (targetTDiff === null || (tDiff <= 0 && targetTDiff < tDiff)) {
  705 + targetTDiff = tDiff;
  706 + value = dataArray[i][1];
  707 +
  708 + }
  709 + }
  710 + if (value !== null) timeStamp[dataKey] = value;
  711 + });
  712 + }
  713 +
  714 + function createNormalizedLatLngs() {
  715 + vm.trips.forEach(function (el) {
  716 + el.latLngs = [];
  717 + el.timeRange.forEach(function (data) {
  718 + let lat = data[vm.staticSettings.latKeyName];
  719 + let lng = data[vm.staticSettings.lngKeyName];
  720 + if (lat && lng && vm.map) {
  721 + data.latLng = vm.map.createLatLng(lat, lng);
  722 + }
  723 + el.latLngs.push(data.latLng);
  724 + });
  725 + addAngleForTrip(el);
  726 + })
  727 + }
  728 +
  729 + function addAngleForTrip(trip) {
  730 + if (trip.timeRange && trip.timeRange.length > 0) {
  731 + trip.timeRange.forEach(function (point, index) {
  732 + let nextPoint, prevPoint;
  733 + nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : trip.timeRange[index + 1];
  734 + prevPoint = index === 0 ? trip.timeRange[0] : trip.timeRange[index - 1];
  735 + let nextLatLng = {
668 lat: nextPoint[vm.staticSettings.latKeyName], 736 lat: nextPoint[vm.staticSettings.latKeyName],
669 lng: nextPoint[vm.staticSettings.lngKeyName] 737 lng: nextPoint[vm.staticSettings.lngKeyName]
670 }; 738 };
@@ -682,78 +750,125 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s @@ -682,78 +750,125 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter, $s
682 point.h = findAngle(prevLatLng.lat, prevLatLng.lng, nextLatLng.lat, nextLatLng.lng); 750 point.h = findAngle(prevLatLng.lat, prevLatLng.lng, nextLatLng.lat, nextLatLng.lng);
683 point.h += vm.staticSettings.rotationAngle; 751 point.h += vm.staticSettings.rotationAngle;
684 } 752 }
685 - });  
686 - }  
687 - }  
688 -  
689 - function createTripsOnMap(apply) {  
690 - if (vm.trips.length > 0) {  
691 - vm.trips.forEach(function (trip) {  
692 - if (trip.timeRange.length > 0 && trip.latLngs.every(el => angular.isDefined(el))) {  
693 - configureTripSettings(trip, vm.index, apply);  
694 - if (vm.staticSettings.showPoints) {  
695 - trip.points = [];  
696 - trip.latLngs.forEach(function (latLng) {  
697 - let point = L.circleMarker(latLng, {  
698 - color: trip.settings.pointColor,  
699 - radius: trip.settings.pointSize  
700 - }).addTo(vm.map.map);  
701 - trip.points.push(point);  
702 - });  
703 - }  
704 -  
705 - if (angular.isUndefined(trip.marker)) {  
706 - trip.polyline = vm.map.createPolyline(trip.latLngs, trip.settings);  
707 - }  
708 -  
709 - if (trip.timeRange && trip.timeRange.length && angular.isUndefined(trip.marker)) {  
710 - trip.marker = L.marker(trip.timeRange[vm.index].latLng);  
711 - trip.marker.setZIndexOffset(1000);  
712 - trip.marker.setIcon(vm.staticSettings.icon);  
713 - trip.marker.setRotationOrigin('center center'); 753 + });
  754 + }
  755 + }
  756 +
  757 + function createTripsOnMap(apply) {
  758 + if (vm.trips.length > 0) {
  759 + vm.trips.forEach(function (trip) {
  760 + configureTripSettings(trip, vm.index, apply);
  761 + if (trip.timeRange.length > 0 && trip.latLngs.every(el => angular.isDefined(el))) {
  762 + if (vm.staticSettings.showPoints) {
  763 + trip.points = [];
  764 + trip.latLngs.forEach(function (latLng) {
  765 + let point = L.circleMarker(latLng, {
  766 + color: trip.settings.pointColor,
  767 + radius: trip.settings.pointSize
  768 + }).addTo(vm.map.map);
  769 + trip.points.push(point);
  770 + });
  771 + }
  772 +
  773 + if (angular.isUndefined(trip.marker)) {
  774 + trip.polyline = vm.map.createPolyline(trip.latLngs, trip.settings);
  775 + }
  776 +
  777 +
  778 + if (trip.timeRange && trip.timeRange.length && angular.isUndefined(trip.marker)) {
  779 + trip.marker = L.marker(trip.timeRange[vm.index].latLng);
  780 + trip.marker.setZIndexOffset(1000);
  781 + trip.marker.setIcon(vm.staticSettings.icon);
  782 + trip.marker.setRotationOrigin('center center');
714 trip.marker.on('click', function () { 783 trip.marker.on('click', function () {
715 showHideTooltip(trip); 784 showHideTooltip(trip);
716 }); 785 });
717 - trip.marker.addTo(vm.map.map);  
718 - moveMarker(trip);  
719 - }  
720 - }  
721 - });  
722 - }  
723 - }  
724 -  
725 - function moveMarker(trip) {  
726 - if (angular.isDefined(trip.marker)) {  
727 - trip.markerAngleIsSet = true;  
728 - trip.marker.setLatLng(trip.timeRange[vm.index].latLng);  
729 - trip.marker.setRotationAngle(trip.timeRange[vm.index].h);  
730 - trip.marker.update();  
731 - } else {  
732 - if (trip.timeRange && trip.timeRange.length) {  
733 - trip.marker = L.marker(trip.timeRange[vm.index].latLng);  
734 - trip.marker.setZIndexOffset(1000);  
735 - trip.marker.setIcon(vm.staticSettings.icon);  
736 - trip.marker.setRotationOrigin('center center');  
737 - trip.marker.on('click', function () {  
738 - showHideTooltip(trip);  
739 - });  
740 - trip.marker.addTo(vm.map.map);  
741 - trip.marker.update();  
742 - }  
743 -  
744 - }  
745 - configureTripSettings(trip);  
746 - }  
747 -  
748 -  
749 - function showHideTooltip(trip) {  
750 - if (vm.staticSettings.displayTooltip) {  
751 - if (vm.staticSettings.showTooltip && trip && vm.activeTripIndex !== trip.dSIndex) {  
752 - vm.staticSettings.showTooltip = true;  
753 - } else {  
754 - vm.staticSettings.showTooltip = !vm.staticSettings.showTooltip;  
755 - }  
756 - }  
757 - if (trip && vm.activeTripIndex !== trip.dSIndex) vm.activeTripIndex = trip.dSIndex;  
758 - } 786 + trip.marker.addTo(vm.map.map);
  787 + moveMarker(trip);
  788 + }
  789 + }
  790 +
  791 + if (vm.staticSettings.showPolygon && angular.isDefined(trip.timeRange[vm.index][vm.staticSettings.polKeyName])) {
  792 + let polygonSettings = {
  793 + fill: true,
  794 + fillColor: trip.settings.polygonColor,
  795 + color: trip.settings.polygonStrokeColor,
  796 + weight: trip.settings.polygonStrokeWeight,
  797 + fillOpacity: trip.settings.polygonOpacity,
  798 + opacity: trip.settings.polygonStrokeOpacity
  799 + };
  800 + let polygonLatLngsRaw = mapPolygonArray(angular.fromJson(trip.timeRange[vm.index][vm.staticSettings.polKeyName]));
  801 + trip.polygon = L.polygon(polygonLatLngsRaw, polygonSettings).addTo(vm.map.map);
  802 + trip.polygon.on('click',function(){showHidePolygonTooltip(trip)});
  803 + }
  804 + });
  805 + }
  806 + }
  807 +
  808 + function mapPolygonArray(rawArray) {
  809 + return rawArray.map(function (el) {
  810 + if (el.length === 2) {
  811 + if (!angular.isNumber(el[0]) && !angular.isNumber(el[1])) {
  812 + return el.map(function (subEl) {
  813 + return mapPolygonArray(subEl);
  814 + })
  815 + } else {
  816 + return vm.map.createLatLng(el[0], el[1]);
  817 + }
  818 + } else if (el.length > 2) {
  819 + return mapPolygonArray(el);
  820 + } else {
  821 + return vm.map.createLatLng(false);
  822 + }
  823 + });
  824 + }
  825 +
  826 + function moveMarker(trip) {
  827 + if (angular.isDefined(trip.timeRange[vm.index].latLng)) {
  828 + if (angular.isDefined(trip.marker)) {
  829 + trip.markerAngleIsSet = true;
  830 + trip.marker.setLatLng(trip.timeRange[vm.index].latLng);
  831 + trip.marker.setRotationAngle(trip.timeRange[vm.index].h);
  832 + trip.marker.update();
  833 + } else {
  834 + if (trip.timeRange && trip.timeRange.length) {
  835 + trip.marker = L.marker(trip.timeRange[vm.index].latLng);
  836 + trip.marker.setZIndexOffset(1000);
  837 + trip.marker.setIcon(vm.staticSettings.icon);
  838 + trip.marker.setRotationOrigin('center center');
  839 + trip.marker.on('click', function () {
  840 + showHideTooltip(trip);
  841 + });
  842 + trip.marker.addTo(vm.map.map);
  843 + trip.marker.update();
  844 + }
  845 + }
  846 + }
  847 + configureTripSettings(trip);
  848 + }
  849 +
  850 +
  851 + function showHideTooltip(trip) {
  852 + if (vm.staticSettings.displayTooltip) {
  853 + if (vm.staticSettings.showTooltip && trip && (vm.activeTripIndex !== trip.dSIndex || vm.staticSettings.tooltipMarker !== 'marker')) {
  854 + vm.staticSettings.showTooltip = true;
  855 + } else {
  856 + vm.staticSettings.showTooltip = !vm.staticSettings.showTooltip;
  857 + }
  858 + vm.staticSettings.tooltipMarker = 'marker';
  859 + }
  860 + if (trip && vm.activeTripIndex !== trip.dSIndex) vm.activeTripIndex = trip.dSIndex;
  861 + }
  862 +
  863 + function showHidePolygonTooltip(trip) {
  864 + if (vm.staticSettings.displayTooltip) {
  865 + if (vm.staticSettings.showTooltip && trip && (vm.activeTripIndex !== trip.dSIndex || vm.staticSettings.tooltipMarker !== 'polygon')) {
  866 + vm.staticSettings.showTooltip = true;
  867 + } else {
  868 + vm.staticSettings.showTooltip = !vm.staticSettings.showTooltip;
  869 + }
  870 + vm.staticSettings.tooltipMarker = 'polygon';
  871 + }
  872 + if (trip && vm.activeTripIndex !== trip.dSIndex) vm.activeTripIndex = trip.dSIndex;
  873 + }
759 } 874 }
@@ -91,6 +91,7 @@ @@ -91,6 +91,7 @@
91 position: relative; 91 position: relative;
92 box-sizing: border-box; 92 box-sizing: border-box;
93 width: 100%; 93 width: 100%;
  94 + padding-bottom: 16px;
94 padding-left: 10px; 95 padding-left: 10px;
95 96
96 md-slider-container { 97 md-slider-container {
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 <ng-md-icon icon="info_outline"></ng-md-icon> 27 <ng-md-icon icon="info_outline"></ng-md-icon>
28 </md-button> 28 </md-button>
29 </div> 29 </div>
30 - <div class="trip-animation-tooltip md-whiteframe-z4" layout="column" ng-class="!vm.staticSettings.showTooltip ? 'trip-animation-tooltip-hidden':''" ng-bind-html="vm.trips[vm.activeTripIndex].settings.tooltipText" 30 + <div class="trip-animation-tooltip md-whiteframe-z4" layout="column" ng-class="!vm.staticSettings.showTooltip ? 'trip-animation-tooltip-hidden':''" ng-bind-html="vm.staticSettings.tooltipMarker === 'polygon' ? vm.trips[vm.activeTripIndex].settings.polygonTooltipText : vm.trips[vm.activeTripIndex].settings.tooltipText"
31 ng-style="{'background-color': vm.staticSettings.tooltipColor, 'opacity': vm.staticSettings.tooltipOpacity, 'color': vm.staticSettings.tooltipFontColor}"> 31 ng-style="{'background-color': vm.staticSettings.tooltipColor, 'opacity': vm.staticSettings.tooltipOpacity, 'color': vm.staticSettings.tooltipFontColor}">
32 </div> 32 </div>
33 </div> 33 </div>
@@ -210,7 +210,7 @@ export function arraysEqual(a, b) { @@ -210,7 +210,7 @@ export function arraysEqual(a, b) {
210 if (a.length != b.length) return false; 210 if (a.length != b.length) return false;
211 211
212 for (var i = 0; i < a.length; ++i) { 212 for (var i = 0; i < a.length; ++i) {
213 - if (!a[i].equals(b[i])) return false; 213 + if (!arraysEqual(a[i],b[i])) return false;
214 } 214 }
215 return true; 215 return true;
216 } 216 }