fix:完成SpectralPlot软件测试报告2中提出的Bug及新需求
This commit is contained in:
@ -5,7 +5,9 @@
|
||||
</a-layout-header>
|
||||
<a-layout>
|
||||
<a-layout-sider :resize-directions="['right']" style=" min-width: 20vw;max-width: 50vw;">
|
||||
<a-dropdown trigger="contextMenu" alignPoint :style="{ display: 'block' }">
|
||||
<a-dropdown ref="contextMenuDropdown" :popup-visible="contextMenuVisible"
|
||||
@popup-visible-change="onContextMenuVisibleChange" trigger="contextMenu" alignPoint
|
||||
:style="{ display: 'block' }">
|
||||
<GuiLeftSider class="lefttree" v-on:NodeClicked="ononeFilechoese"
|
||||
v-on:NodeDblClicked="onFileDblClick" v-on:FilesSelected="onFilesSelected"
|
||||
v-on:FolderClicked="onFolderClicked" v-on:FolderDblClicked="onFolderDblClick"
|
||||
@ -86,9 +88,47 @@ export default {
|
||||
],
|
||||
showSaveDialog: false,
|
||||
filesToSave: [],
|
||||
contextMenuVisible: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('mousedown', this.handleGlobalMouseAction, true)
|
||||
document.addEventListener('wheel', this.handleGlobalMouseAction, true)
|
||||
window.addEventListener('blur', this.closeContextMenu)
|
||||
window.addEventListener('close-app-context-menu', this.closeContextMenu)
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleGlobalMouseAction, true)
|
||||
document.removeEventListener('wheel', this.handleGlobalMouseAction, true)
|
||||
window.removeEventListener('blur', this.closeContextMenu)
|
||||
window.removeEventListener('close-app-context-menu', this.closeContextMenu)
|
||||
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
||||
},
|
||||
methods: {
|
||||
onContextMenuVisibleChange(visible) {
|
||||
this.contextMenuVisible = visible
|
||||
},
|
||||
closeContextMenu() {
|
||||
this.contextMenuVisible = false
|
||||
},
|
||||
handleVisibilityChange() {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
this.closeContextMenu()
|
||||
}
|
||||
},
|
||||
handleGlobalMouseAction(event) {
|
||||
if (!this.contextMenuVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
const popup = document.querySelector('.arco-trigger-popup')
|
||||
if (popup && popup.contains(event.target)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.closeContextMenu()
|
||||
},
|
||||
// 处理单个文件选择(单击)
|
||||
async ononeFilechoese(filePath) {
|
||||
// console.log('选中文件路径:', filePath);
|
||||
@ -136,6 +176,7 @@ export default {
|
||||
|
||||
// 处理"显示曲线"菜单项点击事件
|
||||
async onShowCurvesClick() {
|
||||
this.closeContextMenu()
|
||||
// console.log('点击显示曲线菜单项');
|
||||
// 如果有多选文件,则加载所有选中的文件
|
||||
if (this.selectedFilePaths && this.selectedFilePaths.length > 0) {
|
||||
@ -185,6 +226,7 @@ export default {
|
||||
this.filesToSave = []
|
||||
},
|
||||
async openCurveSaveDialogForSelectedFiles() {
|
||||
this.closeContextMenu()
|
||||
const paths = (this.selectedFilePaths && this.selectedFilePaths.length > 0)
|
||||
? this.selectedFilePaths
|
||||
: (this.selectedFilePath ? [this.selectedFilePath] : []);
|
||||
@ -213,4 +255,4 @@ h1 {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
71
src/DataView/utils/mapLocation.js
Normal file
71
src/DataView/utils/mapLocation.js
Normal file
@ -0,0 +1,71 @@
|
||||
function toFixedNumber(value, digits = 6) {
|
||||
return Number(Number(value).toFixed(digits));
|
||||
}
|
||||
|
||||
function buildSeedFromText(text) {
|
||||
const input = String(text || 'default');
|
||||
let hash = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
hash = ((hash << 5) - hash) + input.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
function buildFallbackCoordinate(seedText) {
|
||||
const seed = buildSeedFromText(seedText);
|
||||
const lonRatio = (seed % 100000) / 100000;
|
||||
const latRatio = (Math.floor(seed / 100000) % 100000) / 100000;
|
||||
|
||||
return {
|
||||
longitude: toFixedNumber(115.7 + (117.4 - 115.7) * lonRatio),
|
||||
latitude: toFixedNumber(40.2 + (40.5 - 40.2) * latRatio)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGpsCoordinate(gps) {
|
||||
if (!gps) return null;
|
||||
|
||||
let latitude = Number(gps.latitude);
|
||||
let longitude = Number(gps.longitude);
|
||||
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 某些数据源纬经度字段可能写反,优先纠正到合法范围
|
||||
if (Math.abs(latitude) > 90 && Math.abs(longitude) <= 90) {
|
||||
[latitude, longitude] = [longitude, latitude];
|
||||
}
|
||||
|
||||
if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
latitude: toFixedNumber(latitude),
|
||||
longitude: toFixedNumber(longitude),
|
||||
altitude: Number.isFinite(Number(gps.altitude)) ? Number(gps.altitude) : null
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDisplayCoordinate(item) {
|
||||
const environmentData = item?.environmentData || {};
|
||||
const normalizedGps = normalizeGpsCoordinate(environmentData.gps);
|
||||
|
||||
if (normalizedGps) {
|
||||
return normalizedGps;
|
||||
}
|
||||
|
||||
return {
|
||||
...buildFallbackCoordinate(environmentData.fileName || item?.name || ''),
|
||||
altitude: null
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
normalizeGpsCoordinate,
|
||||
resolveDisplayCoordinate
|
||||
};
|
||||
@ -5,7 +5,7 @@
|
||||
justify-content: space-between;">
|
||||
<el-row class="secondhang">
|
||||
<GuiForPlotShow @onComBox1="onComBox1" @onComBox2="onComBox2" @legendselectchanged="legendselectchanged"
|
||||
@useDarkDnChanged="onUseDarkDnChanged" ref="ASDPlotShow" class="plotcontainer">
|
||||
@processOptionsChanged="onProcessOptionsChanged" @useDarkDnChanged="onUseDarkDnChanged" ref="ASDPlotShow" class="plotcontainer">
|
||||
</GuiForPlotShow>
|
||||
</el-row>
|
||||
<el-row class="firsthang">
|
||||
@ -40,9 +40,20 @@ defineOptions({
|
||||
name: "GuiForDataShow"
|
||||
});
|
||||
|
||||
function createDefaultProcessConfig() {
|
||||
return {
|
||||
movingAverage: {
|
||||
window: 5,
|
||||
startMethod: 'shrink',
|
||||
endMethod: 'shrink'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const fromData = ref({
|
||||
comBox1: spectralTypeList[0].value,
|
||||
comBox2: '',
|
||||
processConfig: createDefaultProcessConfig()
|
||||
});
|
||||
const useDarkDn = ref(true);
|
||||
const ASDPlotShow = ref(null);
|
||||
@ -53,6 +64,148 @@ const spectralDataForInfo = ref([]); // 新增:专供 Info/图片使用的数
|
||||
const imgeList = ref([]);
|
||||
const fileData = ref([]);
|
||||
|
||||
function getTypeLabel(type) {
|
||||
return spectralTypeList.find(item => item.value === type)?.label || type || '';
|
||||
}
|
||||
|
||||
function formatMissingItem(type) {
|
||||
if (type === 'flat_ref') {
|
||||
return '白板校准文件原数据(flat_ref)';
|
||||
}
|
||||
const label = getTypeLabel(type);
|
||||
if (!label) return '';
|
||||
if (!type || label === type) return label;
|
||||
return `${label}(${type})`;
|
||||
}
|
||||
|
||||
function getAvailableRawTypes(irisData) {
|
||||
const set = new Set();
|
||||
const sections = irisData?.spectral_data_section || [];
|
||||
for (const element of sections) {
|
||||
const name = (element?.name || '').toString().toLowerCase();
|
||||
if (!name) continue;
|
||||
if (name.includes('ground_dn')) set.add('ground_dn');
|
||||
if (name.includes('flat_dn')) set.add('flat_dn');
|
||||
if (name.includes('dark_dn')) set.add('dark_dn');
|
||||
if (name.includes('flat_ref')) set.add('flat_ref');
|
||||
if (name.includes('gain')) set.add('gain');
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
function getRequiredRawTypesForSpectralType(spectralType, useDarkDn) {
|
||||
const needDark = !!useDarkDn;
|
||||
const addDark = (arr) => (needDark ? [...arr, 'dark_dn'] : arr);
|
||||
|
||||
switch ((spectralType || '').toString()) {
|
||||
case 'ground_dn':
|
||||
return ['ground_dn'];
|
||||
case 'flat_dn':
|
||||
return ['flat_dn'];
|
||||
case 'dark_dn':
|
||||
return ['dark_dn'];
|
||||
case 'gain':
|
||||
return ['gain'];
|
||||
case 'bbjzwj':
|
||||
return ['flat_ref'];
|
||||
case 'flat_ref':
|
||||
return addDark(['ground_dn', 'flat_dn']);
|
||||
case 'radiance_ground':
|
||||
return addDark(['gain', 'ground_dn']);
|
||||
case 'radiance_flat':
|
||||
return addDark(['gain', 'flat_dn']);
|
||||
case 'refrad':
|
||||
return addDark(['gain', 'ground_dn', 'flat_dn']);
|
||||
case 'ref_abs':
|
||||
return addDark(['ground_dn', 'flat_dn', 'flat_ref']);
|
||||
case 'fszd':
|
||||
return addDark(['gain', 'ground_dn']);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function parseFiberValue(value) {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
||||
if (typeof value === 'string') {
|
||||
const match = value.match(/-?\d+(\.\d+)?/);
|
||||
if (match) {
|
||||
const n = Number(match[0]);
|
||||
if (Number.isFinite(n)) return n;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseFiberFromCaliFilePath(value) {
|
||||
if (typeof value !== 'string' || !value) return undefined;
|
||||
const match = value.match(/_(\d+(?:\.\d+)?)d_/i);
|
||||
if (!match) return undefined;
|
||||
const n = Number(match[1]);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function isIrradianceMode(irisData) {
|
||||
for (const info of irisData?.spectral_info_section || []) {
|
||||
const explicit = parseFiberValue(info?.ForeOptics);
|
||||
const parsed = explicit ?? parseFiberFromCaliFilePath(info?.cailifilePath) ?? parseFiberFromCaliFilePath(info?.califilePath);
|
||||
if (parsed === undefined) continue;
|
||||
const rounded = Math.round(Number(parsed));
|
||||
return rounded === 180 || rounded === 360;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildNoDataDetailMessage(files, spectralType, useDarkDn) {
|
||||
const usableFiles = (files || []).filter(file => file?.data);
|
||||
const fileCount = usableFiles.length;
|
||||
const required = getRequiredRawTypesForSpectralType(spectralType, useDarkDn);
|
||||
const missingCounts = new Map();
|
||||
const blockedCounts = new Map();
|
||||
|
||||
for (const file of usableFiles) {
|
||||
const present = getAvailableRawTypes(file.data);
|
||||
for (const dep of required) {
|
||||
if (!present.has(dep)) {
|
||||
missingCounts.set(dep, (missingCounts.get(dep) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (spectralType === 'radiance_ground' && isIrradianceMode(file.data)) {
|
||||
blockedCounts.set('辐射照度模式不生成地物辐射亮度(radiance_ground)', (blockedCounts.get('辐射照度模式不生成地物辐射亮度(radiance_ground)') || 0) + 1);
|
||||
}
|
||||
if (spectralType === 'fszd' && !isIrradianceMode(file.data)) {
|
||||
blockedCounts.set('非辐射照度模式不生成辐射照度(fszd)', (blockedCounts.get('非辐射照度模式不生成辐射照度(fszd)') || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const missingList = Array.from(missingCounts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([dep, count]) => {
|
||||
const formatted = formatMissingItem(dep);
|
||||
if (!formatted) return '';
|
||||
return count === fileCount ? formatted : `${formatted}(部分文件)`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const blockedList = Array.from(blockedCounts.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([reason, count]) => (count === fileCount ? reason : `${reason}(部分文件)`));
|
||||
|
||||
const parts = [];
|
||||
if (missingList.length) parts.push(`缺少:${missingList.join('、')}`);
|
||||
if (blockedList.length) parts.push(`原因:${blockedList.join('、')}`);
|
||||
return parts.join(';');
|
||||
}
|
||||
|
||||
function warnNoData() {
|
||||
const spectralType = fromData.value.comBox1;
|
||||
const typeLabel = getTypeLabel(spectralType);
|
||||
const detail = buildNoDataDetailMessage(fileData.value, spectralType, useDarkDn.value);
|
||||
ElMessage.warning(detail ? `当前没有${typeLabel}数据;${detail}` : `当前没有${typeLabel}数据`);
|
||||
}
|
||||
|
||||
async function onloaddata(data) {
|
||||
// 重置数据
|
||||
spectralDataList.value = [];
|
||||
@ -67,13 +220,11 @@ async function onloaddata(data) {
|
||||
try {
|
||||
// 加载文件数据
|
||||
fileData.value = await SpectralDataService.loadFileData(data);
|
||||
|
||||
// 处理数据
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
@ -87,7 +238,8 @@ async function processSpectralData() {
|
||||
fileData.value,
|
||||
fromData.value.comBox1,
|
||||
fromData.value.comBox2,
|
||||
useDarkDn.value
|
||||
useDarkDn.value,
|
||||
fromData.value.processConfig
|
||||
);
|
||||
|
||||
|
||||
@ -143,8 +295,7 @@ const onComBox1 = async (e) => {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
};
|
||||
|
||||
@ -155,8 +306,26 @@ const onComBox2 = async (val) => {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
const typeLabel = spectralTypeList.find(i => i.value === fromData.value.comBox1)?.label || fromData.value.comBox1;
|
||||
ElMessage.warning(`当前数据没有${typeLabel}数据`);
|
||||
warnNoData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onProcessOptionsChanged = async (config) => {
|
||||
const nextConfig = createDefaultProcessConfig();
|
||||
if (config?.movingAverage) {
|
||||
nextConfig.movingAverage = {
|
||||
...nextConfig.movingAverage,
|
||||
...config.movingAverage
|
||||
};
|
||||
}
|
||||
fromData.value.processConfig = nextConfig;
|
||||
|
||||
if (fromData.value.comBox2 === 'MA' && fromData.value.comBox1) {
|
||||
await processSpectralData();
|
||||
|
||||
if (!spectralDataList.value || spectralDataList.value.length === 0) {
|
||||
warnNoData();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -257,4 +426,4 @@ const onUseDarkDnChanged = async (val) => {
|
||||
border-radius: 4px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@ import { Icon, Style, Text, Fill, Stroke } from 'ol/style';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import pinIcon from '../assets/图钉.png';
|
||||
import { boundingExtent } from 'ol/extent';
|
||||
import { resolveDisplayCoordinate } from '../utils/mapLocation';
|
||||
|
||||
const props = defineProps({
|
||||
dataListMap: {
|
||||
@ -37,10 +38,64 @@ const map = ref(null);
|
||||
const vectorLayer = ref(null);
|
||||
const isMapReady = ref(false);
|
||||
const isFullscreen = ref(false);
|
||||
const DEFAULT_FALLBACK_LATITUDE = 39.0856735;
|
||||
const DEFAULT_FALLBACK_LONGITUDE = 117.1951073;
|
||||
const DEFAULT_FALLBACK_LATITUDE_OFFSET = 0.08;
|
||||
const DEFAULT_FALLBACK_LONGITUDE_OFFSET = 0.12;
|
||||
|
||||
function isValidMapCoordinate(longitude, latitude) {
|
||||
const lng = Number(longitude);
|
||||
const lat = Number(latitude);
|
||||
return Number.isFinite(lng) && Number.isFinite(lat) && lng !== 0 && lat !== 0;
|
||||
}
|
||||
|
||||
function hashStringToSeed(value) {
|
||||
const text = String(value || '');
|
||||
let hash = 0;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
hash = ((hash << 5) - hash) + text.charCodeAt(index);
|
||||
hash |= 0;
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
function createStableRandom(seed) {
|
||||
const normalizedSeed = (seed % 2147483647) || 1;
|
||||
let currentSeed = normalizedSeed;
|
||||
return () => {
|
||||
currentSeed = (currentSeed * 16807) % 2147483647;
|
||||
return (currentSeed - 1) / 2147483646;
|
||||
};
|
||||
}
|
||||
|
||||
function getFallbackCoordinateForMap(item, index) {
|
||||
const fileName = item?.environmentData?.fileName || item?.name || `fallback-${index}`;
|
||||
const random = createStableRandom(hashStringToSeed(fileName));
|
||||
const longitudeOffset = (random() - 0.5) * DEFAULT_FALLBACK_LONGITUDE_OFFSET;
|
||||
const latitudeOffset = (random() - 0.5) * DEFAULT_FALLBACK_LATITUDE_OFFSET;
|
||||
|
||||
return {
|
||||
longitude: DEFAULT_FALLBACK_LONGITUDE + longitudeOffset,
|
||||
latitude: DEFAULT_FALLBACK_LATITUDE + latitudeOffset
|
||||
};
|
||||
}
|
||||
|
||||
function getDisplayCoordinateForMap(item, index) {
|
||||
const resolvedCoordinate = resolveDisplayCoordinate(item);
|
||||
if (isValidMapCoordinate(resolvedCoordinate?.longitude, resolvedCoordinate?.latitude)) {
|
||||
return {
|
||||
longitude: Number(resolvedCoordinate.longitude),
|
||||
latitude: Number(resolvedCoordinate.latitude)
|
||||
};
|
||||
}
|
||||
|
||||
// 仅用于地图显示保底,不回写源数据,避免影响导出等真实数据流程。
|
||||
return getFallbackCoordinateForMap(item, index);
|
||||
}
|
||||
|
||||
|
||||
const initializeMap = () => {
|
||||
const center = fromLonLat([116.255535, 40.204654]);
|
||||
const center = fromLonLat([DEFAULT_FALLBACK_LONGITUDE, DEFAULT_FALLBACK_LATITUDE]);
|
||||
|
||||
const view = new View({
|
||||
center,
|
||||
@ -78,33 +133,18 @@ const initializeMap = () => {
|
||||
isMapReady.value = true;
|
||||
};
|
||||
|
||||
const getRandomCoordinateInBeijing = () => {
|
||||
const minLon = 115.7;
|
||||
const maxLon = 117.4;
|
||||
const minLat = 40.2;
|
||||
const maxLat = 40.5;
|
||||
|
||||
const lon = Math.random() * (maxLon - minLon) + minLon;
|
||||
const lat = Math.random() * (maxLat - minLat) + minLat;
|
||||
|
||||
return {
|
||||
longitude: parseFloat(lon.toFixed(6)),
|
||||
latitude: parseFloat(lat.toFixed(6))
|
||||
};
|
||||
};
|
||||
|
||||
const handleTagging = (data) => {
|
||||
if (!isMapReady.value || !Array.isArray(data)) return;
|
||||
|
||||
const features = [];
|
||||
const coords = [];
|
||||
|
||||
data.forEach(item => {
|
||||
const { longitude, latitude } = getRandomCoordinateInBeijing(); // 模拟
|
||||
if (longitude && latitude) {
|
||||
data.forEach((item, index) => {
|
||||
const { longitude, latitude } = getDisplayCoordinateForMap(item, index);
|
||||
if (isValidMapCoordinate(longitude, latitude)) {
|
||||
const feature = new Feature({
|
||||
geometry: new Point(fromLonLat([longitude, latitude])),
|
||||
name: item.environmentData.fileName || ''
|
||||
name: item?.environmentData?.fileName || item?.name || ''
|
||||
});
|
||||
|
||||
feature.setStyle(
|
||||
@ -115,7 +155,7 @@ const handleTagging = (data) => {
|
||||
scale: 1
|
||||
}),
|
||||
text: new Text({
|
||||
text: item.environmentData.fileName || '',
|
||||
text: item?.environmentData?.fileName || item?.name || '',
|
||||
offsetY: 14,
|
||||
font: '300 14px sans-serif',
|
||||
fill: new Fill({
|
||||
@ -144,8 +184,9 @@ const handleTagging = (data) => {
|
||||
}
|
||||
});
|
||||
|
||||
vectorLayer.value?.getSource()?.clear();
|
||||
|
||||
if (features.length > 0) {
|
||||
vectorLayer.value.getSource().clear();
|
||||
vectorLayer.value.getSource().addFeatures(features);
|
||||
|
||||
const extent = boundingExtent(coords);
|
||||
|
||||
Reference in New Issue
Block a user