Commit 5535c33d0eabd9e0b3c10e8094c584126e5f8b1d

Authored by Artem Halushko
1 parent b9db41bd

WIP on history control and route interpolation

... ... @@ -1900,7 +1900,8 @@
1900 1900 "@types/lodash": {
1901 1901 "version": "4.14.149",
1902 1902 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
1903   - "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ=="
  1903 + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
  1904 + "dev": true
1904 1905 },
1905 1906 "@types/minimatch": {
1906 1907 "version": "3.0.3",
... ... @@ -8263,6 +8264,14 @@
8263 8264 "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz",
8264 8265 "integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ=="
8265 8266 },
  8267 + "leaflet-geometryutil": {
  8268 + "version": "0.9.3",
  8269 + "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.9.3.tgz",
  8270 + "integrity": "sha512-Wi6YvfNx/Xu9q35AEfXpsUXmIFLen/MO+C2qimxHRnjyeyOxBhdcZa6kSiReaOX0cGK7yQInqrzz0dkIqZ8Dpg==",
  8271 + "requires": {
  8272 + "leaflet": "1.6.0"
  8273 + }
  8274 + },
8266 8275 "leaflet-providers": {
8267 8276 "version": "1.9.1",
8268 8277 "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.9.1.tgz",
... ...
... ... @@ -57,6 +57,7 @@
57 57 "jstree": "^3.3.9",
58 58 "jstree-bootstrap-theme": "^1.0.1",
59 59 "leaflet": "^1.6.0",
  60 + "leaflet-geometryutil": "^0.9.3",
60 61 "leaflet-providers": "^1.9.1",
61 62 "leaflet.gridlayer.googlemutant": "^0.8.0",
62 63 "leaflet.markercluster": "^1.4.1",
... ...
... ... @@ -451,13 +451,13 @@ export function aspectCache(imageUrl: string): Observable<number> {
451 451
452 452 export function parseArray(input: any[]): any[] {
453 453 let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
454   - console.log("alliases", alliases)
455 454 return alliases.map((alliasArray, dsIndex) =>
456 455 alliasArray[0].data.map((el, i) => {
457 456 const obj = {
458 457 aliasName: alliasArray[0]?.datasource?.aliasName,
459 458 $datasource: alliasArray[0]?.datasource,
460   - dsIndex: dsIndex
  459 + dsIndex: dsIndex,
  460 + time: el[0]
461 461 };
462 462 alliasArray.forEach(el => {
463 463 obj[el?.dataKey?.label] = el?.data[i][1];
... ...
... ... @@ -73,6 +73,10 @@ export class MapWidgetController implements MapWidgetInterface {
73 73 this.map.updateMarkers(parseData(this.data));
74 74 }
75 75
  76 + public updateHistoryData(dataSources){
  77 + dataSources.map()
  78 + }
  79 +
76 80 onDataUpdated() {
77 81 }
78 82
... ...
1 1 import L from 'leaflet';
  2 +import { interpolateOnPointSegment } from 'leaflet-geometryutil';
2 3 import _ from 'lodash';
3 4
4 5 export function createTooltip(target, settings, targetArgs?) {
5   - var popup = L.popup();
  6 + const popup = L.popup();
6 7 popup.setContent('');
7 8 target.bindPopup(popup, { autoClose: settings.autocloseTooltip, closeOnClick: false });
8 9 if (settings.displayTooltipAction == 'hover') {
... ... @@ -21,3 +22,27 @@ export function createTooltip(target, settings, targetArgs?) {
21 22 dsIndex: settings.dsIndex
22 23 };
23 24 }
  25 +
  26 +
  27 +export function interpolateArray(originData, interpolatedIntervals) {
  28 +
  29 + const getRatio = (firsMoment, secondMoment, intermediateMoment) => {
  30 + return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
  31 + };
  32 +
  33 + const result = {};
  34 +
  35 + for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) {
  36 + const currentTime = interpolatedIntervals[j];
  37 + while (originData[i].time < currentTime) i++;
  38 + const before = originData[i - 1];
  39 + const after = originData[i];
  40 + result[currentTime] = (interpolateOnPointSegment(
  41 + new L.Point(before.latitude, before.longitude),
  42 + new L.Point(after.latitude, after.longitude),
  43 + getRatio(before.time, after.time, currentTime)));
  44 + j++;
  45 + }
  46 +
  47 + return result;
  48 +};
... ...
1 1 <div class="map" #map ></div>
2   -<tb-history-selector [settings]="ctx.settings"></tb-history-selector>
\ No newline at end of file
  2 +<div>{{historicalData?.lenth}}</div>
  3 +<tb-history-selector *ngIf="historicalData" [settings]="ctx.settings" [intervals]="intervals"
  4 + (onTimeUpdated)="timeUpdated($event)"></tb-history-selector>
... ...
1   -import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
  1 +import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core';
2 2 import { MapWidgetController } from '../lib/maps/map-widget2';
3 3 import { MapProviders } from '../lib/maps/map-models';
4 4 import { parseArray } from '@app/core/utils';
  5 +import { interpolateArray } from '../lib/maps/maps-utils';
5 6
6 7 @Component({
7 8 selector: 'trip-animation',
... ... @@ -15,18 +16,50 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
15 16 @ViewChild('map') mapContainer;
16 17
17 18 mapWidget: MapWidgetController;
18   - historicalData
  19 + historicalData;
  20 + intervals;
  21 + normalizationStep = 500;
  22 + interpolatedData = [];
19 23
20   - constructor() { }
  24 +
  25 + constructor(private cd: ChangeDetectorRef) { }
21 26
22 27 ngOnInit(): void {
23   - console.log(this.ctx);
24   - this.historicalData = parseArray(this.ctx.data);
25   - console.log("TripAnimationComponent -> ngOnInit -> this.historicalData",this.ctx.data, this.historicalData)
  28 + let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
  29 + if (subscription) subscription.callbacks.onDataUpdated = (updated) => {
  30 + this.historicalData = parseArray(this.ctx.data);
  31 + this.historicalData.forEach(el => {
  32 + console.log("TripAnimationComponent -> if -> el", el)
  33 + el.longitude += (Math.random() - 0.5)
  34 + el.latitude += (Math.random() - 0.5)
  35 + });
  36 + this.calculateIntervals();
  37 + this.cd.detectChanges();
  38 + }
26 39 }
27 40
28 41 ngAfterViewInit() {
29 42 this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
30 43 this.mapWidget.data
31 44 }
  45 +
  46 + timeUpdated(time) {
  47 + //this.mapWidget.ma
  48 + const currentPosition = this.interpolatedData.map(dataSource=>dataSource[time]);
  49 + console.log("TripAnimationComponent -> timeUpdated -> currentPosition", currentPosition)
  50 + }
  51 +
  52 + calculateIntervals() {
  53 + this.historicalData.forEach((dataSource, index) => {
  54 + this.intervals = [];
  55 + for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
  56 + this.intervals.push(time);
  57 + }
  58 + this.intervals.push(dataSource[dataSource.length - 1]?.time);
  59 + this.interpolatedData[index] = interpolateArray(dataSource, this.intervals);
  60 + console.log("TripAnimationComponent -> calculateIntervals -> this.intervals", this.intervals)
  61 +
  62 + });
  63 + }
  64 +
32 65 }
... ...
... ... @@ -6,34 +6,41 @@
6 6 <button mat-icon-button class="mat-icon-button" aria-label="Previous" ng-click="movePrev()">
7 7 <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_previous</mat-icon>
8 8 </button>
9   - <!-- <mat-slider ng-model="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="recalculateTrips()">
10   - </mat-slider>-->
  9 + <mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="recalculateTrips()">
  10 + </mat-slider>
11 11 <button mat-icon-button class="mat-icon-button" aria-label="Next" ng-click="moveNext()">
12 12 <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_next</mat-icon>
13 13 </button>
14 14 <button mat-icon-button class="mat-icon-button" aria-label="End" ng-click="moveEnd()">
15 15 <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">fast_forward</mat-icon>
16 16 </button>
17   - <button mat-icon-button class="mat-icon-button" aria-label="Play" ng-click="playMove(true)"
18   - ng-disabled="isPlaying">
19   - <mat-icon class="material-icons"
20   - ng-style="{'color': isPlaying ? staticSettings.disabledButtonColor : staticSettings.buttonColor}">
  17 + <!--<div class="play-pause">
  18 + <input type="checkbox" [(ngModel)]="play" value="" id="playPauseCheckbox" name="playPauseCheckbox" />
  19 + <label for="playPauseCheckbox"></label>
  20 + </div>-->
  21 + <button mat-icon-button class="mat-icon-button" aria-label="Play" >
  22 + <mat-icon (click)="play()" *ngIf="!playing" class="material-icons" ng-style="{'color': settings.buttonColor}">
21 23 play_circle_outline
22 24 </mat-icon>
  25 + <mat-icon (click)="pause()" *ngIf="playing" class="material-icons" ng-style="{'color': settings.buttonColor}">
  26 + pause_circle_outline
  27 + </mat-icon>
23 28 </button>
24   - <!-- <mat-select ng-model="speed" aria-label="Speed selector">
  29 + <!-- <mat-select [(ngModel)]="speed" aria-label="Speed selector">
25 30 <mat-option ng-value="speed" flex="1" ng-repeat="speed in speeds track by $index">{{ speed}}
26 31 </mat-option>
27 32 </mat-select>-->
28   - <button mat-icon-button class="mat-icon-button" aria-label="Stop playing" ng-click="stopPlay()"
  33 + <!-- <button mat-icon-button class="mat-icon-button" aria-label="Stop playing" ng-click="stopPlay()"
29 34 ng-disabled="!isPlaying">
30 35 <mat-icon class="material-icons"
31 36 ng-style="{'color': isPlaying ? staticSettings.buttonColor : staticSettings.disabledButtonColor}">
32 37 pause_circle_outline
33 38 </mat-icon>
34   - </button>
  39 + </button>-->
35 40 </div>
36 41 <div class="panel-timer">
37 42 <span *ngIf="animationTime">{{ animationTime | date:'medium'}}</span>
38 43 <span *ngIf="!animationTime">{{ "widget.no-data-found" | translate}}</span>
39   - </div>
\ No newline at end of file
  44 + </div>
  45 +
  46 + <div>{{index}}</div>
\ No newline at end of file
... ...
... ... @@ -13,125 +13,152 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   - .trip-animation-widget {
  16 +.trip-animation-widget {
  17 + position: relative;
  18 + width: 100%;
  19 + height: 100%;
  20 + font-size: 16px;
  21 + line-height: 24px;
17 22
  23 + .trip-animation-label-container {
  24 + height: 24px;
  25 + }
  26 +
  27 + .trip-animation-container {
18 28 position: relative;
  29 + z-index: 1;
  30 + flex: 1;
19 31 width: 100%;
20   - height: 100%;
21   - font-size: 16px;
22   - line-height: 24px;
23   -
24   - .trip-animation-label-container {
25   - height: 24px;
26   - }
27   -
28   - .trip-animation-container {
29   - position: relative;
  32 +
  33 + #trip-animation-map {
30 34 z-index: 1;
31   - flex: 1;
32 35 width: 100%;
33   -
34   - #trip-animation-map {
35   - z-index: 1;
36   - width: 100%;
37   - height: 100%;
38   -
39   - .pointsLayerMarkerIcon {
40   - border-radius: 50%;
41   - }
  36 + height: 100%;
  37 +
  38 + .pointsLayerMarkerIcon {
  39 + border-radius: 50%;
42 40 }
43   -
44   - .trip-animation-info-panel {
45   - position: absolute;
  41 + }
  42 +
  43 + .trip-animation-info-panel {
  44 + position: absolute;
  45 + top: 0;
  46 + right: 0;
  47 + z-index: 2;
  48 + pointer-events: none;
  49 +
  50 + .md-button {
46 51 top: 0;
47   - right: 0;
48   - z-index: 2;
49   - pointer-events: none;
50   -
51   - .md-button {
52   - top: 0;
53   - left: 0;
54   - width: 32px;
55   - min-width: 32px;
56   - height: 32px;
57   - min-height: 32px;
58   - padding: 0 0 2px;
59   - margin: 2px;
60   - line-height: 24px;
61   -
62   - ng-md-icon {
63   - width: 24px;
64   - height: 24px;
65   -
66   - svg {
67   - width: inherit;
68   - height: inherit;
69   - }
  52 + left: 0;
  53 + width: 32px;
  54 + min-width: 32px;
  55 + height: 32px;
  56 + min-height: 32px;
  57 + padding: 0 0 2px;
  58 + margin: 2px;
  59 + line-height: 24px;
  60 +
  61 + ng-md-icon {
  62 + width: 24px;
  63 + height: 24px;
  64 +
  65 + svg {
  66 + width: inherit;
  67 + height: inherit;
70 68 }
71 69 }
72 70 }
73   -
74   - .trip-animation-tooltip {
75   - position: absolute;
76   - top: 38px;
77   - right: 0;
78   - z-index: 2;
79   - padding: 10px;
80   - background-color: #fff;
81   - transition: .3s ease-in-out;
82   -
83   - &-hidden {
84   - transform: translateX(110%);
85   - }
  71 + }
  72 +
  73 + .trip-animation-tooltip {
  74 + position: absolute;
  75 + top: 38px;
  76 + right: 0;
  77 + z-index: 2;
  78 + padding: 10px;
  79 + background-color: #fff;
  80 + transition: 0.3s ease-in-out;
  81 +
  82 + &-hidden {
  83 + transform: translateX(110%);
86 84 }
87 85 }
88   -
89   - .trip-animation-control-panel {
90   - position: relative;
91   - box-sizing: border-box;
92   - width: 100%;
93   - padding-bottom: 16px;
94   - padding-left: 10px;
95   -
96   - md-slider-container {
97   - md-slider {
98   - min-width: 80px;
99   - }
100   -
101   - button.md-button.md-icon-button {
102   - width: 44px;
103   - min-width: 44px;
104   - height: 44px;
105   - min-height: 44px;
106   - margin: 0;
107   - line-height: 28px;
108   -
109   - md-icon {
110   - width: 28px;
111   - height: 28px;
112   - font-size: 28px;
113   -
114   - svg {
115   - width: inherit;
116   - height: inherit;
117   - }
  86 + }
  87 +
  88 + .trip-animation-control-panel {
  89 + position: relative;
  90 + box-sizing: border-box;
  91 + width: 100%;
  92 + padding-bottom: 16px;
  93 + padding-left: 10px;
  94 +
  95 + md-slider-container {
  96 + md-slider {
  97 + min-width: 80px;
  98 + }
  99 +
  100 + button.md-button.md-icon-button {
  101 + width: 44px;
  102 + min-width: 44px;
  103 + height: 44px;
  104 + min-height: 44px;
  105 + margin: 0;
  106 + line-height: 28px;
  107 +
  108 + md-icon {
  109 + width: 28px;
  110 + height: 28px;
  111 + font-size: 28px;
  112 +
  113 + svg {
  114 + width: inherit;
  115 + height: inherit;
118 116 }
119 117 }
120   -
121   - md-select {
122   - margin: 0;
123   - }
124 118 }
125   -
126   - .panel-timer {
127   - max-width: none;
128   - padding-right: 250px;
129   - padding-left: 90px;
130   - margin-top: -20px;
131   - font-size: 12px;
132   - font-weight: 500;
133   - text-align: center;
  119 +
  120 + md-select {
  121 + margin: 0;
134 122 }
135 123 }
  124 +
  125 + .panel-timer {
  126 + max-width: none;
  127 + padding-right: 250px;
  128 + padding-left: 90px;
  129 + margin-top: -20px;
  130 + font-size: 12px;
  131 + font-weight: 500;
  132 + text-align: center;
  133 + }
  134 + }
  135 +}
  136 +
  137 +.playpause {
  138 + label {
  139 + display: block;
  140 + box-sizing: border-box;
  141 +
  142 + width: 0;
  143 + height: 74px;
  144 +
  145 + cursor: pointer;
  146 +
  147 + border-color: transparent transparent transparent #202020;
  148 + transition: 100ms all ease;
  149 + will-change: border-width;
  150 +
  151 + // paused state
  152 + border-style: double;
  153 + border-width: 0px 0 0px 60px;
  154 + }
  155 + input[type="checkbox"] {
  156 + visibility: hidden;
  157 +
  158 + &:checked + label {
  159 + // play state
  160 + border-style: solid;
  161 + border-width: 37px 0 37px 60px;
  162 + }
136 163 }
137   -
\ No newline at end of file
  164 +}
... ...
1   -import { Component, OnInit, Input } from '@angular/core';
  1 +import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
  2 +import { interval } from 'rxjs';
  3 +import { filter, tap } from 'rxjs/operators';
2 4
3 5 @Component({
4 6 selector: 'tb-history-selector',
5 7 templateUrl: './history-selector.component.html',
6 8 styleUrls: ['./history-selector.component.scss']
7 9 })
8   -export class HistorySelectorComponent implements OnInit {
  10 +export class HistorySelectorComponent implements OnInit, OnChanges {
9 11
10 12 @Input() settings
  13 + @Input() intervals = [];
11 14
12   - animationTime
  15 + @Output() onTimeUpdated = new EventEmitter();
13 16
  17 + animationTime;
  18 + minTimeIndex = 0;
  19 + maxTimeIndex = 0;
  20 + speed = 1;
  21 + index = 0;
  22 + playing = false;
  23 + interval;
14 24
15   - constructor() { }
  25 +
  26 + constructor(private cd: ChangeDetectorRef) { }
16 27
17 28 ngOnInit(): void {
18   - console.log(this.settings);
19   -
  29 + }
  30 +
  31 + ngOnChanges() {
  32 + this.maxTimeIndex = this.intervals?.length - 1;
  33 + }
  34 +
  35 + play() {
  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 + })
  54 + }
  55 +
  56 + pause() {
  57 + this.playing = false;
  58 + }
  59 + /*
  60 + setSpeed() {
  61 + if (this.interval) this.interval.unsubscribe();
  62 + this.interval = interval(1000 / this.speed)
  63 + .pipe(
  64 + takeWhile(() => this.index < this.maxTimeIndex),
  65 + filter(() => this.play),
  66 + tap(() => this.index++))
  67 + .subscribe(() => {
  68 + console.log(this.intervals);
  69 +
  70 + this.onTimeUpdated.emit(this.intervals[this.index]);
  71 +
  72 + }, err => {
  73 + console.log(err);
  74 + }, () => {
  75 + this.index = this.minTimeIndex;
  76 + this.play = false;
  77 + })
  78 + }*/
  79 +
  80 +
  81 +
  82 +
  83 + /*
  84 +
  85 + playMove(play) {
  86 + if (play && this.isPlaying) return;
  87 + if (play || this.isPlaying) this.isPlaying = true;
  88 + if (this.isPlaying) {
  89 + moveInc(1);
  90 + this.timeout = $timeout(function () {
  91 + this.playMove();
  92 + }, 1000 / this.speed)
  93 + }
  94 + };
  95 +
  96 + moveNext() {
  97 + this.stopPlay();
  98 + if (this.staticSettings.usePointAsAnchor) {
  99 + let newIndex = this.maxTimeIndex;
  100 + for (let index = this.index + 1; index < this.maxTimeIndex; index++) {
  101 + if (this.trips.some(function (trip) {
  102 + return calculateCurrentDate(trip.timeRange, index).hasAnchor;
  103 + })) {
  104 + newIndex = index;
  105 + break;
  106 + }
  107 + }
  108 + this.moveToIndex(newIndex);
  109 + } else moveInc(1);
  110 + };
  111 +
  112 + movePrev() {
  113 + this.stopPlay();
  114 + if (this.staticSettings.usePointAsAnchor) {
  115 + let newIndex = this.minTimeIndex;
  116 + for (let index = this.index - 1; index > this.minTimeIndex; index--) {
  117 + if (this.trips.some(function (trip) {
  118 + return calculateCurrentDate(trip.timeRange, index).hasAnchor;
  119 + })) {
  120 + newIndex = index;
  121 + break;
  122 + }
  123 + }
  124 + this.moveToIndex(newIndex);
  125 + } else moveInc(-1);
  126 + };
  127 +
  128 + moveStart = function () {
  129 + this.stopPlay();
  130 + this.moveToIndex(this.minTimeIndex);
  131 + };
  132 +
  133 + moveEnd = function () {
  134 + this.stopPlay();
  135 + this.moveToIndex(this.maxTimeIndex);
  136 + };
  137 +
  138 + stopPlay = function () {
  139 + if (this.isPlaying) {
  140 + this.isPlaying = false;
  141 + $timeout.cancel(this.timeout);
  142 + }
  143 + };
  144 +
  145 + moveInc(inc) {
  146 + let newIndex = this.index + inc;
  147 + this.moveToIndex(newIndex);
  148 + }
  149 +
  150 + moveToIndex(newIndex) {
  151 + if (newIndex > this.maxTimeIndex || newIndex < this.minTimeIndex) return;
  152 + this.index = newIndex;
  153 + this.animationTime = this.minTime + this.index * this.staticSettings.normalizationStep;
  154 + recalculateTrips();
  155 + }
  156 + */
  157 + recalculateTrips() {
  158 +
20 159 }
21 160
22 161 }
... ...