From 6ecc833a2c5a2d1f0798496fc76f6a0734110f74 Mon Sep 17 00:00:00 2001 From: yarnom Date: Fri, 4 Jul 2025 21:09:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coordinate-utils.js | 94 + .../device-overview-module/device-markers.js | 413 ++++ .../device-overview-main.js | 218 ++ .../js/device-overview-module/map-core.js | 421 ++++ .../js/device-overview-module/map-layers.js | 195 ++ .../device-overview-module/measure-tools.js | 363 +++ .../device-overview-module/search-filter.js | 249 ++ .../weather-forecast.js | 513 ++++ .../templates/page/device_overview.html | 2087 ++--------------- 9 files changed, 2663 insertions(+), 1890 deletions(-) create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js create mode 100644 sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js b/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js new file mode 100644 index 00000000..bbb4d1a8 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/coordinate-utils.js @@ -0,0 +1,94 @@ +/** + * 坐标转换工具模块 + * 提供WGS84和GCJ-02坐标系之间的转换功能 + */ +var CoordinateUtils = (function() { + 'use strict'; + + var pi = 3.14159265358979324; + var a = 6378245.0; + var ee = 0.00669342162296594323; + + /** + * 判断是否在国内,不在国内则不做偏移 + */ + function outOfChina(lon, lat) { + if ((lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)) { + return true; + } else { + return false; + } + } + + function transformLat(x, y) { + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; + return ret; + } + + function transformLon(x, y) { + var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0; + return ret; + } + + /** + * WGS84转GCJ-02坐标系 + * @param {number} wgLat - WGS84纬度 + * @param {number} wgLon - WGS84经度 + * @returns {object} 转换后的坐标 {lat, lon} + */ + function transform(wgLat, wgLon) { + var mars_point = {lon: 0, lat: 0}; + if (outOfChina(wgLon, wgLat)) { + mars_point.lat = wgLat; + mars_point.lon = wgLon; + return mars_point; + } + var dLat = transformLat(wgLon - 105.0, wgLat - 35.0); + var dLon = transformLon(wgLon - 105.0, wgLat - 35.0); + var radLat = wgLat / 180.0 * pi; + var magic = Math.sin(radLat); + magic = 1 - ee * magic * magic; + var sqrtMagic = Math.sqrt(magic); + dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); + dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); + mars_point.lat = wgLat + dLat; + mars_point.lon = wgLon + dLon; + return mars_point; + } + + /** + * 根据地图类型转换坐标 + * @param {number} lat - 纬度 + * @param {number} lon - 经度 + * @param {string} mapType - 地图类型 + * @returns {Array} OpenLayers坐标格式 + */ + function getMapCoordinates(lat, lon, mapType) { + var coordinates; + if (mapType === 'amap' || mapType === 'amap_satellite') { + // 高德地图 WGS84 转换为 GCJ-02 + var gcjCoord = transform(lat, lon); + coordinates = ol.proj.fromLonLat([gcjCoord.lon, gcjCoord.lat]); + } else if (mapType.startsWith('google_')) { + // Google地图使用WGS84坐标系,直接使用 + coordinates = ol.proj.fromLonLat([lon, lat]); + } else { + // 天地图 CGCS2000,2000国家大地坐标系,与WGS84实质一样 + coordinates = ol.proj.fromLonLat([lon, lat]); + } + return coordinates; + } + + // 公开API + return { + transform: transform, + getMapCoordinates: getMapCoordinates, + outOfChina: outOfChina + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js b/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js new file mode 100644 index 00000000..b5aa41f0 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/device-markers.js @@ -0,0 +1,413 @@ +/** + * 设备标记管理模块 + * 负责设备标记的创建、分类和显示管理 + */ +var DeviceMarkers = (function() { + 'use strict'; + + // 私有变量 + var greenFeatures = []; + var orangeFeatures = []; + var redFeatures = []; + var allFeatures = []; + var myLocationFeature = null; + var myLocationInterval = null; + var showDeviceId = true; + var minZoomForLabels = 4; + + // 外部依赖的引用 + var map = null; + var vectorSource = null; + var vectorLayer = null; + + /** + * 初始化设备标记管理器 + * @param {ol.Map} mapInstance - 地图实例 + * @param {ol.source.Vector} vectorSourceInstance - 矢量数据源 + * @param {ol.layer.Vector} vectorLayerInstance - 矢量图层 + */ + function init(mapInstance, vectorSourceInstance, vectorLayerInstance) { + map = mapInstance; + vectorSource = vectorSourceInstance; + vectorLayer = vectorLayerInstance; + } + + /** + * 创建设备标记样式 + * @param {ol.Feature} feature - 要素对象 + * @returns {ol.style.Style} 样式对象 + */ + function createDeviceStyle(feature) { + if (feature.get('isMyLocation')) { + return new ol.style.Style({ + image: new ol.style.Icon({ + anchor: [0.5, 1], + src: '../images/loc_blue.png', + scale: 0.7 + }) + }); + } + + var deviceInfo = feature.get('deviceInfo'); + if (!deviceInfo) return null; + + var iconSrc; + var color = '#000'; + var isHovered = feature.get('hovered') === true; + var scale = isHovered ? 0.85 : 0.7; + + // 根据告警级别选择图标 + if (deviceInfo.warning == 2) { + iconSrc = '../images/loc1_red.png'; + } else if (deviceInfo.warning == 1) { + iconSrc = '../images/loc1_orange.png'; + } else { + iconSrc = '../images/loc1_green.png'; + } + + var style = new ol.style.Style({ + image: new ol.style.Icon({ + anchor: [0.5, 1], + src: iconSrc, + scale: scale + }) + }); + + // 根据缩放级别和设置决定是否显示设备ID + if (showDeviceId && map && map.getView().getZoom() >= minZoomForLabels) { + style.setText(new ol.style.Text({ + text: deviceInfo.deviceid, + offsetY: -30, + fill: new ol.style.Fill({ + color: isHovered ? '#1aa094' : color + }), + stroke: new ol.style.Stroke({ + color: '#fff', + width: isHovered ? 3 : 2 + }), + font: isHovered ? 'bold 12px Arial' : '12px Arial' + })); + } + + return style; + } + + /** + * 添加设备标记到地图 + * @param {Array} deviceList - 设备列表 + */ + function addDeviceMarkers(deviceList) { + if (!vectorSource || !deviceList) return; + + // 保存当前位置标记 + var savedMyLocationFeature = myLocationFeature; + + // 清空现有标记 + vectorSource.clear(); + greenFeatures = []; + orangeFeatures = []; + redFeatures = []; + allFeatures = []; + + // 添加设备标记 + for (var i = 0; i < deviceList.length; i++) { + var device = deviceList[i]; + var currentMapType = getCurrentMapType(); + var mapCoordinates = CoordinateUtils.getMapCoordinates( + device.latitude, + device.longitude, + currentMapType + ); + + var feature = new ol.Feature({ + geometry: new ol.geom.Point(mapCoordinates), + deviceInfo: device + }); + + // 按告警级别分类 + if (device.warning == 2) { + redFeatures.push(feature); + } else if (device.warning == 1) { + orangeFeatures.push(feature); + } else { + greenFeatures.push(feature); + } + + allFeatures.push(feature); + vectorSource.addFeature(feature); + } + + // 恢复位置标记 + if (savedMyLocationFeature) { + vectorSource.addFeature(savedMyLocationFeature); + } + + // 强制更新样式 + if (vectorLayer) { + vectorLayer.changed(); + } + } + + /** + * 获取当前地图类型 + * @returns {string} 地图类型 + */ + function getCurrentMapType() { + var mapTypeSelect = document.getElementById('mapTypeSelectNew'); + return mapTypeSelect ? mapTypeSelect.value : 'tianditu_satellite'; + } + + /** + * 显示所有设备 + */ + function showAllDevices() { + if (!vectorSource) return; + + var savedMyLocationFeature = myLocationFeature; + vectorSource.clear(); + + if (savedMyLocationFeature) { + vectorSource.addFeature(savedMyLocationFeature); + } + + for (var i = 0; i < allFeatures.length; i++) { + vectorSource.addFeature(allFeatures[i]); + } + } + + /** + * 只显示一般告警设备 + */ + function showWarning1Devices() { + if (!vectorSource) return; + + var savedMyLocationFeature = myLocationFeature; + vectorSource.clear(); + + if (savedMyLocationFeature) { + vectorSource.addFeature(savedMyLocationFeature); + } + + for (var i = 0; i < allFeatures.length; i++) { + vectorSource.addFeature(allFeatures[i]); + } + + hideGreenFeatures(); + hideRedFeatures(); + } + + /** + * 只显示严重告警设备 + */ + function showWarning2Devices() { + if (!vectorSource) return; + + var savedMyLocationFeature = myLocationFeature; + vectorSource.clear(); + + if (savedMyLocationFeature) { + vectorSource.addFeature(savedMyLocationFeature); + } + + for (var i = 0; i < allFeatures.length; i++) { + vectorSource.addFeature(allFeatures[i]); + } + + hideGreenFeatures(); + hideOrangeFeatures(); + } + + /** + * 隐藏绿色(正常)设备 + */ + function hideGreenFeatures() { + for (var i = 0; i < greenFeatures.length; i++) { + vectorSource.removeFeature(greenFeatures[i]); + } + } + + /** + * 隐藏橙色(一般告警)设备 + */ + function hideOrangeFeatures() { + for (var i = 0; i < orangeFeatures.length; i++) { + vectorSource.removeFeature(orangeFeatures[i]); + } + } + + /** + * 隐藏红色(严重告警)设备 + */ + function hideRedFeatures() { + for (var i = 0; i < redFeatures.length; i++) { + vectorSource.removeFeature(redFeatures[i]); + } + } + + /** + * 根据设备ID查找设备 + * @param {string} deviceId - 设备ID + * @returns {ol.Feature|null} 找到的设备要素 + */ + function findDeviceById(deviceId) { + for (var i = 0; i < allFeatures.length; i++) { + var feature = allFeatures[i]; + var deviceInfo = feature.get('deviceInfo'); + if (deviceInfo && deviceInfo.deviceid.includes(deviceId)) { + return feature; + } + } + return null; + } + + /** + * 定位到指定设备 + * @param {string} deviceId - 设备ID + * @returns {boolean} 是否成功定位 + */ + function locateDevice(deviceId) { + var targetFeature = findDeviceById(deviceId); + + if (targetFeature) { + var geometry = targetFeature.getGeometry(); + var deviceCoord = geometry.getCoordinates(); + + if (map) { + map.getView().animate({ + center: deviceCoord, + zoom: 16, + duration: 800 + }); + } + + return true; + } + + return false; + } + + /** + * 获取我的位置 + */ + function getMyLocation() { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position) { + var lon = position.coords.longitude; + var lat = position.coords.latitude; + var currentMapType = getCurrentMapType(); + var coordinates = CoordinateUtils.getMapCoordinates(lat, lon, currentMapType); + + // 如果已经存在位置标记,则先移除 + if (myLocationFeature) { + vectorSource.removeFeature(myLocationFeature); + } + + myLocationFeature = new ol.Feature({ + geometry: new ol.geom.Point(coordinates), + isMyLocation: true, + originalCoords: {lat: lat, lon: lon} + }); + + myLocationFeature.setStyle(createDeviceStyle(myLocationFeature)); + vectorSource.addFeature(myLocationFeature); + + if (map) { + map.getView().setCenter(coordinates); + } + }); + } + } + + /** + * 开始定期更新位置 + */ + function startLocationUpdates() { + if (myLocationInterval) { + clearInterval(myLocationInterval); + } + + myLocationInterval = setInterval(function() { + getMyLocation(); + }, 10 * 60 * 1000); // 10分钟更新一次 + } + + /** + * 停止位置更新 + */ + function stopLocationUpdates() { + if (myLocationInterval) { + clearInterval(myLocationInterval); + myLocationInterval = null; + } + } + + /** + * 根据地图类型更新我的位置标记 + * @param {string} mapType - 地图类型 + */ + function updateMyLocationForMapType(mapType) { + if (myLocationFeature) { + var originalCoords = myLocationFeature.get('originalCoords'); + if (originalCoords) { + var coordinates = CoordinateUtils.getMapCoordinates( + originalCoords.lat, + originalCoords.lon, + mapType + ); + myLocationFeature.getGeometry().setCoordinates(coordinates); + } + } + } + + /** + * 设置是否显示设备ID + * @param {boolean} show - 是否显示 + */ + function setShowDeviceId(show) { + showDeviceId = show; + if (vectorLayer) { + vectorLayer.changed(); + } + } + + /** + * 获取各类设备数量统计 + * @returns {Object} 统计信息 + */ + function getDeviceStats() { + return { + total: allFeatures.length, + green: greenFeatures.length, + orange: orangeFeatures.length, + red: redFeatures.length + }; + } + + // 公开API + return { + init: init, + createDeviceStyle: createDeviceStyle, + addDeviceMarkers: addDeviceMarkers, + showAllDevices: showAllDevices, + showWarning1Devices: showWarning1Devices, + showWarning2Devices: showWarning2Devices, + hideGreenFeatures: hideGreenFeatures, + hideOrangeFeatures: hideOrangeFeatures, + hideRedFeatures: hideRedFeatures, + findDeviceById: findDeviceById, + locateDevice: locateDevice, + getMyLocation: getMyLocation, + startLocationUpdates: startLocationUpdates, + stopLocationUpdates: stopLocationUpdates, + updateMyLocationForMapType: updateMyLocationForMapType, + setShowDeviceId: setShowDeviceId, + getDeviceStats: getDeviceStats, + + // 获取器方法 + getAllFeatures: function() { return allFeatures; }, + getGreenFeatures: function() { return greenFeatures; }, + getOrangeFeatures: function() { return orangeFeatures; }, + getRedFeatures: function() { return redFeatures; }, + getMyLocationFeature: function() { return myLocationFeature; } + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js b/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js new file mode 100644 index 00000000..b20a2b18 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/device-overview-main.js @@ -0,0 +1,218 @@ +/** + * 设备总览主入口文件 + * 负责整个设备总览模块的初始化和全局函数暴露 + */ +var DeviceOverview = (function() { + 'use strict'; + + // 全局变量(从原HTML中提取) + var deviceList = []; + + /** + * 初始化设备总览模块 + * @param {Array} devices - 设备列表数据 + * @param {Object} options - 配置选项 + */ + function init(devices, options) { + deviceList = devices || []; + + // 设置全局变量供其他模块使用 + window.deviceList = deviceList; + window.userRole = options && options.role ? options.role : 'USER'; + + // 等待layui加载完成后初始化 + layui.use(['form'], function(){ + var form = layui.form; + + // 绑定表单事件 + form.on('select(mapTypeNew)', function(data){ + MapCore.switchMapType(data.value); + }); + + // 初始化地图核心 + MapCore.initialize(deviceList); + + // 默认显示所有设备 + document.getElementById('warningFilter').value = 'all'; + SearchFilter.showAllDevices(); + }); + } + + // 暴露给HTML使用的全局函数 + + /** + * 地图类型变化处理 + */ + function onMapTypeChange() { + return MapCore.onMapTypeChange(); + } + + /** + * 搜索设备 + */ + function searchDeviceNew() { + return MapCore.searchDeviceNew(); + } + + /** + * 告警过滤变化处理 + */ + function onWarningFilterChange() { + return MapCore.onWarningFilterChange(); + } + + /** + * 切换地图功能菜单 + */ + function toggleMapFunctionsMenu() { + return MapCore.toggleMapFunctionsMenu(); + } + + /** + * 切换设备ID显示 + */ + function toggleDeviceId() { + return MapCore.toggleDeviceId(); + } + + /** + * 切换集群显示 + */ + function toggleCluster() { + return MapCore.toggleCluster(); + } + + /** + * 切换测距功能 + */ + function toggleMeasureDistance() { + return MeasureTools.toggleMeasureDistance(); + } + + /** + * 完成测量 + */ + function finishMeasuring() { + return MeasureTools.finishMeasuring(); + } + + /** + * 清除测距 + */ + function clearMeasure() { + return MeasureTools.clearMeasure(); + } + + /** + * 切换天气预报功能 + */ + function toggleWeatherForecast() { + return WeatherForecast.toggleWeatherForecast(); + } + + /** + * 关闭天气卡片 + */ + function closeWeatherCard() { + return WeatherForecast.closeWeatherCard(); + } + + /** + * 显示上一个天气预报 + */ + function showPrevForecast() { + return WeatherForecast.showPrevForecast(); + } + + /** + * 显示下一个天气预报 + */ + function showNextForecast() { + return WeatherForecast.showNextForecast(); + } + + /** + * 查询设备 + * @param {string} statusType - 状态类型 + */ + function queryDevices(statusType) { + return SearchFilter.queryDevices(statusType); + } + + /** + * 定位设备到地图 + * @param {string} deviceId - 设备ID + * @param {number} latitude - 纬度 + * @param {number} longitude - 经度 + */ + function locateDeviceOnMap(deviceId, latitude, longitude) { + return SearchFilter.locateDeviceOnMap(deviceId, latitude, longitude); + } + + /** + * 直接定位设备 + * @param {string} deviceId - 设备ID + */ + function locateDeviceDirectly(deviceId) { + return SearchFilter.locateDeviceDirectly(deviceId); + } + + /** + * 切换地图类型 + * @param {string} mapType - 地图类型 + */ + function switchMapType(mapType) { + return MapCore.switchMapType(mapType); + } + + // 公开API + return { + init: init, + + // 地图相关 + onMapTypeChange: onMapTypeChange, + switchMapType: switchMapType, + + // 搜索和过滤 + searchDeviceNew: searchDeviceNew, + onWarningFilterChange: onWarningFilterChange, + queryDevices: queryDevices, + locateDeviceOnMap: locateDeviceOnMap, + locateDeviceDirectly: locateDeviceDirectly, + + // 地图功能 + toggleMapFunctionsMenu: toggleMapFunctionsMenu, + toggleDeviceId: toggleDeviceId, + toggleCluster: toggleCluster, + + // 测距工具 + toggleMeasureDistance: toggleMeasureDistance, + finishMeasuring: finishMeasuring, + clearMeasure: clearMeasure, + + // 天气预报 + toggleWeatherForecast: toggleWeatherForecast, + closeWeatherCard: closeWeatherCard, + showPrevForecast: showPrevForecast, + showNextForecast: showNextForecast + }; +})(); + +// 将主要函数暴露到全局作用域,供HTML中的onclick等使用 +window.DeviceOverview = DeviceOverview; +window.onMapTypeChange = DeviceOverview.onMapTypeChange; +window.searchDeviceNew = DeviceOverview.searchDeviceNew; +window.onWarningFilterChange = DeviceOverview.onWarningFilterChange; +window.toggleMapFunctionsMenu = DeviceOverview.toggleMapFunctionsMenu; +window.toggleDeviceId = DeviceOverview.toggleDeviceId; +window.toggleCluster = DeviceOverview.toggleCluster; +window.toggleMeasureDistance = DeviceOverview.toggleMeasureDistance; +window.finishMeasuring = DeviceOverview.finishMeasuring; +window.clearMeasure = DeviceOverview.clearMeasure; +window.toggleWeatherForecast = DeviceOverview.toggleWeatherForecast; +window.closeWeatherCard = DeviceOverview.closeWeatherCard; +window.showPrevForecast = DeviceOverview.showPrevForecast; +window.showNextForecast = DeviceOverview.showNextForecast; +window.queryDevices = DeviceOverview.queryDevices; +window.locateDeviceOnMap = DeviceOverview.locateDeviceOnMap; +window.locateDeviceDirectly = DeviceOverview.locateDeviceDirectly; \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js b/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js new file mode 100644 index 00000000..6df0aa57 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/map-core.js @@ -0,0 +1,421 @@ +/** + * 地图核心模块 + * 负责地图初始化、事件处理和核心功能管理 + */ +var MapCore = (function() { + 'use strict'; + + // 私有变量 + var map = null; + var vectorSource = null; + var vectorLayer = null; + var clusterSource = null; + var clusterLayer = null; + var currentBaseLayer = null; + var showDeviceId = true; + var showCluster = true; + var minZoomForLabels = 4; + var maxZoomForClustering = 8; + var hoveredFeature = null; + + /** + * 初始化地图 + * @param {Array} deviceList - 设备列表 + */ + function initialize(deviceList) { + // 创建矢量数据源和图层 + vectorSource = new ol.source.Vector(); + vectorLayer = new ol.layer.Vector({ + source: vectorSource, + style: function(feature) { + return DeviceMarkers.createDeviceStyle(feature); + } + }); + + // 创建集群数据源和图层 + clusterSource = new ol.source.Cluster({ + distance: 40, + source: vectorSource + }); + + clusterLayer = new ol.layer.Vector({ + source: clusterSource, + style: function(feature) { + var size = feature.get('features').length; + var style = new ol.style.Style({ + image: new ol.style.Circle({ + radius: 15, + fill: new ol.style.Fill({ + color: '#3399CC' + }) + }), + text: new ol.style.Text({ + text: size.toString(), + fill: new ol.style.Fill({ + color: '#fff' + }) + }) + }); + return style; + } + }); + + // 获取初始地图类型 + var initialMapType = document.getElementById('mapTypeSelectNew').value || 'tianditu_satellite'; + currentBaseLayer = MapLayers.getLayer(initialMapType); + + // 创建地图 + map = new ol.Map({ + target: 'map-container', + layers: [ + currentBaseLayer, + clusterLayer, + vectorLayer + ], + view: new ol.View({ + center: ol.proj.fromLonLat([116.404, 39.915]), + zoom: 7 + }) + }); + + // 初始化各个模块 + DeviceMarkers.init(map, vectorSource, vectorLayer); + MeasureTools.init(map); + WeatherForecast.init(); + + // 添加比例尺控件 + var scaleLineControl = new ol.control.ScaleLine(); + map.addControl(scaleLineControl); + + // 设置初始图层可见性 + var initialZoom = map.getView().getZoom(); + updateLayerVisibility(initialZoom); + + // 绑定缩放变化事件 + map.getView().on('change:resolution', function() { + var zoom = map.getView().getZoom(); + updateLayerVisibility(zoom); + vectorLayer.changed(); + }); + + // 绑定鼠标事件 + bindMouseEvents(); + + // 设置地图中心点 + if (deviceList && deviceList.length > 0) { + setCenterFromDevices(deviceList); + } + + // 添加设备标记 + DeviceMarkers.addDeviceMarkers(deviceList); + + // 获取我的位置并开始位置更新 + DeviceMarkers.getMyLocation(); + DeviceMarkers.startLocationUpdates(); + + // 设置初始开关状态 + document.getElementById('showDeviceIdSwitch').checked = showDeviceId; + document.getElementById('showClusterSwitch').checked = showCluster; + } + + /** + * 更新图层可见性 + * @param {number} zoom - 缩放级别 + */ + function updateLayerVisibility(zoom) { + if (showCluster) { + if (zoom >= maxZoomForClustering) { + clusterLayer.setVisible(false); + vectorLayer.setVisible(true); + } else { + clusterLayer.setVisible(true); + vectorLayer.setVisible(false); + } + } else { + clusterLayer.setVisible(false); + vectorLayer.setVisible(true); + } + } + + /** + * 绑定鼠标事件 + */ + function bindMouseEvents() { + // 鼠标移动事件 + map.on('pointermove', function(evt) { + if (evt.dragging) { + return; + } + + var pixel = map.getEventPixel(evt.originalEvent); + var hit = map.hasFeatureAtPixel(pixel); + + map.getTargetElement().style.cursor = hit ? 'pointer' : ''; + + // 处理悬停效果 + var feature = map.forEachFeatureAtPixel(pixel, function(feature) { + return feature; + }); + + if (hoveredFeature && hoveredFeature !== feature) { + hoveredFeature.set('hovered', false); + } + + if (feature) { + // 处理集群 + var features = feature.get('features'); + if (features && features.length === 1) { + features[0].set('hovered', true); + hoveredFeature = features[0]; + } else if (!features && feature.get('deviceInfo')) { + feature.set('hovered', true); + hoveredFeature = feature; + } + } + + vectorLayer.changed(); + }); + + // 点击事件 + map.on('click', function(evt) { + var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { + return feature; + }); + + if (feature) { + var features = feature.get('features'); + if (features && features.length > 1) { + // 集群点击,扩展视图 + var extent = vectorSource.getExtent(); + map.getView().fit(extent, { + padding: [50, 50, 50, 50], + duration: 1000 + }); + } else if (features && features.length === 1) { + // 单个设备点击 + var deviceInfo = features[0].get('deviceInfo'); + if (deviceInfo) { + showDeviceInfo(deviceInfo); + // 如果天气预测开启,显示天气卡片 + if (WeatherForecast.isEnabled()) { + WeatherForecast.showWeatherForecast(deviceInfo); + } + } + } else if (feature.get('deviceInfo')) { + // 直接点击设备标记 + var deviceInfo = feature.get('deviceInfo'); + showDeviceInfo(deviceInfo); + // 如果天气预测开启,显示天气卡片 + if (WeatherForecast.isEnabled()) { + WeatherForecast.showWeatherForecast(deviceInfo); + } + } + } + }); + } + + /** + * 显示设备信息 + * @param {Object} deviceInfo - 设备信息 + */ + function showDeviceInfo(deviceInfo) { + var infoMsg = '
' + + '

设备编号: ' + deviceInfo.deviceid + '

' + + '

坐标: ' + deviceInfo.latitude.toFixed(6) + ', ' + deviceInfo.longitude.toFixed(6) + '

'; + + if (deviceInfo.warning === 2) { + infoMsg += '

状态: 严重告警

'; + } else if (deviceInfo.warning === 1) { + infoMsg += '

状态: 一般告警

'; + } else { + infoMsg += '

状态: 正常

'; + } + + infoMsg += '
'; + + if (window.layer && typeof window.layer.open === 'function') { + window.layer.open({ + type: 1, + title: '设备信息', + area: ['300px', 'auto'], + shade: 0, + offset: 'auto', + content: infoMsg, + time: 3000, + anim: 2 + }); + } + } + + /** + * 根据设备列表设置地图中心点 + * @param {Array} deviceList - 设备列表 + */ + function setCenterFromDevices(deviceList) { + if (!deviceList || deviceList.length === 0) return; + + var centerLat = 0; + var centerLon = 0; + + for (var i = 0; i < deviceList.length; i++) { + centerLat += deviceList[i].latitude; + centerLon += deviceList[i].longitude; + } + + centerLat = centerLat / deviceList.length; + centerLon = centerLon / deviceList.length; + + map.getView().setCenter(ol.proj.fromLonLat([centerLon, centerLat])); + } + + /** + * 切换地图类型 + * @param {string} mapType - 地图类型 + */ + function switchMapType(mapType) { + if (!MapLayers.hasLayer(mapType)) { + console.error('未知的地图类型:', mapType); + return; + } + + map.removeLayer(currentBaseLayer); + currentBaseLayer = MapLayers.getLayer(mapType); + map.getLayers().insertAt(0, currentBaseLayer); + + DeviceMarkers.updateMyLocationForMapType(mapType); + + // 重新获取设备列表并添加标记 + var deviceList = window.deviceList || []; + DeviceMarkers.addDeviceMarkers(deviceList); + } + + /** + * 切换设备ID显示 + */ + function toggleDeviceId() { + showDeviceId = document.getElementById('showDeviceIdSwitch').checked; + DeviceMarkers.setShowDeviceId(showDeviceId); + + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg(showDeviceId ? '已显示设备信息' : '已隐藏设备信息'); + } + } + + /** + * 切换集群显示 + */ + function toggleCluster() { + showCluster = document.getElementById('showClusterSwitch').checked; + var zoom = map.getView().getZoom(); + updateLayerVisibility(zoom); + + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg(showCluster ? '已启用集群显示' : '已禁用集群显示'); + } + } + + /** + * 切换地图功能菜单 + */ + function toggleMapFunctionsMenu() { + var menu = document.getElementById('mapFunctionsMenu'); + if (menu) { + menu.classList.toggle('show'); + if (menu.classList.contains('show')) { + document.addEventListener('click', closeMapFunctionsMenu); + } + } + } + + /** + * 关闭地图功能菜单 + * @param {Event} event - 点击事件 + */ + function closeMapFunctionsMenu(event) { + var dropdown = document.getElementById('mapFunctionsMenu'); + var toggleBtn = document.querySelector('.dropdown-toggle'); + + if (dropdown && !dropdown.contains(event.target) && !toggleBtn.contains(event.target)) { + dropdown.classList.remove('show'); + document.removeEventListener('click', closeMapFunctionsMenu); + } + } + + /** + * 地图类型变化处理 + */ + function onMapTypeChange() { + var mapType = document.getElementById('mapTypeSelectNew').value; + switchMapType(mapType); + } + + /** + * 告警过滤变化处理 + */ + function onWarningFilterChange() { + var filterValue = document.getElementById('warningFilter').value; + + if (filterValue === 'all') { + SearchFilter.showAllDevices(); + } else if (filterValue === 'warning1') { + SearchFilter.showWarning1Devices(); + } else if (filterValue === 'warning2') { + SearchFilter.showWarning2Devices(); + } + } + + /** + * 搜索设备 + */ + function searchDeviceNew() { + var deviceId = document.getElementById('deviceSearchNew').value.trim(); + + if (!deviceId) { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('请输入设备编号'); + } + return; + } + + SearchFilter.searchDevice(deviceId); + } + + /** + * 获取地图实例 + * @returns {ol.Map} 地图实例 + */ + function getMap() { + return map; + } + + /** + * 获取矢量数据源 + * @returns {ol.source.Vector} 矢量数据源 + */ + function getVectorSource() { + return vectorSource; + } + + /** + * 获取矢量图层 + * @returns {ol.layer.Vector} 矢量图层 + */ + function getVectorLayer() { + return vectorLayer; + } + + // 公开API + return { + initialize: initialize, + switchMapType: switchMapType, + toggleDeviceId: toggleDeviceId, + toggleCluster: toggleCluster, + toggleMapFunctionsMenu: toggleMapFunctionsMenu, + onMapTypeChange: onMapTypeChange, + onWarningFilterChange: onWarningFilterChange, + searchDeviceNew: searchDeviceNew, + getMap: getMap, + getVectorSource: getVectorSource, + getVectorLayer: getVectorLayer + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js b/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js new file mode 100644 index 00000000..e97161af --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/map-layers.js @@ -0,0 +1,195 @@ +/** + * 地图图层管理模块 + * 管理不同类型的地图图层(天地图、高德、谷歌等) + */ +var MapLayers = (function() { + 'use strict'; + + // 天地图 API 密钥 + var TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac'; + + /** + * 创建天地图瓦片加载函数,包含错误处理 + */ + function createTiandituTileLoadFunction() { + return function(imageTile, src) { + imageTile.getImage().src = src; + imageTile.getImage().onerror = function() { + // 天地图加载失败时切换到高德地图 + var mapTypeSelect = document.getElementById('mapTypeSelectNew'); + if(mapTypeSelect && mapTypeSelect.value.startsWith('tianditu_')) { + mapTypeSelect.value = 'amap'; + if (window.DeviceOverview && typeof window.DeviceOverview.switchMapType === 'function') { + window.DeviceOverview.switchMapType('amap'); + } + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('天地图加载失败,已自动切换到高德地图'); + } + // 重新渲染layui表单 + if (window.layui && window.layui.form) { + window.layui.form.render('select'); + } + } + }; + }; + } + + /** + * 地图图层配置 + */ + var mapLayers = { + // 高德地图 + amap: new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}' + }) + }), + + // 高德卫星图 + amap_satellite: new ol.layer.Group({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}' + }) + }), + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}' + }) + }) + ] + }), + + // 谷歌卫星图 + google_satellite: new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt{0-3}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', + crossOrigin: 'anonymous' + }) + }), + + // 谷歌地形图 + google_terrain: new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt{0-3}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}', + crossOrigin: 'anonymous' + }) + }), + + // 谷歌道路图 + google_roadmap: new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', + crossOrigin: 'anonymous' + }) + }), + + // 谷歌混合图 + google_hybrid: new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://mt{0-3}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', + crossOrigin: 'anonymous' + }) + }), + + // 天地图卫星影像 + tianditu_satellite: new ol.layer.Group({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }), + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }) + ] + }), + + // 天地图矢量图 + tianditu_normal: new ol.layer.Group({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }), + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }) + ] + }), + + // 天地图地形图 + tianditu_terrain: new ol.layer.Group({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }) + ] + }), + + // 天地图地形混合图 + tianditu_terrain_hybrid: new ol.layer.Group({ + layers: [ + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }), + new ol.layer.Tile({ + source: new ol.source.XYZ({ + url: 'https://t{0-7}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + TIANDITU_KEY, + tileLoadFunction: createTiandituTileLoadFunction() + }) + }) + ] + }) + }; + + /** + * 获取指定类型的地图图层 + * @param {string} mapType - 地图类型 + * @returns {ol.layer.Base} 地图图层 + */ + function getLayer(mapType) { + return mapLayers[mapType]; + } + + /** + * 获取所有可用的地图图层 + * @returns {Object} 所有地图图层对象 + */ + function getAllLayers() { + return mapLayers; + } + + /** + * 检查地图类型是否存在 + * @param {string} mapType - 地图类型 + * @returns {boolean} 是否存在 + */ + function hasLayer(mapType) { + return mapLayers.hasOwnProperty(mapType); + } + + // 公开API + return { + getLayer: getLayer, + getAllLayers: getAllLayers, + hasLayer: hasLayer + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js b/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js new file mode 100644 index 00000000..48d224d5 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/measure-tools.js @@ -0,0 +1,363 @@ +/** + * 测距工具模块 + * 提供地图测距功能 + */ +var MeasureTools = (function() { + 'use strict'; + + // 私有变量 + var measureActive = false; + var measureDraw = null; + var measureSource = null; + var measureLayer = null; + var measureTooltips = []; + var measureFeatures = []; + var currentMeasureTooltips = []; + var currentSketch = null; + var currentListener = null; + var segmentTooltips = []; + var map = null; + + /** + * 初始化测距工具 + * @param {ol.Map} mapInstance - 地图实例 + */ + function init(mapInstance) { + map = mapInstance; + + // 创建测距专用图层 + measureSource = new ol.source.Vector(); + measureLayer = new ol.layer.Vector({ + source: measureSource, + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }), + stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }), + image: new ol.style.RegularShape({ + points: 4, + radius: 8, + radius2: 0, + angle: Math.PI / 4, + stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 }) + }) + }) + }); + + if (map) { + map.addLayer(measureLayer); + } + } + + /** + * 切换测距功能 + */ + function toggleMeasureDistance() { + if (measureActive) { + deactivateMeasure(); + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('测距功能已关闭'); + } + } else { + activateMeasure(); + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('点击左键添加距离节点,点击右键结束测量 :)'); + } + } + + // 关闭地图功能菜单 + var menu = document.getElementById('mapFunctionsMenu'); + if (menu) { + menu.classList.remove('show'); + } + } + + /** + * 完成当前测量 + */ + function finishMeasuring() { + if (measureActive && currentSketch && measureDraw) { + measureDraw.finishDrawing(); + } + } + + /** + * 激活测距功能 + */ + function activateMeasure() { + if (!map || !measureSource) return; + + measureActive = true; + + // 显示测量状态指示器 + var measureStatus = document.getElementById('measureStatus'); + if (measureStatus) { + measureStatus.style.display = 'flex'; + } + + // 创建绘制交互 + measureDraw = new ol.interaction.Draw({ + source: measureSource, + type: 'LineString', + style: new ol.style.Style({ + fill: new ol.style.Fill({ color: 'rgba(255,255,255,0.2)' }), + stroke: new ol.style.Stroke({ color: '#ffcc33', width: 2 }), + image: new ol.style.RegularShape({ + points: 4, + radius: 8, + radius2: 0, + angle: Math.PI / 4, + stroke: new ol.style.Stroke({ color: '#ed8936', width: 2 }) + }) + }) + }); + + map.addInteraction(measureDraw); + + // 添加右键菜单事件监听 + map.getViewport().addEventListener('contextmenu', function(e) { + if (measureActive && currentSketch) { + e.preventDefault(); + measureDraw.finishDrawing(); + } + }); + + // 绘制开始事件 + measureDraw.on('drawstart', function(evt) { + currentSketch = evt.feature; + currentMeasureTooltips = []; + segmentTooltips = []; + + // 监听几何变化 + currentListener = currentSketch.getGeometry().on('change', function(e) { + var geom = e.target; + var coords = geom.getCoordinates(); + + clearTemporaryTooltips(); + clearSegmentTooltips(); + + // 计算并显示每个节点的距离 + var total = 0; + for (var i = 0; i < coords.length; i++) { + if (i === 0) { + // 起点 + var startTooltip = createMeasureTooltip(coords[0], '起点'); + currentMeasureTooltips.push(startTooltip); + } else { + // 计算段距离 + var seg = new ol.geom.LineString([coords[i-1], coords[i]]); + var segmentLength = ol.sphere.getLength(seg); + total += segmentLength; + + // 显示每个节点的累计距离 + var output = formatLength(total); + var tooltip = createMeasureTooltip(coords[i], output); + segmentTooltips.push(tooltip); + + if (i === coords.length - 1) { + currentMeasureTooltips.push(tooltip); + } + } + } + }); + }); + + // 绘制结束事件 + measureDraw.on('drawend', function(evt) { + var coords = evt.feature.getGeometry().getCoordinates(); + + clearTemporaryTooltips(); + clearSegmentTooltips(); + + // 创建永久性的测量标记 + var total = 0; + for (var i = 0; i < coords.length; i++) { + if (i === 0) { + var startTooltip = createMeasureTooltip(coords[0], '起点', true); + measureTooltips.push(startTooltip); + } else { + var seg = new ol.geom.LineString([coords[i-1], coords[i]]); + total += ol.sphere.getLength(seg); + + var output = formatLength(total); + var tooltip = createMeasureTooltip(coords[i], output, true); + measureTooltips.push(tooltip); + } + } + + measureFeatures.push(evt.feature); + + // 清理监听器 + if (currentListener) { + ol.Observable.unByKey(currentListener); + } + currentSketch = null; + currentListener = null; + + // 隐藏测量状态指示器 + var measureStatus = document.getElementById('measureStatus'); + if (measureStatus) { + measureStatus.style.display = 'none'; + } + + // 移除绘制交互 + map.removeInteraction(measureDraw); + measureActive = false; + + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('测量完成'); + } + }); + } + + /** + * 停用测距功能 + */ + function deactivateMeasure() { + measureActive = false; + + // 隐藏测量状态指示器 + var measureStatus = document.getElementById('measureStatus'); + if (measureStatus) { + measureStatus.style.display = 'none'; + } + + if (measureDraw && map) { + map.removeInteraction(measureDraw); + measureDraw = null; + } + + if (currentListener) { + ol.Observable.unByKey(currentListener); + currentListener = null; + } + + currentSketch = null; + clearTemporaryTooltips(); + clearSegmentTooltips(); + } + + /** + * 创建测量提示框 + * @param {Array} coord - 坐标 + * @param {string} text - 显示文本 + * @param {boolean} isStatic - 是否为静态提示框 + * @returns {ol.Overlay} 覆盖层对象 + */ + function createMeasureTooltip(coord, text, isStatic) { + var elem = document.createElement('div'); + elem.className = isStatic ? 'ol-tooltip ol-tooltip-static' : 'ol-tooltip ol-tooltip-measure'; + elem.innerHTML = text; + + var overlay = new ol.Overlay({ + element: elem, + offset: [0, -15], + positioning: 'bottom-center' + }); + + overlay.setPosition(coord); + + if (map) { + map.addOverlay(overlay); + } + + return overlay; + } + + /** + * 格式化长度显示 + * @param {number} length - 长度(米) + * @returns {string} 格式化后的长度字符串 + */ + function formatLength(length) { + if (length > 1000) { + return (Math.round(length / 100) / 10) + ' km'; + } else { + return (Math.round(length * 10) / 10) + ' m'; + } + } + + /** + * 清除所有测量提示框 + */ + function clearAllMeasureTooltips() { + if (!map) return; + + for (var i = 0; i < measureTooltips.length; i++) { + map.removeOverlay(measureTooltips[i]); + } + measureTooltips = []; + + clearTemporaryTooltips(); + } + + /** + * 清除临时提示框 + */ + function clearTemporaryTooltips() { + if (!map) return; + + for (var i = 0; i < currentMeasureTooltips.length; i++) { + map.removeOverlay(currentMeasureTooltips[i]); + } + currentMeasureTooltips = []; + } + + /** + * 清除段落提示框 + */ + function clearSegmentTooltips() { + if (!map) return; + + for (var i = 0; i < segmentTooltips.length; i++) { + map.removeOverlay(segmentTooltips[i]); + } + segmentTooltips = []; + } + + /** + * 清除所有测量标记 + */ + function clearMeasure() { + if (measureSource) { + measureSource.clear(); + } + + clearAllMeasureTooltips(); + measureFeatures = []; + + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('测距标记已清除'); + } + + // 关闭地图功能菜单 + var menu = document.getElementById('mapFunctionsMenu'); + if (menu) { + menu.classList.remove('show'); + } + } + + /** + * 检查测距功能是否激活 + * @returns {boolean} 是否激活 + */ + function isActive() { + return measureActive; + } + + /** + * 获取测量结果数量 + * @returns {number} 测量结果数量 + */ + function getMeasureCount() { + return measureFeatures.length; + } + + // 公开API + return { + init: init, + toggleMeasureDistance: toggleMeasureDistance, + finishMeasuring: finishMeasuring, + clearMeasure: clearMeasure, + isActive: isActive, + getMeasureCount: getMeasureCount + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js b/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js new file mode 100644 index 00000000..a0cd12fe --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/search-filter.js @@ -0,0 +1,249 @@ +/** + * 搜索和过滤功能模块 + * 负责设备搜索、过滤和查询功能 + */ +var SearchFilter = (function() { + 'use strict'; + + // 私有变量 + var currentSearchedDevice = null; + var markerState = 3; // 1:all; 2:orange; 3:red + + /** + * 搜索设备 + * @param {string} deviceId - 设备ID + * @returns {boolean} 是否找到设备 + */ + function searchDevice(deviceId) { + if (!deviceId || !deviceId.trim()) { + clearSearch(); + return false; + } + + deviceId = deviceId.trim(); + currentSearchedDevice = deviceId; + + var success = DeviceMarkers.locateDevice(deviceId); + + if (success) { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('已定位到设备: ' + deviceId); + } + + // 获取设备信息并显示天气预测(如果启用) + var targetFeature = DeviceMarkers.findDeviceById(deviceId); + if (targetFeature && window.WeatherForecast && window.WeatherForecast.isEnabled()) { + var deviceInfo = targetFeature.get('deviceInfo'); + if (deviceInfo) { + window.WeatherForecast.showWeatherForecast(deviceInfo); + } + } + } else { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('未找到设备: ' + deviceId); + } + } + + return success; + } + + /** + * 清除搜索 + */ + function clearSearch() { + currentSearchedDevice = null; + var searchInput = document.getElementById('deviceSearchNew'); + if (searchInput) { + searchInput.value = ''; + } + + // 恢复到当前的过滤状态 + applyCurrentFilter(); + } + + /** + * 应用当前过滤状态 + */ + function applyCurrentFilter() { + switch(markerState) { + case 1: + showAllDevices(); + break; + case 2: + showWarning1Devices(); + break; + case 3: + showWarning2Devices(); + break; + } + } + + /** + * 显示所有设备 + */ + function showAllDevices() { + currentSearchedDevice = null; + clearSearchInput(); + DeviceMarkers.showAllDevices(); + markerState = 1; + updateFilterSelect('all'); + } + + /** + * 显示一般告警设备 + */ + function showWarning1Devices() { + currentSearchedDevice = null; + clearSearchInput(); + DeviceMarkers.showWarning1Devices(); + markerState = 2; + updateFilterSelect('warning1'); + } + + /** + * 显示严重告警设备 + */ + function showWarning2Devices() { + currentSearchedDevice = null; + clearSearchInput(); + DeviceMarkers.showWarning2Devices(); + markerState = 3; + updateFilterSelect('warning2'); + } + + /** + * 清除搜索输入框 + */ + function clearSearchInput() { + var searchInput = document.getElementById('deviceSearchNew'); + if (searchInput) { + searchInput.value = ''; + } + } + + /** + * 更新过滤选择器 + * @param {string} filterValue - 过滤值 + */ + function updateFilterSelect(filterValue) { + var filterSelect = document.getElementById('warningFilter'); + if (filterSelect) { + filterSelect.value = filterValue; + } + } + + /** + * 根据状态过滤设备 + * @param {string} statusType - 状态类型 + */ + function filterDevicesByStatus(statusType) { + switch(statusType) { + case 'warning1': + showWarning1Devices(); + break; + case 'warning2': + showWarning2Devices(); + break; + case 'offline': + case 'no_fwd': + case 'nofixed': + case 'nogga': + // 对于这些状态,显示所有设备,让用户在弹窗中选择 + showAllDevices(); + break; + default: + showAllDevices(); + break; + } + } + + /** + * 查询设备并打开列表页面 + * @param {string} statusType - 状态类型 + */ + function queryDevices(statusType) { + // 首先过滤地图上的设备显示 + filterDevicesByStatus(statusType); + + // 打开设备列表弹窗 + if (window.layer && typeof window.layer.open === 'function') { + var index = window.layer.open({ + title: '设备列表', + type: 2, + shade: 0.2, + maxmin: true, + shadeClose: true, + anim: 2, + offset: 'rb', + area: ['100%', '50%'], + content: '../page/gnss_q_status?query=' + statusType, + }); + } + } + + /** + * 直接定位到设备(用于外部调用) + * @param {string} deviceId - 设备ID + * @param {number} latitude - 纬度(可选) + * @param {number} longitude - 经度(可选) + */ + function locateDeviceOnMap(deviceId, latitude, longitude) { + currentSearchedDevice = deviceId; + var success = DeviceMarkers.locateDevice(deviceId); + + if (success && window.layer && typeof window.layer.closeAll === 'function') { + window.layer.closeAll(); + } + + return success; + } + + /** + * 直接定位设备(简化版) + * @param {string} deviceId - 设备ID + */ + function locateDeviceDirectly(deviceId) { + currentSearchedDevice = deviceId; + return DeviceMarkers.locateDevice(deviceId); + } + + /** + * 获取当前搜索的设备ID + * @returns {string|null} 当前搜索的设备ID + */ + function getCurrentSearchedDevice() { + return currentSearchedDevice; + } + + /** + * 获取当前标记状态 + * @returns {number} 标记状态 + */ + function getMarkerState() { + return markerState; + } + + /** + * 设置标记状态 + * @param {number} state - 标记状态 + */ + function setMarkerState(state) { + markerState = state; + } + + // 公开API + return { + searchDevice: searchDevice, + clearSearch: clearSearch, + showAllDevices: showAllDevices, + showWarning1Devices: showWarning1Devices, + showWarning2Devices: showWarning2Devices, + filterDevicesByStatus: filterDevicesByStatus, + queryDevices: queryDevices, + locateDeviceOnMap: locateDeviceOnMap, + locateDeviceDirectly: locateDeviceDirectly, + getCurrentSearchedDevice: getCurrentSearchedDevice, + getMarkerState: getMarkerState, + setMarkerState: setMarkerState + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js b/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js new file mode 100644 index 00000000..57fde0d6 --- /dev/null +++ b/sec-beidou/src/main/resources/static/js/device-overview-module/weather-forecast.js @@ -0,0 +1,513 @@ +/** + * 天气预报模块 + * 提供Windy天气API调用和天气卡片显示功能 + */ +var WeatherForecast = (function() { + 'use strict'; + + // 私有变量 + var weatherApiKey = 'Uxh4IdMuAvhSiBnsf4UUDVGF4e3YAp2B'; + var weatherEnabled = false; + var weatherData = null; + var currentForecastIndex = 0; + var currentWeatherDevice = null; + var isDragging = false; + + /** + * 初始化天气预报模块 + */ + function init() { + initWeatherCardDrag(); + } + + /** + * 切换天气预报功能 + */ + function toggleWeatherForecast() { + // 权限检查 + var role = window.userRole || 'USER'; // 从全局变量获取角色 + if (role !== 'SUPER_ADMIN') { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('您没有权限使用此功能'); + } + return; + } + + var enableSwitch = document.getElementById('enableWeatherSwitch'); + weatherEnabled = enableSwitch ? enableSwitch.checked : false; + + if (weatherEnabled) { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg( + '搜索设备或点击地图设备图标即可自动查询天气预测', + {time: 3000, area: ['300px', '80px']} + ); + } + } else { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('天气预测功能已关闭'); + } + closeWeatherCard(); + } + + // 关闭地图功能菜单 + var menu = document.getElementById('mapFunctionsMenu'); + if (menu) { + menu.classList.remove('show'); + } + } + + /** + * 显示天气预报卡片 + * @param {Object} deviceInfo - 设备信息 + */ + function showWeatherForecast(deviceInfo) { + currentWeatherDevice = deviceInfo; + weatherData = null; + + // 权限检查 + var role = window.userRole || 'USER'; + if (role !== 'SUPER_ADMIN') { + return; + } + + if (!weatherEnabled) { + if (window.layer && typeof window.layer.msg === 'function') { + window.layer.msg('天气预测功能未启用', {time: 2000}); + } + return; + } + + // 更新设备信息显示 + var deviceIdElement = document.getElementById('weatherDeviceId'); + var deviceCoordsElement = document.getElementById('weatherDeviceCoords'); + + if (deviceIdElement) { + deviceIdElement.textContent = '设备: ' + deviceInfo.deviceid; + } + + if (deviceCoordsElement) { + deviceCoordsElement.textContent = + '坐标: ' + deviceInfo.latitude.toFixed(4) + ', ' + deviceInfo.longitude.toFixed(4); + } + + // 显示天气卡片 + var weatherCard = document.getElementById('weatherForecastCard'); + if (weatherCard) { + weatherCard.style.display = 'block'; + } + + initWeatherCardDrag(); + + // 显示加载状态 + var contentElement = document.getElementById('weatherForecastContent'); + if (contentElement) { + contentElement.innerHTML = + '
' + + '' + + '

请确保网络可访问 Windy API 服务

' + + '

正在获取天气预测数据...

' + + '
'; + } + + // 重置翻页按钮 + var prevBtn = document.getElementById('prevForecast'); + var nextBtn = document.getElementById('nextForecast'); + var timeDisplay = document.getElementById('forecastTimeDisplay'); + + if (prevBtn) prevBtn.disabled = true; + if (nextBtn) nextBtn.disabled = true; + if (timeDisplay) timeDisplay.textContent = '--:--'; + + // 调用天气API + fetchWeatherData(deviceInfo.latitude, deviceInfo.longitude); + } + + /** + * 关闭天气卡片 + */ + function closeWeatherCard() { + var weatherCard = document.getElementById('weatherForecastCard'); + if (weatherCard) { + weatherCard.style.display = 'none'; + } + currentWeatherDevice = null; + weatherData = null; + } + + /** + * 显示上一个预报 + */ + function showPrevForecast() { + if (!weatherData || currentForecastIndex <= 0) return; + + currentForecastIndex--; + displayCurrentForecast(); + updateForecastNavigation(); + } + + /** + * 显示下一个预报 + */ + function showNextForecast() { + if (!weatherData || !weatherData.ts || currentForecastIndex >= weatherData.ts.length - 1) return; + + currentForecastIndex++; + displayCurrentForecast(); + updateForecastNavigation(); + } + + /** + * 更新预报导航按钮 + */ + function updateForecastNavigation() { + if (!weatherData || !weatherData.ts) return; + + var prevBtn = document.getElementById('prevForecast'); + var nextBtn = document.getElementById('nextForecast'); + var timeDisplay = document.getElementById('forecastTimeDisplay'); + + if (prevBtn) prevBtn.disabled = (currentForecastIndex <= 0); + if (nextBtn) nextBtn.disabled = (currentForecastIndex >= weatherData.ts.length - 1); + + if (timeDisplay) { + var timestamp = weatherData.ts[currentForecastIndex]; + var date = new Date(timestamp); + timeDisplay.textContent = formatDateTime(date); + } + } + + /** + * 获取天气数据 + * @param {number} lat - 纬度 + * @param {number} lon - 经度 + */ + function fetchWeatherData(lat, lon) { + var requestBody = { + "lat": parseFloat(lat.toFixed(2)), + "lon": parseFloat(lon.toFixed(2)), + "model": "gfs", // 使用全球预报系统模型 + "parameters": ["temp", "wind", "precip", "pressure", "rh", "windGust"], + "levels": ["surface"], + "key": weatherApiKey, + "hours": 72 // 请求未来72小时的预报数据 + }; + + fetch('https://api.windy.com/api/point-forecast/v2', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(requestBody) + }) + .then(function(response) { + if (!response.ok) { + throw new Error('网络响应状态: ' + response.status); + } + return response.json(); + }) + .then(function(data) { + weatherData = data; + + // 找到最接近当前时间的预报索引,优先选择未来时间点 + var currentTime = new Date().getTime(); + var closestIndex = 0; + var futureIndex = -1; + + if (weatherData.ts && weatherData.ts.length > 0) { + // 首先尝试找到第一个未来时间点 + for (var i = 0; i < weatherData.ts.length; i++) { + if (weatherData.ts[i] > currentTime) { + futureIndex = i; + break; + } + } + + // 如果找到了未来时间点,直接使用 + if (futureIndex >= 0) { + closestIndex = futureIndex; + } else { + // 否则找最接近的时间点 + var smallestDiff = Number.MAX_VALUE; + for (var i = 0; i < weatherData.ts.length; i++) { + var diff = Math.abs(weatherData.ts[i] - currentTime); + if (diff < smallestDiff) { + smallestDiff = diff; + closestIndex = i; + } + } + } + } + + currentForecastIndex = closestIndex; + displayCurrentForecast(); + updateForecastNavigation(); + }) + .catch(function(error) { + console.error('天气数据获取失败:', error); + displayWeatherError('获取天气数据失败: ' + error.message); + }); + } + + /** + * 显示当前预报 + */ + function displayCurrentForecast() { + if (!weatherData || !weatherData.ts || weatherData.ts.length === 0) { + displayWeatherError('无可用的天气预测数据'); + return; + } + + var i = currentForecastIndex; + var forecastHtml = '
'; + + // 温度 + if (weatherData['temp-surface'] && weatherData['temp-surface'][i] !== null) { + var temp = (weatherData['temp-surface'][i] - 273.15).toFixed(1); // 转换为摄氏度 + forecastHtml += createWeatherParam('温度', temp + '°C'); + } + + // 风速和风向 + if (weatherData['wind_u-surface'] && weatherData['wind_v-surface'] && + weatherData['wind_u-surface'][i] !== null && weatherData['wind_v-surface'][i] !== null) { + var windU = weatherData['wind_u-surface'][i]; + var windV = weatherData['wind_v-surface'][i]; + var windSpeed = Math.sqrt(windU * windU + windV * windV).toFixed(1); + var windDir = getWindDirection(windU, windV); + forecastHtml += createWeatherParam('风速', windSpeed + ' m/s'); + forecastHtml += createWeatherParam('风向', windDir); + } + + // 降水 + if (weatherData['past3hprecip-surface'] && weatherData['past3hprecip-surface'][i] !== null) { + var precip = weatherData['past3hprecip-surface'][i].toFixed(1); + forecastHtml += createWeatherParam('降水', precip + ' mm'); + } + + // 湿度 + if (weatherData['rh-surface'] && weatherData['rh-surface'][i] !== null) { + var humidity = weatherData['rh-surface'][i].toFixed(0); + forecastHtml += createWeatherParam('湿度', humidity + '%'); + } + + // 气压 + if (weatherData['pressure-surface'] && weatherData['pressure-surface'][i] !== null) { + var pressure = (weatherData['pressure-surface'][i] / 100).toFixed(0); // 转换为百帕 + forecastHtml += createWeatherParam('气压', pressure + ' hPa'); + } + + // 阵风 + if (weatherData['gust-surface'] && weatherData['gust-surface'][i] !== null) { + var gust = weatherData['gust-surface'][i].toFixed(1); + forecastHtml += createWeatherParam('阵风', gust + ' m/s'); + } + + forecastHtml += '
'; + + var contentElement = document.getElementById('weatherForecastContent'); + if (contentElement) { + contentElement.innerHTML = forecastHtml; + } + } + + /** + * 显示天气错误信息 + * @param {string} message - 错误消息 + */ + function displayWeatherError(message) { + var contentElement = document.getElementById('weatherForecastContent'); + if (contentElement) { + contentElement.innerHTML = + '
' + + '' + + '

' + message + '

' + + '
'; + } + } + + /** + * 创建天气参数HTML + * @param {string} label - 参数标签 + * @param {string} value - 参数值 + * @returns {string} HTML字符串 + */ + function createWeatherParam(label, value) { + return '
' + + '' + label + '' + + '' + value + '' + + '
'; + } + + /** + * 格式化日期时间 + * @param {Date} date - 日期对象 + * @returns {string} 格式化后的日期时间字符串 + */ + function formatDateTime(date) { + var month = (date.getMonth() + 1).toString().padStart(2, '0'); + var day = date.getDate().toString().padStart(2, '0'); + var hours = date.getHours().toString().padStart(2, '0'); + var minutes = date.getMinutes().toString().padStart(2, '0'); + + return month + '-' + day + ' ' + hours + ':' + minutes; + } + + /** + * 获取风向 + * @param {number} u - U分量 + * @param {number} v - V分量 + * @returns {string} 风向字符串 + */ + function getWindDirection(u, v) { + var angle = Math.atan2(-u, -v) * 180 / Math.PI; + angle = (angle + 360) % 360; + + var directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']; + var index = Math.round(angle / 45) % 8; + return directions[index]; + } + + /** + * 初始化天气卡片拖拽功能 + */ + function initWeatherCardDrag() { + var weatherCard = document.getElementById('weatherForecastCard'); + if (!weatherCard) return; + + var startX, startY; + var startLeft, startTop; + + var originalStyles = { + top: '55%', + right: '20px', + left: 'auto', + transform: 'translateY(-50%)' + }; + + var cardHeader = weatherCard.querySelector('.weather-card-header'); + if (!cardHeader) return; + + // 双击重置位置 + cardHeader.addEventListener('dblclick', function() { + weatherCard.style.top = originalStyles.top; + weatherCard.style.right = originalStyles.right; + weatherCard.style.left = originalStyles.left; + weatherCard.style.transform = originalStyles.transform; + }); + + // 鼠标拖拽 + cardHeader.addEventListener('mousedown', function(e) { + if (e.target.tagName === 'BUTTON' || + e.target.classList.contains('weather-nav-btn') || + e.target.classList.contains('weather-close-btn')) { + return; + } + + e.preventDefault(); + startDrag(e.clientX, e.clientY); + }); + + // 触摸拖拽 + cardHeader.addEventListener('touchstart', function(e) { + if (e.target.tagName === 'BUTTON' || + e.target.classList.contains('weather-nav-btn') || + e.target.classList.contains('weather-close-btn')) { + return; + } + + e.preventDefault(); + var touch = e.touches[0]; + startDrag(touch.clientX, touch.clientY); + }); + + function startDrag(clientX, clientY) { + startX = clientX; + startY = clientY; + + var rect = weatherCard.getBoundingClientRect(); + startLeft = rect.left; + startTop = rect.top; + + isDragging = true; + + // 设置为绝对定位 + if (getComputedStyle(weatherCard).position !== 'absolute') { + weatherCard.style.position = 'absolute'; + weatherCard.style.top = rect.top + 'px'; + weatherCard.style.left = rect.left + 'px'; + weatherCard.style.right = 'auto'; + weatherCard.style.transform = 'none'; + } + + weatherCard.style.opacity = '0.9'; + weatherCard.style.boxShadow = '0 12px 48px rgba(0, 0, 0, 0.25)'; + weatherCard.style.zIndex = '1200'; + document.body.style.userSelect = 'none'; + } + + // 鼠标移动事件 + document.addEventListener('mousemove', function(e) { + if (!isDragging) return; + moveDrag(e.clientX, e.clientY); + }); + + // 触摸移动事件 + document.addEventListener('touchmove', function(e) { + if (!isDragging) return; + e.preventDefault(); + var touch = e.touches[0]; + moveDrag(touch.clientX, touch.clientY); + }); + + function moveDrag(clientX, clientY) { + var dx = clientX - startX; + var dy = clientY - startY; + + var newLeft = startLeft + dx; + var newTop = startTop + dy; + + // 限制在视口内 + var maxX = window.innerWidth - weatherCard.offsetWidth; + var maxY = window.innerHeight - weatherCard.offsetHeight; + + newLeft = Math.max(0, Math.min(newLeft, maxX)); + newTop = Math.max(0, Math.min(newTop, maxY)); + + weatherCard.style.left = newLeft + 'px'; + weatherCard.style.top = newTop + 'px'; + } + + // 结束拖拽 + document.addEventListener('mouseup', endDrag); + document.addEventListener('touchend', endDrag); + + function endDrag() { + if (!isDragging) return; + + isDragging = false; + weatherCard.style.opacity = '1'; + weatherCard.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.15)'; + document.body.style.userSelect = ''; + } + } + + /** + * 检查天气功能是否启用 + * @returns {boolean} 是否启用 + */ + function isEnabled() { + return weatherEnabled; + } + + // 公开API + return { + init: init, + toggleWeatherForecast: toggleWeatherForecast, + showWeatherForecast: showWeatherForecast, + closeWeatherCard: closeWeatherCard, + showPrevForecast: showPrevForecast, + showNextForecast: showNextForecast, + isEnabled: isEnabled + }; +})(); \ No newline at end of file diff --git a/sec-beidou/src/main/resources/templates/page/device_overview.html b/sec-beidou/src/main/resources/templates/page/device_overview.html index cb43ec11..5fa39e60 100644 --- a/sec-beidou/src/main/resources/templates/page/device_overview.html +++ b/sec-beidou/src/main/resources/templates/page/device_overview.html @@ -795,218 +795,200 @@ - -
-
-
-
- - 设备数量 - 1101 -
-
- - 装机量 - 1123 -
-
- - 掉线数 - 22 -
-
- - 未推送数 - 11 -
-
- - 长期无效解 - 10 -
-
- - 严重告警 - 40 -
-
- - 一般告警 - 79 -
-
- - 无GGA告警 - 4 +
+
+
+
+ + 设备数量 + 1101 +
+
+ + 装机量 + 1123 +
+
+ + 掉线数 + 22 +
+
+ + 未推送数 + 11 +
+
+ + 长期无效解 + 10 +
+
+ + 严重告警 + 40 +
+
+ + 一般告警 + 79 +
+
+ + 无GGA告警 + 4 +
-
- -
-
- -
-