## 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:
'© <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 差不多,能正常使用。