Commit 5e65f2a48a109b7359f29c8762fe419ee1b52d58
1 parent
5535c33d
trip-animation map provider & custom markers
Showing
15 changed files
with
630 additions
and
166 deletions
... | ... | @@ -38,7 +38,8 @@ |
38 | 38 | "src/app/shared/components/json-form/react/json-form.scss", |
39 | 39 | "node_modules/rc-select/assets/index.less", |
40 | 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 | 44 | "stylePreprocessorOptions": { |
44 | 45 | "includePaths": [ | ... | ... |
ui-ngx/src/app/core/schema-utils.ts
0 → 100644
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 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -452,54 +452,55 @@ export function aspectCache(imageUrl: string): Observable<number> { |
452 | 452 | export function parseArray(input: any[]): any[] { |
453 | 453 | let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value(); |
454 | 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 | 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 | 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 | 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 | 494 | export function safeExecute(func: Function, params = []) { |
494 | 495 | let res = null; |
495 | 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 | 505 | return res; |
505 | 506 | } |
... | ... | @@ -507,13 +508,13 @@ export function safeExecute(func: Function, params = []) { |
507 | 508 | export function parseFunction(source: string, params: string[] = []): Function { |
508 | 509 | let res = null; |
509 | 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 | 519 | return res; |
519 | 520 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -122,6 +122,11 @@ export default abstract class LeafletMap { |
122 | 122 | ////Markers |
123 | 123 | updateMarkers(markersData) { |
124 | 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 | 130 | if (this.markers.get(data.aliasName)) { |
126 | 131 | this.updateMarker(data.aliasName, data, markersData, this.options as MarkerSettings) |
127 | 132 | } | ... | ... |
... | ... | @@ -18,6 +18,7 @@ export interface MapOptions { |
18 | 18 | useDefaultCenterPosition?: boolean, |
19 | 19 | gmDefaultMapType?: string, |
20 | 20 | useLabelFunction: string; |
21 | + icon?: any; | |
21 | 22 | } |
22 | 23 | |
23 | 24 | export enum MapProviders { |
... | ... | @@ -29,6 +30,7 @@ export enum MapProviders { |
29 | 30 | } |
30 | 31 | |
31 | 32 | export interface MarkerSettings extends MapOptions { |
33 | + icon?: any; | |
32 | 34 | showLabel?: boolean, |
33 | 35 | draggable?: boolean, |
34 | 36 | displayTooltip?: boolean, | ... | ... |
... | ... | @@ -8,7 +8,8 @@ export interface MapWidgetInterface { |
8 | 8 | } |
9 | 9 | |
10 | 10 | export interface MapWidgetStaticInterface { |
11 | - settingsSchema(mapProvider, drawRoutes): Object; | |
11 | + settingsSchema(mapProvider?, drawRoutes?): Object; | |
12 | + getProvidersSchema():Object | |
12 | 13 | dataKeySettingsSchema(): Object; |
13 | 14 | actionSources(): Object; |
14 | 15 | } | ... | ... |
... | ... | @@ -15,6 +15,7 @@ import { |
15 | 15 | import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; |
16 | 16 | import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; |
17 | 17 | import { parseFunction, parseArray, parseData } from '@app/core/utils'; |
18 | +import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@app/core/schema-utils'; | |
18 | 19 | |
19 | 20 | export class MapWidgetController implements MapWidgetInterface { |
20 | 21 | |
... | ... | @@ -73,7 +74,7 @@ export class MapWidgetController implements MapWidgetInterface { |
73 | 74 | this.map.updateMarkers(parseData(this.data)); |
74 | 75 | } |
75 | 76 | |
76 | - public updateHistoryData(dataSources){ | |
77 | + public updateHistoryData(dataSources) { | |
77 | 78 | dataSources.map() |
78 | 79 | } |
79 | 80 | |
... | ... | @@ -93,90 +94,28 @@ export class MapWidgetController implements MapWidgetInterface { |
93 | 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 | 103 | public static settingsSchema(mapProvider, drawRoutes): Object { |
97 | 104 | //const providerInfo = providerSets[mapProvider]; |
98 | 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 | 112 | if (drawRoutes) { |
174 | - addToSchema(routeMapSettingsSchema); | |
175 | - addGroupInfo("Route Map Settings"); | |
113 | + addToSchema(schema, routeMapSettingsSchema); | |
114 | + addGroupInfo(schema, "Route Map Settings"); | |
176 | 115 | } else if (mapProvider !== 'image-map') { |
177 | 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 | 120 | console.log(11, schema); |
182 | 121 | |
... | ... | @@ -243,7 +182,7 @@ const defaultSettings = { |
243 | 182 | latKeyName: 'latitude', |
244 | 183 | lngKeyName: 'longitude', |
245 | 184 | polygonKeyName: 'coordinates', |
246 | - showLabel: true, | |
185 | + showLabel: false, | |
247 | 186 | label: "${entityName}", |
248 | 187 | showTooltip: false, |
249 | 188 | useDefaultCenterPosition: false, | ... | ... |
... | ... | @@ -30,6 +30,12 @@ export function interpolateArray(originData, interpolatedIntervals) { |
30 | 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 | 39 | const result = {}; |
34 | 40 | |
35 | 41 | for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { |
... | ... | @@ -37,10 +43,16 @@ export function interpolateArray(originData, interpolatedIntervals) { |
37 | 43 | while (originData[i].time < currentTime) i++; |
38 | 44 | const before = originData[i - 1]; |
39 | 45 | const after = originData[i]; |
40 | - result[currentTime] = (interpolateOnPointSegment( | |
46 | + const interpolation = interpolateOnPointSegment( | |
41 | 47 | new L.Point(before.latitude, before.longitude), |
42 | 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 | 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 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -25,7 +25,7 @@ export class Marker { |
25 | 25 | this.leafletMarker.setIcon(iconInfo.icon); |
26 | 26 | if (settings.showLabel) { |
27 | 27 | this.tooltipOffset = [0, -iconInfo.size[1] + 10]; |
28 | - this.updateMarkerLabel(settings) | |
28 | + // this.updateMarkerLabel(settings) | |
29 | 29 | } |
30 | 30 | |
31 | 31 | this.leafletMarker.addTo(map) |
... | ... | @@ -55,8 +55,6 @@ export class Marker { |
55 | 55 | this.leafletMarker.setLatLng(position); |
56 | 56 | } |
57 | 57 | |
58 | - | |
59 | - | |
60 | 58 | updateMarkerLabel(settings) { |
61 | 59 | |
62 | 60 | function getText(template, data) { |
... | ... | @@ -74,7 +72,7 @@ export class Marker { |
74 | 72 | return res; |
75 | 73 | } |
76 | 74 | |
77 | - | |
75 | + | |
78 | 76 | this.leafletMarker.unbindTooltip(); |
79 | 77 | if (settings.showLabel) { |
80 | 78 | if (settings.useLabelFunction) { |
... | ... | @@ -104,9 +102,20 @@ export class Marker { |
104 | 102 | } |
105 | 103 | |
106 | 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 | 119 | if (currentImage && currentImage.url) { |
111 | 120 | aspectCache(currentImage.url).subscribe( |
112 | 121 | (aspect) => { | ... | ... |
... | ... | @@ -856,4 +856,373 @@ export const mapProviderSchema = |
856 | 856 | ] |
857 | 857 | } |
858 | 858 | ] |
859 | -}; | |
\ No newline at end of file | ||
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 | +} | |
\ No newline at end of file | ... | ... |
1 | 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 | 3 | (onTimeUpdated)="timeUpdated($event)"></tb-history-selector> | ... | ... |
1 | 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 | 3 | import { MapProviders } from '../lib/maps/map-models'; |
4 | 4 | import { parseArray } from '@app/core/utils'; |
5 | 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 | 11 | @Component({ |
8 | 12 | selector: 'trip-animation', |
... | ... | @@ -18,35 +22,45 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { |
18 | 22 | mapWidget: MapWidgetController; |
19 | 23 | historicalData; |
20 | 24 | intervals; |
21 | - normalizationStep = 500; | |
25 | + normalizationStep = 1000; | |
22 | 26 | interpolatedData = []; |
23 | - | |
27 | + widgetConfig; | |
28 | + settings; | |
24 | 29 | |
25 | 30 | constructor(private cd: ChangeDetectorRef) { } |
26 | 31 | |
27 | 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 | 43 | let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; |
29 | 44 | if (subscription) subscription.callbacks.onDataUpdated = (updated) => { |
30 | 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 | 47 | el.longitude += (Math.random() - 0.5) |
34 | 48 | el.latitude += (Math.random() - 0.5) |
35 | - }); | |
49 | + })); | |
36 | 50 | this.calculateIntervals(); |
51 | + this.timeUpdated(this.intervals[0]); | |
37 | 52 | this.cd.detectChanges(); |
53 | + this.mapWidget.map.map.invalidateSize(); | |
38 | 54 | } |
39 | 55 | } |
40 | 56 | |
41 | 57 | ngAfterViewInit() { |
42 | 58 | this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); |
43 | - this.mapWidget.data | |
44 | 59 | } |
45 | 60 | |
46 | 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 | 66 | calculateIntervals() { |
... | ... | @@ -57,9 +71,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { |
57 | 71 | } |
58 | 72 | this.intervals.push(dataSource[dataSource.length - 1]?.time); |
59 | 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; | |
\ No newline at end of file | ... | ... |
1 | 1 | import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; |
2 | -import { interval } from 'rxjs'; | |
2 | +import { interval, Subscription } from 'rxjs'; | |
3 | 3 | import { filter, tap } from 'rxjs/operators'; |
4 | 4 | |
5 | 5 | @Component({ |
... | ... | @@ -34,23 +34,25 @@ export class HistorySelectorComponent implements OnInit, OnChanges { |
34 | 34 | |
35 | 35 | play() { |
36 | 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 | 58 | pause() { | ... | ... |
... | ... | @@ -93,6 +93,7 @@ import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radi |
93 | 93 | import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge'; |
94 | 94 | import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge'; |
95 | 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 | 98 | import * as tinycolor_ from 'tinycolor2'; |
98 | 99 | |
... | ... | @@ -106,3 +107,5 @@ const tinycolor = tinycolor_; |
106 | 107 | (window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge; |
107 | 108 | (window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge; |
108 | 109 | (window as any).TbMapWidgetV2 = TbMapWidgetV2; |
110 | +(window as any).TbTripAnimationWidget = TbTripAnimationWidget; | |
111 | +console.log("TbTripAnimationWidget", TbTripAnimationWidget) | ... | ... |