feat: 新增地图边界
This commit is contained in:
parent
1cc2d5f519
commit
97c0df46bf
@ -30,6 +30,7 @@
|
||||
<option value="hybrid">混合地形图</option>
|
||||
</select>
|
||||
|
||||
|
||||
<label class="text-sm text-gray-600">预报源</label>
|
||||
<select class="px-2 py-1 border rounded text-sm" [(ngModel)]="provider">
|
||||
<option value="">不显示预报</option>
|
||||
@ -92,6 +93,17 @@
|
||||
<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>
|
||||
|
||||
<!-- 全屏弹窗: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 class="font-bold mb-5 mt-5 text-center">{{ selectedTitle }}</div>
|
||||
<chart-panel [history]="history" [forecast]="forecast" [legendMode]="legendMode"></chart-panel>
|
||||
|
||||
@ -55,6 +55,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
private clusterSource: any;
|
||||
private stationLayer: any;
|
||||
private clusterLayer: any;
|
||||
private kmlLayer: any;
|
||||
private kmlOverlay: any;
|
||||
private CLUSTER_THRESHOLD = 10;
|
||||
private tileOverlayGroup: any;
|
||||
private tileLastList: any[] = [];
|
||||
@ -66,6 +68,9 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
tileDt = '';
|
||||
tileProduct: 'none'|'radar'|'rain' = 'radar';
|
||||
isMapCollapsed = false;
|
||||
kmlInfoTitle = '';
|
||||
kmlInfoHtml = '';
|
||||
isKmlDialogOpen = false;
|
||||
|
||||
async ngOnInit() {
|
||||
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.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.layers.satellite,
|
||||
this.layers.vector,
|
||||
this.layers.terrain,
|
||||
this.layers.hybrid,
|
||||
this.kmlLayer,
|
||||
this.tileOverlayGroup,
|
||||
this.clusterLayer,
|
||||
this.stationLayer
|
||||
], view: new ol.View({ center: ol.proj.fromLonLat([108, 35]), zoom: 5, minZoom: 3, maxZoom: 18 }) });
|
||||
|
||||
// 使用全屏遮罩的页面级弹窗显示 KML 详情
|
||||
|
||||
this.map.getView().on('change:resolution', () => {
|
||||
const z = this.map.getView().getZoom();
|
||||
this.updateClusterDistance(z);
|
||||
@ -157,6 +174,29 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
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) {
|
||||
const layers = this.layers; if (!layers) return;
|
||||
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
|
||||
if (this.map && !this.mapEventsBound) {
|
||||
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 features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
|
||||
if (!features || features.length===0) return;
|
||||
@ -244,7 +298,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
return;
|
||||
}
|
||||
const sid = f.get('stationId');
|
||||
const loc = f.get('location') || '';
|
||||
if (!sid) return;
|
||||
const hex = String(sid).slice(-6).toUpperCase();
|
||||
this.decimalId = hex;
|
||||
@ -252,7 +305,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
this.scrollToChart();
|
||||
});
|
||||
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();
|
||||
if (el) el.style.cursor = (features && features.length>0) ? 'pointer' : '';
|
||||
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