---
# System prepended metadata

title: Leaflet Map Issue in TB
tags: [Leaflet, ' ThingsBoard', Ongoing]

---

## 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 差不多，能正常使用。