// 地图相关功能 const WeatherMap = { map: null, stations: [], stationLayer: null, clusterLayer: null, clusterSource: null, singleStationLayer: null, combinedChart: null, CLUSTER_THRESHOLD: 10, isMapCollapsed: false, // 初始化地图 init(tiandituKey) { this.initializeLayers(); this.initializeMap(tiandituKey); this.setupEventListeners(); }, // 初始化图层 initializeLayers() { this.stationLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); this.clusterSource = new ol.source.Cluster({ distance: 60, minDistance: 20, source: this.stationLayer.getSource(), geometryFunction: function(feature) { return feature.getGeometry(); } }); this.clusterLayer = new ol.layer.Vector({ source: this.clusterSource, style: (feature) => this.createClusterStyle(feature) }); this.singleStationLayer = new ol.layer.Vector({ source: this.stationLayer.getSource(), style: (feature) => this.createStationStyle(feature), visible: false }); }, // 初始化地图 initializeMap(tiandituKey) { const layers = this.createMapLayers(tiandituKey); this.layers = layers; this.map = new ol.Map({ target: 'map', layers: [ layers.satellite, layers.vector, layers.terrain, layers.hybrid, this.clusterLayer, this.singleStationLayer ], view: new ol.View({ center: ol.proj.fromLonLat([108, 35]), zoom: 5, minZoom: 3, maxZoom: 18 }) }); // 初始化时设置图层状态 const initialZoom = this.map.getView().getZoom(); this.updateClusterDistance(initialZoom); this.updateLayerVisibility(initialZoom); }, // 创建地图图层 createMapLayers(tiandituKey) { return { 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=${tiandituKey}` }) }), 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=${tiandituKey}` }) }) ] }), vector: 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=${tiandituKey}` }) }), 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=${tiandituKey}` }) }) ], visible: false }), 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=${tiandituKey}` }) }), 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=${tiandituKey}` }) }) ], visible: false }), hybrid: 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=${tiandituKey}` }) }), 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=${tiandituKey}` }) }) ], visible: false }) }; }, // 设置事件监听 setupEventListeners() { // 监听缩放事件 this.map.getView().on('change:resolution', () => { const zoom = this.map.getView().getZoom(); this.updateClusterDistance(zoom); this.updateLayerVisibility(zoom); }); // 添加点击事件 this.map.on('click', (event) => this.handleMapClick(event)); // 添加鼠标移动事件 this.map.on('pointermove', (event) => { const pixel = this.map.getEventPixel(event.originalEvent); const hit = this.map.hasFeatureAtPixel(pixel); this.map.getTargetElement().style.cursor = hit ? 'pointer' : ''; }); }, // 处理地图点击 handleMapClick(event) { const feature = this.map.forEachFeatureAtPixel(event.pixel, feature => feature); if (!feature) return; const features = feature.get('features'); if (features && features.length > 1) { this.handleClusterClick(features); } else { this.handleStationClick(features ? features[0] : feature); } }, // 处理集群点击 handleClusterClick(features) { const extent = ol.extent.createEmpty(); features.forEach(feature => { ol.extent.extend(extent, feature.getGeometry().getExtent()); }); const zoom = this.map.getView().getZoom(); const targetZoom = Math.min( features.length <= 5 ? this.CLUSTER_THRESHOLD : zoom + 2, this.CLUSTER_THRESHOLD ); this.map.getView().fit(extent, { padding: [100, 100, 100, 100], duration: 800, maxZoom: targetZoom }); }, // 处理站点点击 handleStationClick(feature) { const decimalId = feature.get('decimalId'); if (!decimalId) return; document.getElementById('stationInput').value = decimalId; const center = feature.getGeometry().getCoordinates(); const currentZoom = this.map.getView().getZoom(); if (currentZoom < this.CLUSTER_THRESHOLD) { this.map.getView().animate({ center: center, zoom: this.CLUSTER_THRESHOLD, duration: 500 }); } if (document.getElementById('interval').value === '1hour') { // 触发查询事件 window.dispatchEvent(new CustomEvent('query-history-data')); } }, // 更新集群距离 updateClusterDistance(zoom) { let distance; if (zoom < 5) distance = 120; else if (zoom < 7) distance = 90; else if (zoom < 9) distance = 60; else if (zoom < this.CLUSTER_THRESHOLD) distance = 40; else distance = 0; this.clusterSource.setDistance(distance); this.clusterSource.refresh(); setTimeout(() => { this.clusterLayer.changed(); this.singleStationLayer.changed(); }, 100); }, // 更新图层可见性 updateLayerVisibility(zoom) { if (zoom >= this.CLUSTER_THRESHOLD) { this.clusterLayer.setVisible(false); this.singleStationLayer.setVisible(true); } else { this.clusterLayer.setVisible(true); this.singleStationLayer.setVisible(false); } }, // 创建站点样式 createStationStyle(feature) { const isOnline = new Date(feature.get('lastUpdate')) > new Date(Date.now() - 5*60*1000); const zoom = this.map ? this.map.getView().getZoom() : 10; let labelText = ''; if (zoom >= this.CLUSTER_THRESHOLD - 2) { labelText = `${feature.get('decimalId') || '未知'} | ${feature.get('name') || '未知'} | ${feature.get('location') || '未知'}`; } return new ol.style.Style({ image: this.getMarkerIconStyle(isOnline).getImage(), text: new ol.style.Text({ text: labelText, font: '11px Arial', offsetY: -24, textAlign: 'center', textBaseline: 'bottom', fill: new ol.style.Fill({ color: '#666' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) }) }); }, // 创建集群样式 createClusterStyle(feature) { const features = feature.get('features'); const size = features.length; const zoom = this.map.getView().getZoom(); if (zoom < this.CLUSTER_THRESHOLD) { if (size > 1) { const radius = Math.min(16 + size * 0.8, 32); const fontSize = Math.min(11 + size/12, 16); return new ol.style.Style({ image: new ol.style.Circle({ radius: radius, fill: new ol.style.Fill({ color: 'rgba(0, 123, 255, 0.8)' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) }), text: new ol.style.Text({ text: String(size), fill: new ol.style.Fill({ color: '#fff' }), font: `bold ${fontSize}px Arial`, offsetY: 1 }) }); } return new ol.style.Style({ image: new ol.style.Circle({ radius: 6, fill: new ol.style.Fill({ color: new Date(features[0].get('lastUpdate')) > new Date(Date.now() - 5*60*1000) ? '#007bff' : '#6c757d' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) }) }); } if (size === 1) { return this.createStationStyle(features[0]); } const radius = Math.min(16 + size * 0.8, 32); const fontSize = Math.min(11 + size/12, 16); return new ol.style.Style({ image: new ol.style.Circle({ radius: radius, fill: new ol.style.Fill({ color: 'rgba(0, 123, 255, 0.8)' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) }), text: new ol.style.Text({ text: String(size), fill: new ol.style.Fill({ color: '#fff' }), font: `bold ${fontSize}px Arial`, offsetY: 1 }) }); }, // 获取标记图标样式 getMarkerIconStyle(isOnline) { const key = isOnline ? 'online' : 'offline'; if (this.markerStyleCache?.[key]) return this.markerStyleCache[key]; const iconPath = isOnline ? '/static/images/marker-online.svg' : '/static/images/marker-offline.svg'; const style = new ol.style.Style({ image: new ol.style.Icon({ src: iconPath, anchor: [0.5, 1], anchorXUnits: 'fraction', anchorYUnits: 'fraction', scale: 0.9 }) }); if (!this.markerStyleCache) this.markerStyleCache = {}; this.markerStyleCache[key] = style; return style; }, // 切换地图图层 switchLayer(layerType) { Object.keys(this.layers).forEach(key => { this.layers[key].setVisible(key === layerType); }); }, // 切换地图折叠状态 toggleMap() { this.isMapCollapsed = !this.isMapCollapsed; const mapContainer = document.getElementById('mapContainer'); const toggleBtn = document.getElementById('toggleMapBtn'); if (this.isMapCollapsed) { mapContainer.classList.add('collapsed'); toggleBtn.textContent = '展开地图'; } else { mapContainer.classList.remove('collapsed'); toggleBtn.textContent = '折叠地图'; } setTimeout(() => { this.map.updateSize(); }, 300); }, // 加载站点数据 async loadStations() { try { const response = await fetch('/api/stations'); this.stations = await response.json(); // 更新WH65LP设备数量 const wh65lpDevices = this.stations.filter(station => station.device_type === 'WH65LP'); document.getElementById('wh65lpCount').textContent = wh65lpDevices.length; this.displayStationsOnMap(); } catch (error) { console.error('加载站点失败:', error); } }, // 在地图上显示站点 displayStationsOnMap() { const source = this.stationLayer.getSource(); source.clear(); const now = Date.now(); const fiveMinutesAgo = now - 5 * 60 * 1000; let onlineCount = 0; let offlineCount = 0; this.stations.forEach(station => { if (station.latitude && station.longitude) { const isOnline = new Date(station.last_update) > new Date(fiveMinutesAgo); if (isOnline) onlineCount++; else offlineCount++; const feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([station.longitude, station.latitude])), stationId: station.station_id, decimalId: station.decimal_id, name: station.name, location: station.location, lastUpdate: station.last_update, isOnline: isOnline }); source.addFeature(feature); } }); console.log(`已加载 ${this.stations.length} 个站点,在线: ${onlineCount},离线: ${offlineCount}`); if (source.getFeatures().length > 0) { if (source.getFeatures().length === 1) { const feature = source.getFeatures()[0]; this.map.getView().setCenter(feature.getGeometry().getCoordinates()); this.map.getView().setZoom(12); } else { const extent = source.getExtent(); this.map.getView().fit(extent, { padding: [50, 50, 50, 50], maxZoom: 10 }); } } this.updateClusterDistance(this.map.getView().getZoom()); } }; // 导出地图对象 window.WeatherMap = WeatherMap;