Commit 5e65f2a48a109b7359f29c8762fe419ee1b52d58

Authored by Artem Halushko
1 parent 5535c33d

trip-animation map provider & custom markers

@@ -38,7 +38,8 @@ @@ -38,7 +38,8 @@
38 "src/app/shared/components/json-form/react/json-form.scss", 38 "src/app/shared/components/json-form/react/json-form.scss",
39 "node_modules/rc-select/assets/index.less", 39 "node_modules/rc-select/assets/index.less",
40 "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css", 40 "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
41 - "node_modules/leaflet/dist/leaflet.css" 41 + "node_modules/leaflet/dist/leaflet.css",
  42 + "src/app/modules/home/components/widget/lib/maps/markers.scss"
42 ], 43 ],
43 "stylePreprocessorOptions": { 44 "stylePreprocessorOptions": {
44 "includePaths": [ 45 "includePaths": [
  1 +
  2 +export function initSchema() {
  3 + return {
  4 + schema: {
  5 + type: "object",
  6 + properties: {},
  7 + required: []
  8 + },
  9 + form: [],
  10 + groupInfoes: []
  11 + };
  12 +}
  13 +
  14 +export function addGroupInfo(schema, title: string) {
  15 + schema.groupInfoes.push({
  16 + "formIndex": schema.groupInfoes?.length || 0,
  17 + "GroupTitle": title
  18 + });
  19 +}
  20 +
  21 +export function addToSchema(schema, newSchema) {
  22 + Object.assign(schema.schema.properties, newSchema.schema.properties);
  23 + schema.schema.required = schema.schema.required.concat(newSchema.schema.required);
  24 + schema.form.push(newSchema.form);
  25 +}
  26 +
  27 +export function mergeSchemes(schemes: any[]) {
  28 + return schemes.reduce((finalSchema, schema) => {
  29 + return {
  30 + schema: {
  31 + properties: {
  32 + ...finalSchema.schema.properties,
  33 + ...schema.schema.properties
  34 + },
  35 + required: [
  36 + ...finalSchema.schema.required,
  37 + ...schema.schema.required
  38 + ]
  39 + },
  40 + form: [
  41 + ...finalSchema.form,
  42 + ...schema.form
  43 + ]
  44 + }
  45 + }, initSchema());
  46 +}
  47 +
  48 +export function addCondition(schema, condition: String) {
  49 + schema.form = schema.form.map(element => {
  50 + if (typeof element === 'string') {
  51 + return {
  52 + key: element,
  53 + condition: condition
  54 + }
  55 + }
  56 + if (typeof element == 'object') {
  57 + if (element.condition) {
  58 + element.condition += ' && ' + condition
  59 + }
  60 + else element.condition = condition;
  61 + }
  62 + return element;
  63 + });
  64 + return schema;
  65 +}
@@ -452,54 +452,55 @@ export function aspectCache(imageUrl: string): Observable<number> { @@ -452,54 +452,55 @@ export function aspectCache(imageUrl: string): Observable<number> {
452 export function parseArray(input: any[]): any[] { 452 export function parseArray(input: any[]): any[] {
453 let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value(); 453 let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
454 return alliases.map((alliasArray, dsIndex) => 454 return alliases.map((alliasArray, dsIndex) =>
455 - alliasArray[0].data.map((el, i) => {  
456 - const obj = {  
457 - aliasName: alliasArray[0]?.datasource?.aliasName,  
458 - $datasource: alliasArray[0]?.datasource,  
459 - dsIndex: dsIndex,  
460 - time: el[0]  
461 - };  
462 - alliasArray.forEach(el => {  
463 - obj[el?.dataKey?.label] = el?.data[i][1];  
464 - obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];  
465 - if (el?.dataKey?.label == 'type') {  
466 - obj['deviceType'] = el?.data[0][1];  
467 - }  
468 - });  
469 - return obj;  
470 - })  
471 - );  
472 -}  
473 -  
474 -export function parseData(input: any[]): any[] {  
475 - return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => { 455 + alliasArray[0].data.map((el, i) => {
476 const obj = { 456 const obj = {
477 - aliasName: alliasArray[0]?.datasource?.aliasName,  
478 - entityName: alliasArray[0]?.datasource?.entityName,  
479 - $datasource: alliasArray[0]?.datasource,  
480 - dsIndex: i 457 + aliasName: alliasArray[0]?.datasource?.aliasName,
  458 + entityName: alliasArray[0]?.datasource?.entityName,
  459 + $datasource: alliasArray[0]?.datasource,
  460 + dsIndex: dsIndex,
  461 + time: el[0]
481 }; 462 };
482 alliasArray.forEach(el => { 463 alliasArray.forEach(el => {
483 - obj[el?.dataKey?.label] = el?.data[0][1];  
484 - obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];  
485 - if (el?.dataKey?.label == 'type') {  
486 - obj['deviceType'] = el?.data[0][1];  
487 - } 464 + obj[el?.dataKey?.label] = el?.data[i][1];
  465 + obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
  466 + if (el?.dataKey?.label == 'type') {
  467 + obj['deviceType'] = el?.data[0][1];
  468 + }
488 }); 469 });
489 return obj; 470 return obj;
  471 + })
  472 + );
  473 +}
  474 +
  475 +export function parseData(input: any[]): any[] {
  476 + return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {
  477 + const obj = {
  478 + aliasName: alliasArray[0]?.datasource?.aliasName,
  479 + entityName: alliasArray[0]?.datasource?.entityName,
  480 + $datasource: alliasArray[0]?.datasource,
  481 + dsIndex: i
  482 + };
  483 + alliasArray.forEach(el => {
  484 + obj[el?.dataKey?.label] = el?.data[0][1];
  485 + obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
  486 + if (el?.dataKey?.label == 'type') {
  487 + obj['deviceType'] = el?.data[0][1];
  488 + }
  489 + });
  490 + return obj;
490 }); 491 });
491 } 492 }
492 493
493 export function safeExecute(func: Function, params = []) { 494 export function safeExecute(func: Function, params = []) {
494 let res = null; 495 let res = null;
495 if (func && typeof (func) == "function") { 496 if (func && typeof (func) == "function") {
496 - try {  
497 - res = func(...params);  
498 - }  
499 - catch (err) {  
500 - console.error(err);  
501 - res = null;  
502 - } 497 + try {
  498 + res = func(...params);
  499 + }
  500 + catch (err) {
  501 + console.error(err);
  502 + res = null;
  503 + }
503 } 504 }
504 return res; 505 return res;
505 } 506 }
@@ -507,13 +508,13 @@ export function safeExecute(func: Function, params = []) { @@ -507,13 +508,13 @@ export function safeExecute(func: Function, params = []) {
507 export function parseFunction(source: string, params: string[] = []): Function { 508 export function parseFunction(source: string, params: string[] = []): Function {
508 let res = null; 509 let res = null;
509 if (source?.length) { 510 if (source?.length) {
510 - try {  
511 - res = new Function(...params, source);  
512 - }  
513 - catch (err) {  
514 - console.error(err);  
515 - res = null;  
516 - } 511 + try {
  512 + res = new Function(...params, source);
  513 + }
  514 + catch (err) {
  515 + console.error(err);
  516 + res = null;
  517 + }
517 } 518 }
518 return res; 519 return res;
519 } 520 }
@@ -122,6 +122,11 @@ export default abstract class LeafletMap { @@ -122,6 +122,11 @@ export default abstract class LeafletMap {
122 ////Markers 122 ////Markers
123 updateMarkers(markersData) { 123 updateMarkers(markersData) {
124 markersData.forEach(data => { 124 markersData.forEach(data => {
  125 + if(data.rotationAngle){
  126 + this.options.icon= L.divIcon({
  127 + html: `<div class="arrow" style="transform: rotate(${data.rotationAngle}deg)"><div>`
  128 + })
  129 + }
125 if (this.markers.get(data.aliasName)) { 130 if (this.markers.get(data.aliasName)) {
126 this.updateMarker(data.aliasName, data, markersData, this.options as MarkerSettings) 131 this.updateMarker(data.aliasName, data, markersData, this.options as MarkerSettings)
127 } 132 }
@@ -18,6 +18,7 @@ export interface MapOptions { @@ -18,6 +18,7 @@ export interface MapOptions {
18 useDefaultCenterPosition?: boolean, 18 useDefaultCenterPosition?: boolean,
19 gmDefaultMapType?: string, 19 gmDefaultMapType?: string,
20 useLabelFunction: string; 20 useLabelFunction: string;
  21 + icon?: any;
21 } 22 }
22 23
23 export enum MapProviders { 24 export enum MapProviders {
@@ -29,6 +30,7 @@ export enum MapProviders { @@ -29,6 +30,7 @@ export enum MapProviders {
29 } 30 }
30 31
31 export interface MarkerSettings extends MapOptions { 32 export interface MarkerSettings extends MapOptions {
  33 + icon?: any;
32 showLabel?: boolean, 34 showLabel?: boolean,
33 draggable?: boolean, 35 draggable?: boolean,
34 displayTooltip?: boolean, 36 displayTooltip?: boolean,
@@ -8,7 +8,8 @@ export interface MapWidgetInterface { @@ -8,7 +8,8 @@ export interface MapWidgetInterface {
8 } 8 }
9 9
10 export interface MapWidgetStaticInterface { 10 export interface MapWidgetStaticInterface {
11 - settingsSchema(mapProvider, drawRoutes): Object; 11 + settingsSchema(mapProvider?, drawRoutes?): Object;
  12 + getProvidersSchema():Object
12 dataKeySettingsSchema(): Object; 13 dataKeySettingsSchema(): Object;
13 actionSources(): Object; 14 actionSources(): Object;
14 } 15 }
@@ -15,6 +15,7 @@ import { @@ -15,6 +15,7 @@ import {
15 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; 15 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
16 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; 16 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
17 import { parseFunction, parseArray, parseData } from '@app/core/utils'; 17 import { parseFunction, parseArray, parseData } from '@app/core/utils';
  18 +import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@app/core/schema-utils';
18 19
19 export class MapWidgetController implements MapWidgetInterface { 20 export class MapWidgetController implements MapWidgetInterface {
20 21
@@ -73,7 +74,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -73,7 +74,7 @@ export class MapWidgetController implements MapWidgetInterface {
73 this.map.updateMarkers(parseData(this.data)); 74 this.map.updateMarkers(parseData(this.data));
74 } 75 }
75 76
76 - public updateHistoryData(dataSources){ 77 + public updateHistoryData(dataSources) {
77 dataSources.map() 78 dataSources.map()
78 } 79 }
79 80
@@ -93,90 +94,28 @@ export class MapWidgetController implements MapWidgetInterface { @@ -93,90 +94,28 @@ export class MapWidgetController implements MapWidgetInterface {
93 return {}; 94 return {};
94 } 95 }
95 96
  97 + public static getProvidersSchema(){
  98 + return mergeSchemes([mapProviderSchema,
  99 + ...Object.values(providerSets)?.map(
  100 + setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))])
  101 + }
  102 +
96 public static settingsSchema(mapProvider, drawRoutes): Object { 103 public static settingsSchema(mapProvider, drawRoutes): Object {
97 //const providerInfo = providerSets[mapProvider]; 104 //const providerInfo = providerSets[mapProvider];
98 let schema = initSchema(); 105 let schema = initSchema();
  106 + addToSchema(schema,this.getProvidersSchema());
99 107
100 - function initSchema() {  
101 - return {  
102 - schema: {  
103 - type: "object",  
104 - properties: {},  
105 - required: []  
106 - },  
107 - form: [],  
108 - groupInfoes: []  
109 - };  
110 - }  
111 -  
112 - function addGroupInfo(title: string) {  
113 - schema.groupInfoes.push({  
114 - "formIndex": schema.groupInfoes?.length || 0,  
115 - "GroupTitle": title  
116 - });  
117 - }  
118 -  
119 - function addToSchema(newSchema) {  
120 - Object.assign(schema.schema.properties, newSchema.schema.properties);  
121 - schema.schema.required = schema.schema.required.concat(newSchema.schema.required);  
122 - schema.form.push(newSchema.form);//schema.form.concat(commonMapSettingsSchema.form);  
123 - }  
124 -  
125 - function mergeSchemes(schemes: any[]) {  
126 - return schemes.reduce((finalSchema, schema) => {  
127 - return {  
128 - schema: {  
129 - properties: {  
130 - ...finalSchema.schema.properties,  
131 - ...schema.schema.properties  
132 - },  
133 - required: [  
134 - ...finalSchema.schema.required,  
135 - ...schema.schema.required  
136 - ]  
137 - },  
138 - form: [  
139 - ...finalSchema.form,  
140 - ...schema.form  
141 - ]  
142 - }  
143 - }, initSchema());  
144 - }  
145 -  
146 - function addCondition(schema, condition: String) {  
147 - schema.form = schema.form.map(element => {  
148 - if (typeof element === 'string') {  
149 - return {  
150 - key: element,  
151 - condition: condition  
152 - }  
153 - }  
154 - if (typeof element == 'object') {  
155 - if (element.condition) {  
156 - element.condition += ' && ' + condition  
157 - }  
158 - else element.condition = condition;  
159 - }  
160 - return element;  
161 - });  
162 - return schema;  
163 - }  
164 -  
165 - addToSchema(mergeSchemes([mapProviderSchema,  
166 - ...Object.values(providerSets)?.map(  
167 - setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]));  
168 -  
169 - addGroupInfo("Map Provider Settings");  
170 - addToSchema(commonMapSettingsSchema);  
171 - addGroupInfo("Common Map Settings"); 108 + addGroupInfo(schema, "Map Provider Settings");
  109 + addToSchema(schema, commonMapSettingsSchema);
  110 + addGroupInfo(schema, "Common Map Settings");
172 111
173 if (drawRoutes) { 112 if (drawRoutes) {
174 - addToSchema(routeMapSettingsSchema);  
175 - addGroupInfo("Route Map Settings"); 113 + addToSchema(schema, routeMapSettingsSchema);
  114 + addGroupInfo(schema, "Route Map Settings");
176 } else if (mapProvider !== 'image-map') { 115 } else if (mapProvider !== 'image-map') {
177 let clusteringSchema = mergeSchemes([markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchema]) 116 let clusteringSchema = mergeSchemes([markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchema])
178 - addToSchema(clusteringSchema);  
179 - addGroupInfo("Markers Clustering Settings"); 117 + addToSchema(schema, clusteringSchema);
  118 + addGroupInfo(schema, "Markers Clustering Settings");
180 } 119 }
181 console.log(11, schema); 120 console.log(11, schema);
182 121
@@ -243,7 +182,7 @@ const defaultSettings = { @@ -243,7 +182,7 @@ const defaultSettings = {
243 latKeyName: 'latitude', 182 latKeyName: 'latitude',
244 lngKeyName: 'longitude', 183 lngKeyName: 'longitude',
245 polygonKeyName: 'coordinates', 184 polygonKeyName: 'coordinates',
246 - showLabel: true, 185 + showLabel: false,
247 label: "${entityName}", 186 label: "${entityName}",
248 showTooltip: false, 187 showTooltip: false,
249 useDefaultCenterPosition: false, 188 useDefaultCenterPosition: false,
@@ -30,6 +30,12 @@ export function interpolateArray(originData, interpolatedIntervals) { @@ -30,6 +30,12 @@ export function interpolateArray(originData, interpolatedIntervals) {
30 return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); 30 return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
31 }; 31 };
32 32
  33 + function findAngle(startPoint, endPoint) {
  34 + let angle = -Math.atan2(endPoint.latitude - startPoint.longitude, endPoint.longitude - startPoint.latitude);
  35 + angle = angle * 180 / Math.PI;
  36 + return parseInt(angle.toFixed(2));
  37 + }
  38 +
33 const result = {}; 39 const result = {};
34 40
35 for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { 41 for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) {
@@ -37,10 +43,16 @@ export function interpolateArray(originData, interpolatedIntervals) { @@ -37,10 +43,16 @@ export function interpolateArray(originData, interpolatedIntervals) {
37 while (originData[i].time < currentTime) i++; 43 while (originData[i].time < currentTime) i++;
38 const before = originData[i - 1]; 44 const before = originData[i - 1];
39 const after = originData[i]; 45 const after = originData[i];
40 - result[currentTime] = (interpolateOnPointSegment( 46 + const interpolation = interpolateOnPointSegment(
41 new L.Point(before.latitude, before.longitude), 47 new L.Point(before.latitude, before.longitude),
42 new L.Point(after.latitude, after.longitude), 48 new L.Point(after.latitude, after.longitude),
43 - getRatio(before.time, after.time, currentTime))); 49 + getRatio(before.time, after.time, currentTime));
  50 + result[currentTime] = ({
  51 + ...originData[i],
  52 + rotationAngle: findAngle(before, after),
  53 + latitude: interpolation.x,
  54 + longitude: interpolation.y
  55 + });
44 j++; 56 j++;
45 } 57 }
46 58
  1 +
  2 +.arrow{
  3 + background:#222;
  4 + text-align:center;
  5 + font-size:180%;
  6 + margin:2em;
  7 + font-family: Calibri, arial, sans-serif;
  8 + color:white;
  9 + padding-top:1.8em;
  10 + display:inline-block;/* or block */
  11 + position:relative;
  12 + border-color:white;
  13 + text-decoration:none;
  14 + transition:all .3s ease-out;
  15 +}
  16 +.arrow:before{
  17 + content:'▲';
  18 + font-size:.9em;
  19 + position:absolute;
  20 + top:0;
  21 + left:50%;
  22 + margin-left:-.7em;
  23 + border:solid .13em white;
  24 + border-radius:10em;
  25 + width:1.4em;
  26 + height:1.4em;
  27 + line-height:1.3em;
  28 + border-color:inherit;
  29 + transition:transform .5s ease-in;
  30 +}
  31 +.arrow:hover{
  32 + color:pink;
  33 + border-color:pink;
  34 +}
@@ -25,7 +25,7 @@ export class Marker { @@ -25,7 +25,7 @@ export class Marker {
25 this.leafletMarker.setIcon(iconInfo.icon); 25 this.leafletMarker.setIcon(iconInfo.icon);
26 if (settings.showLabel) { 26 if (settings.showLabel) {
27 this.tooltipOffset = [0, -iconInfo.size[1] + 10]; 27 this.tooltipOffset = [0, -iconInfo.size[1] + 10];
28 - this.updateMarkerLabel(settings) 28 + // this.updateMarkerLabel(settings)
29 } 29 }
30 30
31 this.leafletMarker.addTo(map) 31 this.leafletMarker.addTo(map)
@@ -55,8 +55,6 @@ export class Marker { @@ -55,8 +55,6 @@ export class Marker {
55 this.leafletMarker.setLatLng(position); 55 this.leafletMarker.setLatLng(position);
56 } 56 }
57 57
58 -  
59 -  
60 updateMarkerLabel(settings) { 58 updateMarkerLabel(settings) {
61 59
62 function getText(template, data) { 60 function getText(template, data) {
@@ -74,7 +72,7 @@ export class Marker { @@ -74,7 +72,7 @@ export class Marker {
74 return res; 72 return res;
75 } 73 }
76 74
77 - 75 +
78 this.leafletMarker.unbindTooltip(); 76 this.leafletMarker.unbindTooltip();
79 if (settings.showLabel) { 77 if (settings.showLabel) {
80 if (settings.useLabelFunction) { 78 if (settings.useLabelFunction) {
@@ -104,9 +102,20 @@ export class Marker { @@ -104,9 +102,20 @@ export class Marker {
104 } 102 }
105 103
106 createMarkerIcon(onMarkerIconReady) { 104 createMarkerIcon(onMarkerIconReady) {
107 - const currentImage = this.settings.useMarkerImageFunction ?  
108 - safeExecute(this.settings.markerImageFunction, [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;  
109 - // var opMap = this; 105 +
  106 + if (this.settings.icon) {
  107 + onMarkerIconReady({
  108 + size: [30,30],
  109 + icon: this.settings.icon,
  110 + });
  111 + return;
  112 + }
  113 +
  114 + let currentImage = this.settings.useMarkerImageFunction ?
  115 + safeExecute(this.settings.markerImageFunction,
  116 + [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
  117 +
  118 +
110 if (currentImage && currentImage.url) { 119 if (currentImage && currentImage.url) {
111 aspectCache(currentImage.url).subscribe( 120 aspectCache(currentImage.url).subscribe(
112 (aspect) => { 121 (aspect) => {
@@ -856,4 +856,373 @@ export const mapProviderSchema = @@ -856,4 +856,373 @@ export const mapProviderSchema =
856 ] 856 ]
857 } 857 }
858 ] 858 ]
859 -};  
  859 +};
  860 +
  861 +
  862 +export const tripAnimationSchema = {
  863 + "schema": {
  864 + "title": "Openstreet Map Configuration",
  865 + "type": "object",
  866 + "properties": {
  867 + "normalizationStep": {
  868 + "title": "Normalization data step (ms)",
  869 + "type": "number",
  870 + "default": 1000
  871 + },
  872 + "latKeyName": {
  873 + "title": "Latitude key name",
  874 + "type": "string",
  875 + "default": "latitude"
  876 + },
  877 + "lngKeyName": {
  878 + "title": "Longitude key name",
  879 + "type": "string",
  880 + "default": "longitude"
  881 + },
  882 + "polKeyName": {
  883 + "title": "Polygon key name",
  884 + "type": "string",
  885 + "default": "coordinates"
  886 + },
  887 + "showLabel": {
  888 + "title": "Show label",
  889 + "type": "boolean",
  890 + "default": true
  891 + },
  892 + "label": {
  893 + "title": "Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )",
  894 + "type": "string",
  895 + "default": "${entityName}"
  896 + },
  897 + "useLabelFunction": {
  898 + "title": "Use label function",
  899 + "type": "boolean",
  900 + "default": false
  901 + },
  902 + "labelFunction": {
  903 + "title": "Label function: f(data, dsData, dsIndex)",
  904 + "type": "string"
  905 + },
  906 + "showTooltip": {
  907 + "title": "Show tooltip",
  908 + "type": "boolean",
  909 + "default": true
  910 + },
  911 + "tooltipColor": {
  912 + "title": "Tooltip background color",
  913 + "type": "string",
  914 + "default": "#fff"
  915 + },
  916 + "tooltipFontColor": {
  917 + "title": "Tooltip font color",
  918 + "type": "string",
  919 + "default": "#000"
  920 + },
  921 + "tooltipOpacity": {
  922 + "title": "Tooltip opacity (0-1)",
  923 + "type": "number",
  924 + "default": 1
  925 + },
  926 + "tooltipPattern": {
  927 + "title": "Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
  928 + "type": "string",
  929 + "default": "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}"
  930 + },
  931 + "useTooltipFunction": {
  932 + "title": "Use tooltip function",
  933 + "type": "boolean",
  934 + "default": false
  935 + },
  936 + "tooltipFunction": {
  937 + "title": "Tooltip function: f(data, dsData, dsIndex)",
  938 + "type": "string"
  939 + },
  940 + "color": {
  941 + "title": "Path color",
  942 + "type": "string"
  943 + },
  944 + "strokeWeight": {
  945 + "title": "Stroke weight",
  946 + "type": "number",
  947 + "default": 2
  948 + },
  949 + "strokeOpacity": {
  950 + "title": "Stroke opacity",
  951 + "type": "number",
  952 + "default": 1
  953 + },
  954 + "useColorFunction": {
  955 + "title": "Use path color function",
  956 + "type": "boolean",
  957 + "default": false
  958 + },
  959 + "colorFunction": {
  960 + "title": "Path color function: f(data, dsData, dsIndex)",
  961 + "type": "string"
  962 + },
  963 + "usePolylineDecorator": {
  964 + "title": "Use path decorator",
  965 + "type": "boolean",
  966 + "default": false
  967 + },
  968 + "decoratorSymbol": {
  969 + "title": "Decorator symbol",
  970 + "type": "string",
  971 + "default": "arrowHead"
  972 + },
  973 + "decoratorSymbolSize": {
  974 + "title": "Decorator symbol size (px)",
  975 + "type": "number",
  976 + "default": 10
  977 + },
  978 + "useDecoratorCustomColor": {
  979 + "title": "Use path decorator custom color",
  980 + "type": "boolean",
  981 + "default": false
  982 + },
  983 + "decoratorCustomColor": {
  984 + "title": "Decorator custom color",
  985 + "type": "string",
  986 + "default": "#000"
  987 + },
  988 + "decoratorOffset": {
  989 + "title": "Decorator offset",
  990 + "type": "string",
  991 + "default": "20px"
  992 + },
  993 + "endDecoratorOffset": {
  994 + "title": "End decorator offset",
  995 + "type": "string",
  996 + "default": "20px"
  997 + },
  998 + "decoratorRepeat": {
  999 + "title": "Decorator repeat",
  1000 + "type": "string",
  1001 + "default": "20px"
  1002 + },
  1003 + "showPolygon": {
  1004 + "title": "Show polygon",
  1005 + "type": "boolean",
  1006 + "default": false
  1007 + },
  1008 + "polygonTooltipPattern": {
  1009 + "title": "Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
  1010 + "type": "string",
  1011 + "default": "<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}"
  1012 + },
  1013 + "usePolygonTooltipFunction": {
  1014 + "title": "Use polygon tooltip function",
  1015 + "type": "boolean",
  1016 + "default": false
  1017 + },
  1018 + "polygonTooltipFunction": {
  1019 + "title": "Polygon tooltip function: f(data, dsData, dsIndex)",
  1020 + "type": "string"
  1021 + },
  1022 + "polygonColor": {
  1023 + "title": "Polygon color",
  1024 + "type": "string"
  1025 + },
  1026 + "polygonOpacity": {
  1027 + "title": "Polygon opacity",
  1028 + "type": "number",
  1029 + "default": 0.5
  1030 + },
  1031 + "polygonStrokeColor": {
  1032 + "title": "Polygon border color",
  1033 + "type": "string"
  1034 + },
  1035 + "polygonStrokeOpacity": {
  1036 + "title": "Polygon border opacity",
  1037 + "type": "number",
  1038 + "default": 1
  1039 + },
  1040 + "polygonStrokeWeight": {
  1041 + "title": "Polygon border weight",
  1042 + "type": "number",
  1043 + "default": 1
  1044 + },
  1045 + "usePolygonColorFunction": {
  1046 + "title": "Use polygon color function",
  1047 + "type": "boolean",
  1048 + "default": false
  1049 + },
  1050 + "polygonColorFunction": {
  1051 + "title": "Polygon Color function: f(data, dsData, dsIndex)",
  1052 + "type": "string"
  1053 + },
  1054 + "showPoints": {
  1055 + "title": "Show points",
  1056 + "type": "boolean",
  1057 + "default": false
  1058 + },
  1059 + "pointColor": {
  1060 + "title": "Point color",
  1061 + "type": "string"
  1062 + },
  1063 + "pointSize": {
  1064 + "title": "Point size (px)",
  1065 + "type": "number",
  1066 + "default": 10
  1067 + },
  1068 + "usePointAsAnchor": {
  1069 + "title": "Use point as anchor",
  1070 + "type": "boolean",
  1071 + "default": false
  1072 + },
  1073 + "pointAsAnchorFunction": {
  1074 + "title": "Point as anchor function: f(data, dsData, dsIndex)",
  1075 + "type": "string"
  1076 + },
  1077 + "pointTooltipOnRightPanel": {
  1078 + "title": "Independant point tooltip",
  1079 + "type": "boolean",
  1080 + "default": true
  1081 + },
  1082 + "autocloseTooltip": {
  1083 + "title": "Auto-close point popup",
  1084 + "type": "boolean",
  1085 + "default": true
  1086 + },
  1087 + "markerImage": {
  1088 + "title": "Custom marker image",
  1089 + "type": "string"
  1090 + },
  1091 + "markerImageSize": {
  1092 + "title": "Custom marker image size (px)",
  1093 + "type": "number",
  1094 + "default": 34
  1095 + },
  1096 + "rotationAngle": {
  1097 + "title": "Set additional rotation angle for marker (deg)",
  1098 + "type": "number",
  1099 + "default": 180
  1100 + },
  1101 + "useMarkerImageFunction": {
  1102 + "title": "Use marker image function",
  1103 + "type": "boolean",
  1104 + "default": false
  1105 + },
  1106 + "markerImageFunction": {
  1107 + "title": "Marker image function: f(data, images, dsData, dsIndex)",
  1108 + "type": "string"
  1109 + },
  1110 + "markerImages": {
  1111 + "title": "Marker images",
  1112 + "type": "array",
  1113 + "items": {
  1114 + "title": "Marker image",
  1115 + "type": "string"
  1116 + }
  1117 + }
  1118 + },
  1119 + "required": []
  1120 + },
  1121 + "form": [{
  1122 + "key": "mapProvider",
  1123 + "type": "rc-select",
  1124 + "multiple": false,
  1125 + "items": [{
  1126 + "value": "OpenStreetMap.Mapnik",
  1127 + "label": "OpenStreetMap.Mapnik (Default)"
  1128 + }, {
  1129 + "value": "OpenStreetMap.BlackAndWhite",
  1130 + "label": "OpenStreetMap.BlackAndWhite"
  1131 + }, {
  1132 + "value": "OpenStreetMap.HOT",
  1133 + "label": "OpenStreetMap.HOT"
  1134 + }, {
  1135 + "value": "Esri.WorldStreetMap",
  1136 + "label": "Esri.WorldStreetMap"
  1137 + }, {
  1138 + "value": "Esri.WorldTopoMap",
  1139 + "label": "Esri.WorldTopoMap"
  1140 + }, {
  1141 + "value": "CartoDB.Positron",
  1142 + "label": "CartoDB.Positron"
  1143 + }, {
  1144 + "value": "CartoDB.DarkMatter",
  1145 + "label": "CartoDB.DarkMatter"
  1146 + }]
  1147 + }, "normalizationStep", "latKeyName", "lngKeyName", "polKeyName", "showLabel", "label", "useLabelFunction", {
  1148 + "key": "labelFunction",
  1149 + "type": "javascript"
  1150 + }, "showTooltip", {
  1151 + "key": "tooltipColor",
  1152 + "type": "color"
  1153 + }, {
  1154 + "key": "tooltipFontColor",
  1155 + "type": "color"
  1156 + }, "tooltipOpacity", {
  1157 + "key": "tooltipPattern",
  1158 + "type": "textarea"
  1159 + }, "useTooltipFunction", {
  1160 + "key": "tooltipFunction",
  1161 + "type": "javascript"
  1162 + }, {
  1163 + "key": "color",
  1164 + "type": "color"
  1165 + }, "useColorFunction", {
  1166 + "key": "colorFunction",
  1167 + "type": "javascript"
  1168 + }, "usePolylineDecorator", {
  1169 + "key": "decoratorSymbol",
  1170 + "type": "rc-select",
  1171 + "multiple": false,
  1172 + "items": [{
  1173 + "value": "arrowHead",
  1174 + "label": "Arrow"
  1175 + }, {
  1176 + "value": "dash",
  1177 + "label": "Dash"
  1178 + }]
  1179 + }, "decoratorSymbolSize", "useDecoratorCustomColor", {
  1180 + "key": "decoratorCustomColor",
  1181 + "type": "color"
  1182 + }, {
  1183 + "key": "decoratorOffset",
  1184 + "type": "textarea"
  1185 + }, {
  1186 + "key": "endDecoratorOffset",
  1187 + "type": "textarea"
  1188 + }, {
  1189 + "key": "decoratorRepeat",
  1190 + "type": "textarea"
  1191 + }, "strokeWeight", "strokeOpacity", "showPolygon", {
  1192 + "key": "polygonTooltipPattern",
  1193 + "type": "textarea"
  1194 + }, "usePolygonTooltipFunction", {
  1195 + "key": "polygonTooltipFunction",
  1196 + "type": "javascript"
  1197 + }, {
  1198 + "key": "polygonColor",
  1199 + "type": "color"
  1200 + }, "polygonOpacity", {
  1201 + "key": "polygonStrokeColor",
  1202 + "type": "color"
  1203 + }, "polygonStrokeOpacity", "polygonStrokeWeight", "usePolygonColorFunction", {
  1204 + "key": "polygonColorFunction",
  1205 + "type": "javascript"
  1206 + }, "showPoints", {
  1207 + "key": "pointColor",
  1208 + "type": "color"
  1209 + }, "pointSize", "usePointAsAnchor", {
  1210 + "key": "pointAsAnchorFunction",
  1211 + "type": "javascript"
  1212 + }, "pointTooltipOnRightPanel", "autocloseTooltip", {
  1213 + "key": "markerImage",
  1214 + "type": "image"
  1215 + }, "markerImageSize", "rotationAngle", "useMarkerImageFunction",
  1216 + {
  1217 + "key": "markerImageFunction",
  1218 + "type": "javascript"
  1219 + }, {
  1220 + "key": "markerImages",
  1221 + "items": [
  1222 + {
  1223 + "key": "markerImages[]",
  1224 + "type": "image"
  1225 + }
  1226 + ]
  1227 + }]
  1228 +}
1 <div class="map" #map ></div> 1 <div class="map" #map ></div>
2 -<div>{{historicalData?.lenth}}</div>  
3 -<tb-history-selector *ngIf="historicalData" [settings]="ctx.settings" [intervals]="intervals" 2 +<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"
4 (onTimeUpdated)="timeUpdated($event)"></tb-history-selector> 3 (onTimeUpdated)="timeUpdated($event)"></tb-history-selector>
1 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; 1 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core';
2 -import { MapWidgetController } from '../lib/maps/map-widget2'; 2 +import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
3 import { MapProviders } from '../lib/maps/map-models'; 3 import { MapProviders } from '../lib/maps/map-models';
4 import { parseArray } from '@app/core/utils'; 4 import { parseArray } from '@app/core/utils';
5 import { interpolateArray } from '../lib/maps/maps-utils'; 5 import { interpolateArray } from '../lib/maps/maps-utils';
  6 +import tinycolor from "tinycolor2";
  7 +import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
  8 +import { tripAnimationSchema } from '../lib/maps/schemes';
  9 +import L from 'leaflet';
6 10
7 @Component({ 11 @Component({
8 selector: 'trip-animation', 12 selector: 'trip-animation',
@@ -18,35 +22,45 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -18,35 +22,45 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
18 mapWidget: MapWidgetController; 22 mapWidget: MapWidgetController;
19 historicalData; 23 historicalData;
20 intervals; 24 intervals;
21 - normalizationStep = 500; 25 + normalizationStep = 1000;
22 interpolatedData = []; 26 interpolatedData = [];
23 - 27 + widgetConfig;
  28 + settings;
24 29
25 constructor(private cd: ChangeDetectorRef) { } 30 constructor(private cd: ChangeDetectorRef) { }
26 31
27 ngOnInit(): void { 32 ngOnInit(): void {
  33 + this.widgetConfig = this.ctx.widgetConfig;
  34 + const settings = {
  35 + normalizationStep: 1000,
  36 + buttonColor: tinycolor(this.widgetConfig.color).setAlpha(0.54).toRgbString(),
  37 + disabledButtonColor: tinycolor(this.widgetConfig.color).setAlpha(0.3).toRgbString(),
  38 + rotationAngle: 0
  39 + }
  40 + this.settings = { ...settings, ...this.ctx.settings };
  41 + //this.ctx.settings = settings;
  42 + console.log("TripAnimationComponent -> ngOnInit -> this.ctx.settings", this.ctx.settings)
28 let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; 43 let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
29 if (subscription) subscription.callbacks.onDataUpdated = (updated) => { 44 if (subscription) subscription.callbacks.onDataUpdated = (updated) => {
30 this.historicalData = parseArray(this.ctx.data); 45 this.historicalData = parseArray(this.ctx.data);
31 - this.historicalData.forEach(el => {  
32 - console.log("TripAnimationComponent -> if -> el", el) 46 + this.historicalData.forEach(ds => ds.forEach(el => {
33 el.longitude += (Math.random() - 0.5) 47 el.longitude += (Math.random() - 0.5)
34 el.latitude += (Math.random() - 0.5) 48 el.latitude += (Math.random() - 0.5)
35 - }); 49 + }));
36 this.calculateIntervals(); 50 this.calculateIntervals();
  51 + this.timeUpdated(this.intervals[0]);
37 this.cd.detectChanges(); 52 this.cd.detectChanges();
  53 + this.mapWidget.map.map.invalidateSize();
38 } 54 }
39 } 55 }
40 56
41 ngAfterViewInit() { 57 ngAfterViewInit() {
42 this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); 58 this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
43 - this.mapWidget.data  
44 } 59 }
45 60
46 timeUpdated(time) { 61 timeUpdated(time) {
47 - //this.mapWidget.ma  
48 - const currentPosition = this.interpolatedData.map(dataSource=>dataSource[time]);  
49 - console.log("TripAnimationComponent -> timeUpdated -> currentPosition", currentPosition) 62 + const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
  63 + this.mapWidget.map.updateMarkers(currentPosition);
50 } 64 }
51 65
52 calculateIntervals() { 66 calculateIntervals() {
@@ -57,9 +71,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -57,9 +71,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
57 } 71 }
58 this.intervals.push(dataSource[dataSource.length - 1]?.time); 72 this.intervals.push(dataSource[dataSource.length - 1]?.time);
59 this.interpolatedData[index] = interpolateArray(dataSource, this.intervals); 73 this.interpolatedData[index] = interpolateArray(dataSource, this.intervals);
60 - console.log("TripAnimationComponent -> calculateIntervals -> this.intervals", this.intervals)  
61 -  
62 }); 74 });
63 } 75 }
64 76
  77 + static getSettingsSchema() {
  78 + let schema = initSchema();
  79 + addToSchema(schema, TbMapWidgetV2.getProvidersSchema());
  80 + addGroupInfo(schema, "Map Provider Settings");
  81 + addToSchema(schema, tripAnimationSchema);
  82 + addGroupInfo(schema, "Trip Animation Settings");
  83 + return schema;
  84 + }
65 } 85 }
  86 +
  87 +export let TbTripAnimationWidget = TripAnimationComponent;
1 import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; 1 import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
2 -import { interval } from 'rxjs'; 2 +import { interval, Subscription } from 'rxjs';
3 import { filter, tap } from 'rxjs/operators'; 3 import { filter, tap } from 'rxjs/operators';
4 4
5 @Component({ 5 @Component({
@@ -34,23 +34,25 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @@ -34,23 +34,25 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
34 34
35 play() { 35 play() {
36 this.playing = true; 36 this.playing = true;
37 - this.interval = interval(1000 / this.speed)  
38 - .pipe(  
39 - filter(() => this.playing),  
40 - tap(() => this.index++)).subscribe(() => {  
41 - if (this.index < this.maxTimeIndex) {  
42 - this.cd.detectChanges();  
43 - this.onTimeUpdated.emit(this.intervals[this.index]);  
44 - }  
45 - else {  
46 - this.interval.complete();  
47 - }  
48 - }, err => {  
49 - console.log(err);  
50 - }, () => {  
51 - this.index = this.minTimeIndex;  
52 - this.playing = false;  
53 - }) 37 + if (!this.interval)
  38 + this.interval = interval(1000 / this.speed)
  39 + .pipe(
  40 + filter(() => this.playing),
  41 + tap(() => this.index++)).subscribe(() => {
  42 + if (this.index < this.maxTimeIndex) {
  43 + this.cd.detectChanges();
  44 + this.onTimeUpdated.emit(this.intervals[this.index]);
  45 + }
  46 + else {
  47 + this.interval.complete();
  48 + }
  49 + }, err => {
  50 + console.log(err);
  51 + }, () => {
  52 + this.index = this.minTimeIndex;
  53 + this.playing = false;
  54 + this.interval = null;
  55 + });
54 } 56 }
55 57
56 pause() { 58 pause() {
@@ -93,6 +93,7 @@ import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radi @@ -93,6 +93,7 @@ import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radi
93 import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge'; 93 import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge';
94 import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge'; 94 import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge';
95 import { TbMapWidgetV2 } from '@home/components/widget/lib/maps/map-widget2'; 95 import { TbMapWidgetV2 } from '@home/components/widget/lib/maps/map-widget2';
  96 +import { TbTripAnimationWidget } from '@app/modules/home/components/widget/trip-animation/trip-animation.component';
96 97
97 import * as tinycolor_ from 'tinycolor2'; 98 import * as tinycolor_ from 'tinycolor2';
98 99
@@ -106,3 +107,5 @@ const tinycolor = tinycolor_; @@ -106,3 +107,5 @@ const tinycolor = tinycolor_;
106 (window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge; 107 (window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge;
107 (window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge; 108 (window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge;
108 (window as any).TbMapWidgetV2 = TbMapWidgetV2; 109 (window as any).TbMapWidgetV2 = TbMapWidgetV2;
  110 +(window as any).TbTripAnimationWidget = TbTripAnimationWidget;
  111 +console.log("TbTripAnimationWidget", TbTripAnimationWidget)