444 lines
16 KiB
JavaScript
444 lines
16 KiB
JavaScript
// 地图相关功能
|
|
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;
|