feat: 新增地图边界
This commit is contained in:
parent
1cc2d5f519
commit
97c0df46bf
@ -30,6 +30,7 @@
|
|||||||
<option value="hybrid">混合地形图</option>
|
<option value="hybrid">混合地形图</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
<label class="text-sm text-gray-600">预报源</label>
|
<label class="text-sm text-gray-600">预报源</label>
|
||||||
<select class="px-2 py-1 border rounded text-sm" [(ngModel)]="provider">
|
<select class="px-2 py-1 border rounded text-sm" [(ngModel)]="provider">
|
||||||
<option value="">不显示预报</option>
|
<option value="">不显示预报</option>
|
||||||
@ -91,6 +92,17 @@
|
|||||||
<button class="map-toggle-btn bg-blue-600 hover:bg-blue-700 text-white" style="position:absolute;top:10px;right:10px;z-index:1001;border-radius:4px;padding:5px 10px;font-size:12px;font-weight:bold;" (click)="toggleMap()">{{ isMapCollapsed ? '展开地图' : '折叠地图' }}</button>
|
<button class="map-toggle-btn bg-blue-600 hover:bg-blue-700 text-white" style="position:absolute;top:10px;right:10px;z-index:1001;border-radius:4px;padding:5px 10px;font-size:12px;font-weight:bold;" (click)="toggleMap()">{{ isMapCollapsed ? '展开地图' : '折叠地图' }}</button>
|
||||||
<div id="tileValueTooltip" style="position:absolute;pointer-events:none;z-index:1003;display:none;background:rgba(0,0,0,0.65);color:#fff;font-size:12px;padding:4px 6px;border-radius:4px;"></div>
|
<div id="tileValueTooltip" style="position:absolute;pointer-events:none;z-index:1003;display:none;background:rgba(0,0,0,0.65);color:#fff;font-size:12px;padding:4px 6px;border-radius:4px;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 全屏弹窗:KML 详情 -->
|
||||||
|
<div *ngIf="isKmlDialogOpen" class="fixed inset-0 z-50" style="background:rgba(0,0,0,0.45);display:flex;align-items:center;justify-content:center;">
|
||||||
|
<div class="bg-white rounded shadow-lg" style="width: min(92vw, 720px); max-height: 80vh; overflow: hidden; border: 1px solid #ddd;">
|
||||||
|
<div class="px-4 py-2 border-b flex items-center justify-between" style="border-color:#eee;">
|
||||||
|
<div class="font-semibold text-base">{{kmlInfoTitle}}</div>
|
||||||
|
<button class="text-sm text-gray-600 hover:text-gray-900" (click)="closeKmlPopup()">关闭</button>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 text-sm leading-6" style="overflow:auto; max-height: calc(80vh - 48px);" [innerHTML]="kmlInfoHtml"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="showPanels" id="chartSection" class="border rounded p-3 mb-4" style="border-color:#ddd;">
|
<div *ngIf="showPanels" id="chartSection" class="border rounded p-3 mb-4" style="border-color:#ddd;">
|
||||||
<div class="font-bold mb-5 mt-5 text-center">{{ selectedTitle }}</div>
|
<div class="font-bold mb-5 mt-5 text-center">{{ selectedTitle }}</div>
|
||||||
|
|||||||
@ -55,6 +55,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
private clusterSource: any;
|
private clusterSource: any;
|
||||||
private stationLayer: any;
|
private stationLayer: any;
|
||||||
private clusterLayer: any;
|
private clusterLayer: any;
|
||||||
|
private kmlLayer: any;
|
||||||
|
private kmlOverlay: any;
|
||||||
private CLUSTER_THRESHOLD = 10;
|
private CLUSTER_THRESHOLD = 10;
|
||||||
private tileOverlayGroup: any;
|
private tileOverlayGroup: any;
|
||||||
private tileLastList: any[] = [];
|
private tileLastList: any[] = [];
|
||||||
@ -66,6 +68,9 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
tileDt = '';
|
tileDt = '';
|
||||||
tileProduct: 'none'|'radar'|'rain' = 'radar';
|
tileProduct: 'none'|'radar'|'rain' = 'radar';
|
||||||
isMapCollapsed = false;
|
isMapCollapsed = false;
|
||||||
|
kmlInfoTitle = '';
|
||||||
|
kmlInfoHtml = '';
|
||||||
|
isKmlDialogOpen = false;
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await Promise.all([this.loadStatus(), this.loadStations()]);
|
await Promise.all([this.loadStatus(), this.loadStations()]);
|
||||||
@ -138,16 +143,28 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.stationLayer = new ol.layer.Vector({ source: this.stationSource, visible: false, style: (f:any)=> this.createStationStyle(f) });
|
this.stationLayer = new ol.layer.Vector({ source: this.stationSource, visible: false, style: (f:any)=> this.createStationStyle(f) });
|
||||||
this.tileOverlayGroup = new ol.layer.Group({ layers: [], zIndex: 999, visible: true });
|
this.tileOverlayGroup = new ol.layer.Group({ layers: [], zIndex: 999, visible: true });
|
||||||
|
|
||||||
|
// Load KML overlay from /static/kml/selected_polygons.kml
|
||||||
|
try {
|
||||||
|
const kmlSource = new ol.source.Vector({
|
||||||
|
url: '/static/kml/selected_polygons.kml',
|
||||||
|
format: new ol.format.KML({ extractStyles: true })
|
||||||
|
});
|
||||||
|
this.kmlLayer = new ol.layer.Vector({ source: kmlSource, zIndex: 800, visible: true });
|
||||||
|
} catch {}
|
||||||
|
|
||||||
this.map = new ol.Map({ target: 'map', layers: [
|
this.map = new ol.Map({ target: 'map', layers: [
|
||||||
this.layers.satellite,
|
this.layers.satellite,
|
||||||
this.layers.vector,
|
this.layers.vector,
|
||||||
this.layers.terrain,
|
this.layers.terrain,
|
||||||
this.layers.hybrid,
|
this.layers.hybrid,
|
||||||
|
this.kmlLayer,
|
||||||
this.tileOverlayGroup,
|
this.tileOverlayGroup,
|
||||||
this.clusterLayer,
|
this.clusterLayer,
|
||||||
this.stationLayer
|
this.stationLayer
|
||||||
], view: new ol.View({ center: ol.proj.fromLonLat([108, 35]), zoom: 5, minZoom: 3, maxZoom: 18 }) });
|
], view: new ol.View({ center: ol.proj.fromLonLat([108, 35]), zoom: 5, minZoom: 3, maxZoom: 18 }) });
|
||||||
|
|
||||||
|
// 使用全屏遮罩的页面级弹窗显示 KML 详情
|
||||||
|
|
||||||
this.map.getView().on('change:resolution', () => {
|
this.map.getView().on('change:resolution', () => {
|
||||||
const z = this.map.getView().getZoom();
|
const z = this.map.getView().getZoom();
|
||||||
this.updateClusterDistance(z);
|
this.updateClusterDistance(z);
|
||||||
@ -157,6 +174,29 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
if (this.stations?.length) this.updateStationsOnMap();
|
if (this.stations?.length) this.updateStationsOnMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openKmlPopup(feature: any, coordinate: any) {
|
||||||
|
try {
|
||||||
|
const name = feature?.get ? (feature.get('name') || '') : '';
|
||||||
|
let desc = feature?.get ? (feature.get('description') || '') : '';
|
||||||
|
// Cleanup KML-wrapped CDATA and decode HTML entities
|
||||||
|
try {
|
||||||
|
desc = String(desc);
|
||||||
|
desc = desc.replace(/^<!\[CDATA\[/, '').replace(/\]\]>$/, '');
|
||||||
|
const ta = document.createElement('textarea'); ta.innerHTML = desc; desc = ta.value;
|
||||||
|
} catch {}
|
||||||
|
this.kmlInfoTitle = String(name || '详情');
|
||||||
|
this.kmlInfoHtml = String(desc || '');
|
||||||
|
// 使用页面级模态对话框显示
|
||||||
|
this.isKmlDialogOpen = true;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeKmlPopup() {
|
||||||
|
try {
|
||||||
|
this.isKmlDialogOpen = false;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
switchLayer(layerType: string) {
|
switchLayer(layerType: string) {
|
||||||
const layers = this.layers; if (!layers) return;
|
const layers = this.layers; if (!layers) return;
|
||||||
Object.keys(layers).forEach(key => { if (layers[key].setVisible) layers[key].setVisible(key === layerType); });
|
Object.keys(layers).forEach(key => { if (layers[key].setVisible) layers[key].setVisible(key === layerType); });
|
||||||
@ -229,6 +269,20 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
// click to select
|
// click to select
|
||||||
if (this.map && !this.mapEventsBound) {
|
if (this.map && !this.mapEventsBound) {
|
||||||
this.map.on('singleclick', async (evt:any) => {
|
this.map.on('singleclick', async (evt:any) => {
|
||||||
|
// 先尝试命中 KML 要素
|
||||||
|
try {
|
||||||
|
let handledKml = false;
|
||||||
|
this.map.forEachFeatureAtPixel(evt.pixel, (f:any, layer:any) => {
|
||||||
|
if (this.kmlLayer && layer === this.kmlLayer) {
|
||||||
|
this.openKmlPopup(f, evt.coordinate);
|
||||||
|
handledKml = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, { layerFilter: (l:any)=> l===this.kmlLayer, hitTolerance: 6 });
|
||||||
|
if (handledKml) return;
|
||||||
|
} catch {}
|
||||||
|
// 再处理站点/聚合点击
|
||||||
const olAny: any = (window as any).ol;
|
const olAny: any = (window as any).ol;
|
||||||
const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
|
const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
|
||||||
if (!features || features.length===0) return;
|
if (!features || features.length===0) return;
|
||||||
@ -244,7 +298,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sid = f.get('stationId');
|
const sid = f.get('stationId');
|
||||||
const loc = f.get('location') || '';
|
|
||||||
if (!sid) return;
|
if (!sid) return;
|
||||||
const hex = String(sid).slice(-6).toUpperCase();
|
const hex = String(sid).slice(-6).toUpperCase();
|
||||||
this.decimalId = hex;
|
this.decimalId = hex;
|
||||||
@ -252,7 +305,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.scrollToChart();
|
this.scrollToChart();
|
||||||
});
|
});
|
||||||
this.map.on('pointermove', (evt:any) => {
|
this.map.on('pointermove', (evt:any) => {
|
||||||
const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
|
const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer || l===this.kmlLayer });
|
||||||
const el = this.map.getTargetElement();
|
const el = this.map.getTargetElement();
|
||||||
if (el) el.style.cursor = (features && features.length>0) ? 'pointer' : '';
|
if (el) el.style.cursor = (features && features.length>0) ? 'pointer' : '';
|
||||||
this.showTileTooltip(evt);
|
this.showTileTooltip(evt);
|
||||||
|
|||||||
1205
static/kml/selected_polygons.kml
Normal file
1205
static/kml/selected_polygons.kml
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user