## Issue * 使用 Map Widget 時,若 device 數量到一定程度 ( 500 ~ 1000+ ),Web 會嚴重延遲導致幾乎無法操作 ( Rendering? ) ## 嘗試 * 自建 Angular project,使用 @asymmetrik/ngx-leaflet library 建立地圖並串接 WebSocket API 1. ng add mapTest 2. npm i @asymmetrik/ngx-leaflet 3. html ``` <div leaflet [leafletOptions]="mapOptions" class="d-flex" style="height: 100%; width: 100%"> <div *ngIf="markerLayers" [leafletLayers]="markerLayers"></div> </div> ``` 4. ts ``` // init ngOnInit(): void { this.setMap(); } ``` ``` ... // init map setMap(): void { this.mapOptions = { layers: [ tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>', }), ], zoom: 13, center: latLng({ lat: 21.422510, lng: 39.826168 }), }; } ``` ``` // WebSocket API payload request = { "attrSubCmds": [], "tsSubCmds": [], "historyCmds": [], "entityDataCmds": [ { "query": { "entityFilter": { "type": "deviceType", "resolveMultiple": true, "deviceTypes": [ "MapTest" ], "deviceNameFilter": "" }, "pageLink": { "page": 0, "pageSize": 16384, "textSearch": null, "dynamic": true }, "keyFilters": [ { "key": { "type": "ATTRIBUTE", "key": "active" }, "valueType": "BOOLEAN", "predicate": { "operation": "EQUAL", "value": { "defaultValue": true, "dynamicValue": null }, "type": "BOOLEAN" } } ], "entityFields": [ { "type": "ENTITY_FIELD", "key": "name" }, { "type": "ENTITY_FIELD", "key": "label" }, { "type": "ENTITY_FIELD", "key": "additionalInfo" } ], "latestValues": [ { "type": "ATTRIBUTE", "key": "latitude" }, { "type": "ATTRIBUTE", "key": "longitude" } ] }, "latestCmd": { "keys": [ { "type": "ATTRIBUTE", "key": "latitude" }, { "type": "ATTRIBUTE", "key": "longitude" } ] } } ] }; ``` ``` // init websocket subWebSocket(): void { let ws$ = new WebSocket(`ws://tbhost:port/api/ws/plugins/telemetry?token=${jwtoken}`); ws$.onopen = event => { ws$.send(JSON.stringify(this.request)); } ws$.onmessage = event => { let res = JSON.parse(event.data); if (res.data) { let devices = res.data.data; devices.forEach((device: any) => { this.saveDeviceData(device); }); this.createMarkers(); } else if (res.update) { let update = res.update[0]; this.updateMarker(update); } }; } ``` ``` // create & update markers createMarkers(): void { this.markers.forEach((value, key) => { this.markerLayers.push(value); }); } updateMarker(updateData: any): void { if (updateData.latest.ATTRIBUTE.latitude && updateData.latest.ATTRIBUTE.longitude) { this.markers.get(updateData.entityId.id)?.setLatLng([updateData.latest.ATTRIBUTE.latitude.value, updateData.latest.ATTRIBUTE.longitude.value]); } } ``` 5. `ng s -o` - 幾乎沒有延遲狀況,能順暢操作。 ## 於 TB CE Source code 建立 Map Component 測試 * 因 `@asymmetrik/ngx-leaflet` 於 source code 引入時有錯誤待解,改用原生 leaflet ``` Error: node_modules/@asymmetrik/ngx-leaflet/lib/core/leaflet.directive.d.ts:93:82 - error TS2344: Type '{ fitBoundsOptions: { alias: "leafletFitBoundsOptions"; required: false; }; panOptions: { alias: "leafletPanOptions"; required: false; }; zoomOptions: { alias: "leafletZoomOptions"; required: false; }; ... 7 more ...; maxZoom: { ...; }; }' does not satisfy the constraint '{ [key: string]: string; }'. Property '"fitBoundsOptions"' is incompatible with index signature. Type '{ alias: "leafletFitBoundsOptions"; required: false; }' is not assignable to type 'string'. 93 static ɵdir: i0.ɵɵDirectiveDeclaration<LeafletDirective, "[leaflet]", never, { "fitBoundsOptions": { "alias": "leafletFitBoundsOptions"; "required": false; }; "panOptions": { "alias": "leafletPanOptions"; "required": false; }; "zoomOptions": { "alias": "leafletZoomOptions"; "required": false; }; "zoomPanOptions": { "alias": "leafletZoomPanOptions"; "required": false; }; "options": { "alias": "leafletOptions"; "required": false; }; "zoom": { "alias": "leafletZoom"; "required": false; }; "center": { "alias": "leafletCenter"; "required": false; }; "fitBounds": { "alias": "leafletFitBounds"; "required": false; }; "maxBounds": { "alias": "leafletMaxBounds"; "required": false; }; "minZoom": { "alias": "leafletMinZoom"; "required": false; }; "maxZoom": { "alias": "leafletMaxZoom"; "required": false; }; }, { "mapReady": "leafletMapReady"; "zoomChange": "leafletZoomChange"; "centerChange": "leafletCenterChange"; "onClick": "leafletClick"; "onDoubleClick": "leafletDoubleClick"; "onMouseDown": "leafletMouseDown"; "onMouseUp": "leafletMouseUp"; "onMouseMove": "leafletMouseMove"; "onMouseOver": "leafletMouseOver"; "onMouseOut": "leafletMouseOut"; "onMapMove": "leafletMapMove"; "onMapMoveStart": "leafletMapMoveStart"; "onMapMoveEnd": "leafletMapMoveEnd"; "onMapZoom": "leafletMapZoom"; "onMapZoomStart": "leafletMapZoomStart"; "onMapZoomEnd": "leafletMapZoomEnd"; }, never, never, false, never>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/@asymmetrik/ngx-leaflet/lib/layers/base/leaflet-baselayers.directive.d.ts:44:102 - error TS2344: Type '{ baseLayers: { alias: "leafletBaseLayers"; required: false; }; layersControlOptions: { alias: "leafletLayersControlOptions"; required: false; }; }' does not satisfy the constraint '{ [key: string]: string; }'. Property '"baseLayers"' is incompatible with index signature. Type '{ alias: "leafletBaseLayers"; required: false; }' is not assignable to type 'string'. 44 static ɵdir: i0.ɵɵDirectiveDeclaration<LeafletBaseLayersDirective, "[leafletBaseLayers]", never, { "baseLayers": { "alias": "leafletBaseLayers"; "required": false; }; "layersControlOptions": { "alias": "leafletLayersControlOptions"; "required": false; }; }, { "layersControlReady": "leafletLayersControlReady"; }, never, never, false, never>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/@asymmetrik/ngx-leaflet/lib/layers/control/leaflet-control-layers.directive.d.ts:34:108 - error TS2344: Type '{ layersControlConfig: { alias: "leafletLayersControl"; required: false; }; layersControlOptions: { alias: "leafletLayersControlOptions"; required: false; }; }' does not satisfy the constraint '{ [key: string]: string; }'. Property '"layersControlConfig"' is incompatible with index signature. Type '{ alias: "leafletLayersControl"; required: false; }' is not assignable to type 'string'. 34 static ɵdir: i0.ɵɵDirectiveDeclaration<LeafletLayersControlDirective, "[leafletLayersControl]", never, { "layersControlConfig": { "alias": "leafletLayersControl"; "required": false; }; "layersControlOptions": { "alias": "leafletLayersControlOptions"; "required": false; }; }, { "layersControlReady": "leafletLayersControlReady"; }, never, never, false, never>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/@asymmetrik/ngx-leaflet/lib/layers/leaflet-layer.directive.d.ts:29:92 - error TS2344: Type '{ layer: { alias: "leafletLayer"; required: false; }; }' does not satisfy the constraint '{ [key: string]: string; }'. Property '"layer"' is incompatible with index signature. Type '{ alias: "leafletLayer"; required: false; }' is not assignable to type 'string'. 29 static ɵdir: i0.ɵɵDirectiveDeclaration<LeafletLayerDirective, "[leafletLayer]", never, { "layer": { "alias": "leafletLayer"; "required": false; }; }, { "onAdd": "leafletLayerAdd"; "onRemove": "leafletLayerRemove"; }, never, never, false, never>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: node_modules/@asymmetrik/ngx-leaflet/lib/layers/leaflet-layers.directive.d.ts:40:94 - error TS2344: Type '{ layers: { alias: "leafletLayers"; required: false; }; }' does not satisfy the constraint '{ [key: string]: string; }'. Property '"layers"' is incompatible with index signature. Type '{ alias: "leafletLayers"; required: false; }' is not assignable to type 'string'. 40 static ɵdir: i0.ɵɵDirectiveDeclaration<LeafletLayersDirective, "[leafletLayers]", never, { "layers": { "alias": "leafletLayers"; "required": false; }; }, {}, never, never, false, never>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: src/app/modules/home/components/map/map.component.html:1:14 - error NG8002: Can't bind to 'leafletOptions' since it isn't a known property of 'div'. 1 <div leaflet [leafletOptions]="mapOptions" class="d-flex" style="height: 100%; width: 100%"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/modules/home/components/map/map.component.ts:7:16 7 templateUrl: './map.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component MapComponent. Error: src/app/modules/home/components/map/map.component.html:2:29 - error NG8002: Can't bind to 'leafletLayers' since it isn't a known property of 'div'. 2 <div *ngIf="markerLayers" [leafletLayers]="markerLayers"></div> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/modules/home/components/map/map.component.ts:7:16 7 templateUrl: './map.component.html', ~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component MapComponent. ``` 1. 建立 componet - `ng g c modules/home/components/map --skip-import` 2. 建立 module & routing - `ng g m modules/home/pages/map --routing` 3. map.component.html ``` <div class="container"> <div id="map"></div> </div> ``` 4. map.component.ts ``` ngOnInit(): void { this.setLeafletMap(); } setLeafletMap(): void { this.map = L.map('map').setView([21.422510, 39.826168], 13); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' }).addTo(this.map); this.subWebSocket(); } ... // 其餘與 ngx-leaflet 一致,僅以下調整 createMarkers(): void { this.markers.forEach((value, key) => { this.markerLayers.push(value); }); + const markerGroup = L.layerGroup(this.markerLayers); + markerGroup.addTo(this.map); } ``` 5. map.module.ts ``` import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MapComponent } from '../../components/map/map.component'; import { MapRoutingModule } from './map-routing.module'; @NgModule({ declarations: [ MapComponent ], imports: [ CommonModule, MapRoutingModule ] }) export class MapModule { } ``` 6. map-routing.module.ts ``` import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { MapComponent } from '../../components/map/map.component'; const routes: Routes = [ { path: 'map', component: MapComponent, data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], title: 'map.maps', breadcrumb: { skip: true } }, } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [] }) export class MapRoutingModule { } ``` 6. add module to `src\app\modules\home\pages\home-pages.module.ts` ``` ... @NgModule({ exports: [ MapModule, AdminModule, HomeLinksModule, ... ... ``` 7. `http://host:port/map` - 結果與新建的 Angular project 差不多,能正常使用。