feat: improve D-InSAR pairing and distribution

This commit is contained in:
2026-05-09 14:59:20 +08:00
parent 69d15bffcb
commit 5c73e2c1c2
28 changed files with 1480 additions and 86 deletions
+46 -3
View File
@@ -13,9 +13,11 @@ const BATCH_API_MAX_PAGES = 200;
const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
const { t } = useI18n();
const [activeTab, setActiveTab] = useState('ps');
const [activeTab, setActiveTab] = useState('dinsar');
const [destDir, setDestDir] = useState('');
const [copyStatuses, setCopyStatuses] = useState(['COMPLETED']);
const [includeDinsarOrbitFiles, setIncludeDinsarOrbitFiles] = useState(false);
const [dinsarExportZip, setDinsarExportZip] = useState(false);
const [batches, setBatches] = useState([]);
const [selectedBatchId, setSelectedBatchId] = useState('');
const [isUploading, setIsUploading] = useState(false);
@@ -117,11 +119,16 @@ const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
: `${apiEndpoint}/tools/copy-dinsar-pairs`;
try {
const response = await axios.post(endpoint, {
const payload = {
batch_id: selectedBatchId,
dest_dir: destDir,
copy_statuses: copyStatuses,
}, { withCredentials: true });
};
if (activeTab === 'dinsar') {
payload.include_orbit_files = includeDinsarOrbitFiles;
payload.export_zip = dinsarExportZip;
}
const response = await axios.post(endpoint, payload, { withCredentials: true });
const taskId = response.data.task_id;
setTaskId(taskId);
@@ -178,6 +185,42 @@ const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
当前账号为只读模式无法发起复制任务
</div>
)}
{activeTab === 'dinsar' && (
<div
className="input-group"
style={{
border: '1px solid #c7d2fe',
background: '#eef2ff',
borderRadius: '8px',
padding: '10px 12px',
}}
>
<label>D-InSAR 分发设置</label>
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap', marginTop: '8px' }}>
<label style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
<input
type="checkbox"
checked={includeDinsarOrbitFiles}
onChange={(event) => setIncludeDinsarOrbitFiles(event.target.checked)}
disabled={status === 'RUNNING' || readOnly}
/>
<span>复制精密轨道到 Task/orbit</span>
</label>
<label style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
<input
type="checkbox"
checked={dinsarExportZip}
onChange={(event) => setDinsarExportZip(event.target.checked)}
disabled={status === 'RUNNING' || readOnly}
/>
<span>导出为 ZIP 压缩包</span>
</label>
</div>
<div style={{ fontSize: '12px', color: '#475569', marginTop: '6px' }}>
未勾选 ZIP 时直接导出 Task 文件夹勾选后每个 Task 输出一个 .zip
</div>
</div>
)}
<div className="input-group">
<label>1. 选择批次</label>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
@@ -476,7 +476,7 @@ export default function DinsarCatalogPanel({
<MetaField label="主影像日期" value={selectedProduct.profile?.master_imaging_date} />
<MetaField label="从影像日期" value={selectedProduct.profile?.slave_imaging_date} />
<MetaField label="时间基线" value={selectedProduct.profile?.time_baseline_days} />
<MetaField label="空间基线" value={selectedProduct.profile?.spatial_baseline_meters} />
<MetaField label="footprint 中心距" value={selectedProduct.profile?.scene_center_distance_meters ?? selectedProduct.profile?.spatial_baseline_meters} />
</div>
<div className="dinsar-catalog-section-card">
<div className="dinsar-catalog-section-title">空间范围</div>
@@ -521,7 +521,7 @@ export default function DinsarCatalogPanel({
<MetaField label="主从模式" value={`${selectedPairingMetric?.master_imaging_mode || '-'} / ${selectedPairingMetric?.slave_imaging_mode || '-'}`} />
<MetaField label="主从极化" value={`${selectedPairingMetric?.master_polarization || '-'} / ${selectedPairingMetric?.slave_polarization || '-'}`} />
<MetaField label="时间基线" value={selectedPairingMetric?.time_baseline_days} />
<MetaField label="空间基线" value={selectedPairingMetric?.spatial_baseline_meters} />
<MetaField label="footprint 中心距" value={selectedPairingMetric?.scene_center_distance_meters ?? selectedPairingMetric?.spatial_baseline_meters} />
</div>
</div>
)}
+8 -8
View File
@@ -11,23 +11,23 @@ const STRATEGY_DESCRIPTIONS = {
title: '全部配对(默认)',
description: '列出所有满足约束条件的候选干涉对,由用户自行筛选。',
details: [
'• 系统遍历所有影像组合,保留满足时间基线、空间基线和重叠率阈值的配对',
'• 系统遍历所有影像组合,保留满足时间基线、footprint 中心距和两景 footprint 最小重叠率的配对',
'• 结果按时间排序,用户可在配对列表中逐一勾选或取消',
'• 适用于研究型场景,需要精确控制每一对干涉组合',
'• 配对数量可能较多,建议配合 AOI 和日期范围缩小结果'
],
params: '参数:时间基线范围、空间基线上限、最小重叠率'
params: '参数:时间基线范围、中心距上限、两景 footprint 最小重叠率'
},
sbas: {
title: 'SBAS (短基线子集)',
description: '基于短基线原则的配对策略,通过覆盖优化算法自动筛选配对。',
details: [
'• 优先选择时间和空间基线都较短的配对',
'• 优先选择时间间隔和 footprint 中心距都较短的配对',
'• 通过覆盖优化算法,去除冗余配对,确保时间序列连续性',
'• 适用于大范围、长时间序列的形变监测',
'• 配对数量会比"全部配对"少,但覆盖更均匀'
],
params: '参数:时间基线、空间基线、重叠率阈值、覆盖多样性惩罚'
params: '参数:时间基线、中心距、两景 footprint 最小重叠率、覆盖多样性惩罚'
},
sequential: {
title: 'Sequential (顺序配对)',
@@ -321,24 +321,24 @@ function PairingModal({
</div>
)}
{/* 基线和重叠率约束 */}
{/* 时间、中心距和重叠率约束 */}
<div className="form-group">
<label>时间基线最小值 ():</label>
<input type="number" min="0" value={pairingParams.time_baseline_min}
onChange={e => setPairingParams({...pairingParams, time_baseline_min: parseInt(e.target.value) || 0})} />
</div>
<div className="form-group">
<label>时间基线最大值 ():</label>
<label>最大时间间隔 ():</label>
<input type="number" min="1" value={pairingParams.time_baseline_max}
onChange={e => setPairingParams({...pairingParams, time_baseline_max: parseInt(e.target.value) || 90})} />
</div>
<div className="form-group">
<label>最小重叠率 (0-1):</label>
<label>两景 footprint 最小重叠率 (0-1):</label>
<input type="number" step="0.1" min="0" max="1" value={pairingParams.overlap_threshold}
onChange={e => setPairingParams({...pairingParams, overlap_threshold: parseFloat(e.target.value) || 0})} />
</div>
<div className="form-group">
<label>空间基线上限 ():</label>
<label>footprint 中心距上限 ():</label>
<input type="number" min="0" value={pairingParams.spatial_baseline_max_meters}
onChange={e => setPairingParams({...pairingParams, spatial_baseline_max_meters: parseInt(e.target.value) || 3000})} />
</div>
@@ -7,6 +7,7 @@ function PairListRow({
onVisualizePair,
onTogglePairVisibility,
}) {
const centerDistance = pair.scene_center_distance_meters ?? pair.spatial_baseline_meters;
return (
<li className="pair-item">
<input
@@ -20,7 +21,7 @@ function PairListRow({
<strong>{pair.task_name}</strong>
<div className="pair-details">
<span>时基: {pair.time_baseline_days}d</span>
<span>空基: {pair.spatial_baseline_meters.toFixed(2)}m</span>
<span>中心距: {Number(centerDistance || 0).toFixed(2)}m</span>
</div>
</div>
<button
+1 -1
View File
@@ -138,7 +138,7 @@
{ zh: '请先训练模型。', en: 'Please train the model first.' },
// PairingPanel
{ zh: '基于时间基线、空间基线与重叠率筛选干涉对,可选 AOI 限定范围。', en: 'Filter interferometric pairs by temporal baseline, spatial baseline, and overlap ratio. Optional AOI constraint.' },
{ zh: '基于时间基线、footprint 中心距与两景 footprint 最小重叠率筛选干涉对,可选 AOI 限定范围。', en: 'Filter interferometric pairs by temporal baseline, footprint center distance, and pair footprint overlap ratio. Optional AOI constraint.' },
{ zh: '配对', en: 'Pair' },
{ zh: '时序准备', en: 'Timeseries Prep' },
{ zh: '结果与刷新', en: 'Results & Refresh' },
+2 -2
View File
@@ -107,8 +107,8 @@ export default function PairPlanningPanel({
<div className="panel-card-title">{en ? 'Pair Planning' : '配对规划'}</div>
<p className="panel-card-desc">
{en
? 'Filter interferometric pairs by temporal baseline, spatial baseline, and overlap ratio. Optional AOI constraint.'
: '基于时间基线、空间基线和重叠率筛选干涉对,可选 AOI 约束范围。'}
? 'Filter interferometric pairs by temporal baseline, footprint center distance, and pair footprint overlap ratio. Optional AOI constraint.'
: '基于时间基线、footprint 中心距和两景 footprint 最小重叠率筛选干涉对,可选 AOI 约束范围。'}
</p>
<div className="header-buttons" style={{ marginTop: '10px' }}>
<button onClick={onOpenPairingModal} disabled={isLoading || !hasEnoughRadarScenesForPlanning || isReadOnlyUser} style={{ flex: 1 }}>
+2 -2
View File
@@ -19,8 +19,8 @@ export default function PairingPanel({
<div className="panel-card-title">{en ? 'Pair Planning' : '配对规划'}</div>
<p className="panel-card-desc">
{en
? 'Filter interferometric pairs by temporal baseline, spatial baseline, and overlap ratio. Optional AOI constraint.'
: '基于时间基线、空间基线与重叠率筛选干涉对,可选 AOI 限定范围。'
? 'Filter interferometric pairs by temporal baseline, footprint center distance, and pair footprint overlap ratio. Optional AOI constraint.'
: '基于时间基线、footprint 中心距与两景 footprint 最小重叠率筛选干涉对,可选 AOI 限定范围。'
}
</p>
<div className="header-buttons" style={{ marginTop: '10px' }}>