feat: 优化页面效果
This commit is contained in:
parent
e4ec7a13eb
commit
6ecc833a2c
@ -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
|
||||
};
|
||||
})();
|
||||
@ -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; }
|
||||
};
|
||||
})();
|
||||
@ -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;
|
||||
@ -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 = '<div style="padding: 10px; line-height: 1.5;">' +
|
||||
'<p><strong>设备编号:</strong> ' + deviceInfo.deviceid + '</p>' +
|
||||
'<p><strong>坐标:</strong> ' + deviceInfo.latitude.toFixed(6) + ', ' + deviceInfo.longitude.toFixed(6) + '</p>';
|
||||
|
||||
if (deviceInfo.warning === 2) {
|
||||
infoMsg += '<p><strong>状态:</strong> <span style="color: #f56565;">严重告警</span></p>';
|
||||
} else if (deviceInfo.warning === 1) {
|
||||
infoMsg += '<p><strong>状态:</strong> <span style="color: #ed8936;">一般告警</span></p>';
|
||||
} else {
|
||||
infoMsg += '<p><strong>状态:</strong> <span style="color: #48bb78;">正常</span></p>';
|
||||
}
|
||||
|
||||
infoMsg += '</div>';
|
||||
|
||||
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
|
||||
};
|
||||
})();
|
||||
@ -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
|
||||
};
|
||||
})();
|
||||
@ -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
|
||||
};
|
||||
})();
|
||||
@ -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
|
||||
};
|
||||
})();
|
||||
@ -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 =
|
||||
'<div class="weather-loading">' +
|
||||
'<i class="layui-icon layui-icon-loading layui-icon-anim-rotate"></i>' +
|
||||
'<p>请确保网络可访问 Windy API 服务</p>' +
|
||||
'<p>正在获取天气预测数据...</p>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// 重置翻页按钮
|
||||
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 = '<div class="weather-forecast-item"><div class="weather-param-grid">';
|
||||
|
||||
// 温度
|
||||
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 += '</div></div>';
|
||||
|
||||
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 =
|
||||
'<div class="weather-error">' +
|
||||
'<i class="layui-icon layui-icon-close"></i>' +
|
||||
'<p>' + message + '</p>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建天气参数HTML
|
||||
* @param {string} label - 参数标签
|
||||
* @param {string} value - 参数值
|
||||
* @returns {string} HTML字符串
|
||||
*/
|
||||
function createWeatherParam(label, value) {
|
||||
return '<div class="weather-param">' +
|
||||
'<span class="weather-param-label">' + label + '</span>' +
|
||||
'<span class="weather-param-value">' + value + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
* @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
|
||||
};
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user