|
|
|
@@ -1,9 +1,175 @@ |
|
|
|
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; |
|
|
|
import { |
|
|
|
AfterViewInit, |
|
|
|
Component, |
|
|
|
OnDestroy, |
|
|
|
OnInit |
|
|
|
} from '@angular/core'; |
|
|
|
import * as L from 'leaflet'; |
|
|
|
import 'leaflet.markercluster'; |
|
|
|
import { Subscription } from 'rxjs'; |
|
|
|
import { SocketService } from '../../../shared/services/socket.service'; |
|
|
|
import {ICON} from "../../../app.constants"; |
|
|
|
|
|
|
|
|
|
|
|
const alarmData = [ |
|
|
|
{ |
|
|
|
title: ' Alarm System 1', |
|
|
|
detail: { |
|
|
|
position: 'Hanoi', |
|
|
|
coordinates: { |
|
|
|
lat: 21.0285, |
|
|
|
lng: 105.8542 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 2', |
|
|
|
detail: { |
|
|
|
position: 'Hai Phong', |
|
|
|
coordinates: { |
|
|
|
lat: 20.8449, |
|
|
|
lng: 106.6881 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 3', |
|
|
|
detail: { |
|
|
|
position: 'Ha Long', |
|
|
|
coordinates: { |
|
|
|
lat: 20.9460, |
|
|
|
lng: 107.0740 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 4', |
|
|
|
detail: { |
|
|
|
position: 'Vinh', |
|
|
|
coordinates: { |
|
|
|
lat: 18.6796, |
|
|
|
lng: 105.6813 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 5', |
|
|
|
detail: { |
|
|
|
position: 'Dong Hoi', |
|
|
|
coordinates: { |
|
|
|
lat: 17.4834, |
|
|
|
lng: 106.6000 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 6', |
|
|
|
detail: { |
|
|
|
position: 'Hue', |
|
|
|
coordinates: { |
|
|
|
lat: 16.4637, |
|
|
|
lng: 107.5909 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 7', |
|
|
|
detail: { |
|
|
|
position: 'Da Nang', |
|
|
|
coordinates: { |
|
|
|
lat: 16.0471, |
|
|
|
lng: 108.2068 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 8', |
|
|
|
detail: { |
|
|
|
position: 'Quy Nhon', |
|
|
|
coordinates: { |
|
|
|
lat: 13.7820, |
|
|
|
lng: 109.2198 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 9', |
|
|
|
detail: { |
|
|
|
position: 'Nha Trang', |
|
|
|
coordinates: { |
|
|
|
lat: 12.2388, |
|
|
|
lng: 109.1967 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 10', |
|
|
|
detail: { |
|
|
|
position: 'Da Lat', |
|
|
|
coordinates: { |
|
|
|
lat: 11.9416, |
|
|
|
lng: 108.4580 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 11', |
|
|
|
detail: { |
|
|
|
position: 'Ho Chi Minh City', |
|
|
|
coordinates: { |
|
|
|
lat: 10.8231, |
|
|
|
lng: 106.6297 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: false |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: ' Alarm System 12', |
|
|
|
detail: { |
|
|
|
position: 'Can Tho', |
|
|
|
coordinates: { |
|
|
|
lat: 10.0452, |
|
|
|
lng: 105.7469 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
warning: true |
|
|
|
}, |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
const alarmDemo = { |
|
|
|
title: ' Alarm System demo', // Thêm mới warning |
|
|
|
detail: { |
|
|
|
position: 'Vinhomes Quận 9', |
|
|
|
coordinates: { |
|
|
|
lat: 10.7483, |
|
|
|
lng: 106.8016 |
|
|
|
}, |
|
|
|
time: '06-07-2024' |
|
|
|
}, |
|
|
|
} |
|
|
|
@Component({ |
|
|
|
selector: 'app-map', |
|
|
|
templateUrl: './map.component.html', |
|
|
|
@@ -11,32 +177,23 @@ import {ICON} from "../../../app.constants"; |
|
|
|
}) |
|
|
|
export class MapComponent implements OnInit, AfterViewInit, OnDestroy { |
|
|
|
private map!: L.Map; |
|
|
|
private markers!: L.MarkerClusterGroup; |
|
|
|
private statusSubscription?: Subscription; |
|
|
|
private messageSubscription?: Subscription; |
|
|
|
sensorSmoke = ICON.sensorActiveSmoke; |
|
|
|
sensorVib = ICON.sensorActiveVib; |
|
|
|
|
|
|
|
sensorSmokeIcon = L.divIcon({ |
|
|
|
html: this.sensorSmoke, |
|
|
|
iconSize: [170, 48], |
|
|
|
className: 'hidden-background' |
|
|
|
}); |
|
|
|
|
|
|
|
sensorVibIcon = L.divIcon({ |
|
|
|
html: this.sensorVib, |
|
|
|
iconSize: [170, 48], |
|
|
|
className: 'hidden-background' |
|
|
|
}); |
|
|
|
data = alarmData; |
|
|
|
alarmDemo = alarmDemo; |
|
|
|
state1 = false; |
|
|
|
state2 = false; |
|
|
|
state5 = false; |
|
|
|
state6 = false; |
|
|
|
isReady = true; |
|
|
|
|
|
|
|
constructor(private socketService$: SocketService) { } |
|
|
|
|
|
|
|
ngOnInit() { |
|
|
|
this.statusSubscription = this.socketService$.status$.subscribe(isConnected => { |
|
|
|
if (isConnected) { |
|
|
|
let str = {id: '0', type: 'get'}; |
|
|
|
this.socketService$.sendMessage(str); |
|
|
|
this.socketService$.sendMessage({ id: '0', type: 'get' }); |
|
|
|
this.messageSubscription = this.socketService$.messages$.subscribe(message => { |
|
|
|
this.onMessage(message); |
|
|
|
}); |
|
|
|
@@ -54,16 +211,16 @@ export class MapComponent implements OnInit, AfterViewInit, OnDestroy { |
|
|
|
this.socketService$.close(); |
|
|
|
} |
|
|
|
|
|
|
|
private initMap(): void { |
|
|
|
initMap(): void { |
|
|
|
const mapContainer = document.getElementById('map'); |
|
|
|
if (mapContainer) { |
|
|
|
this.map = L.map('map', { |
|
|
|
center: [10.8494, 106.7537], |
|
|
|
center: [10.7483, 106.7537], |
|
|
|
zoom: 12 |
|
|
|
}); |
|
|
|
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { |
|
|
|
maxZoom: 18, |
|
|
|
maxZoom: 15, |
|
|
|
minZoom: 3, |
|
|
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' |
|
|
|
}).addTo(this.map); |
|
|
|
@@ -75,110 +232,101 @@ export class MapComponent implements OnInit, AfterViewInit, OnDestroy { |
|
|
|
} |
|
|
|
|
|
|
|
onMessage(message: any) { |
|
|
|
if (message.id == '0' && message.type === 'get') { |
|
|
|
this.state5 = message.state5 === '1'; |
|
|
|
this.state6 = message.state6 === '1'; |
|
|
|
if (message.id === '0' && message.type === 'get') { |
|
|
|
this.state1 = message.state1 === '0';// 1 OFF // alarm 12h |
|
|
|
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h |
|
|
|
this.state5 = message.state5 === '1';// alarm 9h && 6h // 1 ON, 0 OFF |
|
|
|
this.isReady = message.ready === '1'; |
|
|
|
this.updateIcons(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
addIconToMap() { |
|
|
|
const popupContent = `<div>Vinhome quận 9</div> |
|
|
|
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>`; |
|
|
|
|
|
|
|
const fireContent = ICON.fireContent; |
|
|
|
|
|
|
|
L.marker([10.8356, 106.8300], {icon: this.applyIcon(this.state5, this.state6, 'smoke')}) |
|
|
|
.addTo(this.map) |
|
|
|
this.markers = L.markerClusterGroup(); |
|
|
|
this.data.forEach((item: any) => { |
|
|
|
// create icon |
|
|
|
const icon = this.createIcon(item.warning); |
|
|
|
// add icon into map |
|
|
|
L.marker([item.detail.coordinates.lat, item.detail.coordinates.lng], { icon: icon }) |
|
|
|
.bindPopup(this.popupDetail(item)) |
|
|
|
.addTo(this.markers); |
|
|
|
}); |
|
|
|
|
|
|
|
L.marker([10.8661, 106.8029], {icon: this.applyIcon(this.state5, this.state6, 'vib')}) |
|
|
|
.addTo(this.map) |
|
|
|
const iconDemo = this.getIcon(this.state5, this.isReady, this.state1, this.state2); |
|
|
|
L.marker([this.alarmDemo.detail.coordinates.lat,this.alarmDemo.detail.coordinates.lng], { icon: iconDemo }) |
|
|
|
.bindPopup(this.popupDetail(this.alarmDemo)) |
|
|
|
.addTo(this.markers); |
|
|
|
|
|
|
|
this.markers.addTo(this.map); |
|
|
|
} |
|
|
|
|
|
|
|
updateIcons(): void { |
|
|
|
this.map.eachLayer((layer:any) => { |
|
|
|
if (layer instanceof L.Marker as any) { |
|
|
|
this.map.removeLayer(layer); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
//clear layer icon mỗi lần update |
|
|
|
this.markers.clearLayers(); |
|
|
|
this.addIconToMap(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
applyIcon(state1: boolean, state2: boolean, type:any) { |
|
|
|
if (state1 && state2) { |
|
|
|
return type === 'smoke' ? this.sensorSmokeIcon : this.sensorVibIcon; |
|
|
|
} |
|
|
|
const isOn = state1; |
|
|
|
const systemName = type === 'smoke' ? 'Hệ thống báo động 1' : 'Hệ thống báo động 2'; |
|
|
|
const htmlContent = this.createSensorIconHTML( |
|
|
|
systemName, |
|
|
|
'120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh', |
|
|
|
'10.8661° N, 106.8029° E', |
|
|
|
'01:54, 16/05/2022', |
|
|
|
isOn |
|
|
|
); |
|
|
|
return L.divIcon({ |
|
|
|
html: htmlContent, |
|
|
|
iconSize: [150, 38], |
|
|
|
className: 'hidden-background' |
|
|
|
}); |
|
|
|
popupDetail(item: any): string { |
|
|
|
return ` |
|
|
|
<div class="tooltip" style="width: 200px"> |
|
|
|
<div style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 5px"> |
|
|
|
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; max-width: 180px;">${item.title}</div> |
|
|
|
<a href="/overview/camera-stream" target="_blank"> |
|
|
|
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none"> |
|
|
|
<g stroke="currentColor" stroke-width="2"> |
|
|
|
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/> |
|
|
|
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/> |
|
|
|
</g> |
|
|
|
</svg> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
<div><strong>Địa điểm:</strong> ${item.detail.position}</div> |
|
|
|
<div><strong>Tọa độ:</strong> ${item.detail.coordinates.lat}, ${item.detail.coordinates.lng}</div> |
|
|
|
<div><strong>Thời gian:</strong> ${item.detail.time}</div> |
|
|
|
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> |
|
|
|
</div>`; |
|
|
|
} |
|
|
|
|
|
|
|
createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon { |
|
|
|
if (text.length < 1) { |
|
|
|
return L.icon({ |
|
|
|
iconUrl: active ? '../../../../assets/images/sensor-on.png' : '../../../../assets/images/sensor-off.png', |
|
|
|
iconSize: [30, 30], |
|
|
|
className: className |
|
|
|
}); |
|
|
|
} else { |
|
|
|
let htmlContent = ''; |
|
|
|
text.forEach((item: any) => { |
|
|
|
htmlContent += `<span><b>${item}</b></span><br>`; |
|
|
|
}); |
|
|
|
|
|
|
|
createSensorIconHTML(systemName:any, location:any, coordinates:any, time:any, isOn:any) { |
|
|
|
const imageUrl = isOn ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; |
|
|
|
return `<div style="display: flex; flex-direction: column; align-items: center"> |
|
|
|
<div style="display: flex; flex-direction: row; gap: 5px"> |
|
|
|
<div class="tooltip"> |
|
|
|
<img style="width: 30px; height: 30px;" src="${imageUrl}"> |
|
|
|
<div class="tooltiptext"> |
|
|
|
<div style="display: flex; flex-direction: row; justify-content: space-between;"> |
|
|
|
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; width: 120px;">${systemName}</div> |
|
|
|
<a href="/overview/camera-stream"> |
|
|
|
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none"> |
|
|
|
<g stroke="currentColor" stroke-width="2"> |
|
|
|
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/> |
|
|
|
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/> |
|
|
|
</g> |
|
|
|
</svg> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
<div><strong>Địa điểm:</strong> ${location}</div> |
|
|
|
<div><strong>Tọa độ:</strong> ${coordinates}</div> |
|
|
|
<div><strong>Thời gian:</strong> ${time}</div> |
|
|
|
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<a href="/overview/camera-stream" target="_blank"> |
|
|
|
<img style="width: 30px; height: 30px;" src="assets/images/camera.png"> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<style> |
|
|
|
.tooltip { |
|
|
|
position: relative; |
|
|
|
display: inline-block; |
|
|
|
} |
|
|
|
.tooltip .tooltiptext { |
|
|
|
visibility: hidden; |
|
|
|
width: 300px; |
|
|
|
padding: 10px; |
|
|
|
background-color: #fff; |
|
|
|
color: black; |
|
|
|
border-radius: 6px; |
|
|
|
position: absolute; |
|
|
|
z-index: 1; |
|
|
|
bottom: 100%; |
|
|
|
left: 50%; |
|
|
|
margin-left: -60px; |
|
|
|
} |
|
|
|
.tooltip:hover .tooltiptext { |
|
|
|
visibility: visible; |
|
|
|
} |
|
|
|
</style>`; |
|
|
|
return L.divIcon({ |
|
|
|
html: `<div class="icon"> |
|
|
|
<div style="z-index:9999" class="sensor-on"> |
|
|
|
<img alt="icon-alarm" src ="assets/images/sensor-on.png" style="width: 30px; height: 30px"> |
|
|
|
</div> |
|
|
|
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px; margin-top: 10px; text-align: center"> |
|
|
|
${htmlContent} |
|
|
|
</div> |
|
|
|
</div>`, |
|
|
|
iconSize: [100, 30], |
|
|
|
className: className |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
getIcon(isTurnOn: boolean, isReady: boolean, fireArm: boolean, fenceArm: boolean): L.Icon<L.IconOptions> | L.DivIcon { |
|
|
|
if (isTurnOn && isReady) { |
|
|
|
let text = []; |
|
|
|
if (fireArm && fenceArm) { |
|
|
|
text.push('FIRE ALARM', 'FENCE ALARM'); |
|
|
|
} else if (fireArm) { |
|
|
|
text.push('FIRE ALARM'); |
|
|
|
} else if (fenceArm) { |
|
|
|
text.push('FENCE ALARM'); |
|
|
|
} |
|
|
|
return this.createIcon(true, '', text); |
|
|
|
} |
|
|
|
return this.createIcon(false); |
|
|
|
} |
|
|
|
} |