轨迹图逻辑修改

This commit is contained in:
wangxk 2025-07-31 14:26:05 +08:00
parent 694e16240c
commit d76271da24
2 changed files with 251 additions and 92 deletions

View File

@ -1,12 +1,16 @@
<template> <template>
<div class="map-container"> <div class="map-container" :class="{ fullscreen: isFullscreen }">
<svg ref="svgRef" class="trajectory-svg" width="800" height="600"> <!-- 全屏按钮 -->
<!-- <image> 放入 zoom-container 使其与轨迹同步缩放 --> <button class="fullscreen-btn" @click="toggleFullscreen">
{{ isFullscreen ? '退出全屏' : '全屏' }}
</button>
<svg ref="svgRef" class="trajectory-svg" :width="svgWidth" :height="svgHeight">
<g class="zoom-container"> <g class="zoom-container">
<image <image
:xlink:href="imageUrl" :xlink:href="imageUrl"
width="800" :width="svgWidth"
height="600" :height="svgHeight"
/> />
</g> </g>
</svg> </svg>
@ -39,6 +43,13 @@ const props = defineProps({
// === Emits === // === Emits ===
const emit = defineEmits(['trajectoryComplete']) const emit = defineEmits(['trajectoryComplete'])
// === ===
const isFullscreen = ref(false)
const svgWidth = ref(800)
const svgHeight = ref(600)
let resizeTimeout = null
const prevFullscreenState = ref(isFullscreen.value)
// === DOM === // === DOM ===
const svgRef = ref(null) const svgRef = ref(null)
const xScale = ref(null) const xScale = ref(null)
@ -47,15 +58,22 @@ const zoomTransform = ref(d3.zoomIdentity)
// === === // === ===
const initMap = () => { const initMap = () => {
//
const container = d3.select(svgRef.value).node()
if (container) {
svgWidth.value = container.clientWidth
svgHeight.value = container.clientHeight
}
const [minLng, minLat, maxLng, maxLat] = props.bounds const [minLng, minLat, maxLng, maxLat] = props.bounds
xScale.value = d3.scaleLinear() xScale.value = d3.scaleLinear()
.domain([minLng, maxLng]) .domain([minLng, maxLng])
.range([0, 800]) .range([0, svgWidth.value])
yScale.value = d3.scaleLinear() yScale.value = d3.scaleLinear()
.domain([minLat, maxLat]) .domain([minLat, maxLat])
.range([600, 0]) .range([svgHeight.value, 0])
initZoom() initZoom()
updateTrajectory() updateTrajectory()
@ -65,7 +83,7 @@ const initMap = () => {
const initZoom = () => { const initZoom = () => {
const mapZoom = d3.zoom() const mapZoom = d3.zoom()
.scaleExtent([0.5, 3]) // .scaleExtent([0.5, 3]) //
.translateExtent([[-800, -600], [1600, 1200]]) // .translateExtent([[-svgWidth.value, -svgHeight.value], [2*svgWidth.value, 2*svgHeight.value]])
.on("zoom", (event) => { .on("zoom", (event) => {
d3.select(svgRef.value).select('.zoom-container') d3.select(svgRef.value).select('.zoom-container')
.attr("transform", event.transform) .attr("transform", event.transform)
@ -79,9 +97,8 @@ const initZoom = () => {
const updateTrajectory = () => { const updateTrajectory = () => {
if (!props.trajectory?.length || !xScale.value || !yScale.value) return if (!props.trajectory?.length || !xScale.value || !yScale.value) return
if (props.qvehuan) { //
clearOldElements() // clearOldElements()
}
if (shouldRedraw()) { if (shouldRedraw()) {
renderTrajectoryPath() renderTrajectoryPath()
@ -160,12 +177,65 @@ const renderTrajectoryMarkers = () => {
// //
const shouldRedraw = () => { const shouldRedraw = () => {
//
if (isFullscreen.value !== prevFullscreenState.value) {
prevFullscreenState.value = isFullscreen.value
return true
}
if (props.qvehuan && props.trajectory.length > 0) { if (props.qvehuan && props.trajectory.length > 0) {
return true return true
} }
return props.trajectory.length > 0 return props.trajectory.length > 0
} }
// === ===
const toggleFullscreen = () => {
if (!isFullscreen.value) {
//
const elem = document.documentElement
if (elem.requestFullscreen) {
elem.requestFullscreen()
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen()
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen()
}
} else {
// 退
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
}
}
}
// === ===
const handleFullscreenChange = () => {
isFullscreen.value = !!document.fullscreenElement
//
clearOldElements()
// DOM
setTimeout(() => {
initMap()
updateTrajectory()
}, 500)
}
// === ===
const handleResize = () => {
if (resizeTimeout) clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(() => {
initMap()
updateTrajectory()
}, 300)
}
// === === // === ===
watch(() => props.trajectory, (newVal) => { watch(() => props.trajectory, (newVal) => {
if (newVal.length) { if (newVal.length) {
@ -180,6 +250,18 @@ watch(() => props.qvehuan, (newVal) => {
}) })
onMounted(() => { onMounted(() => {
//
document.addEventListener('fullscreenchange', handleFullscreenChange)
window.addEventListener('resize', handleResize)
// SVG
const container = d3.select(svgRef.value).node()
if (container) {
svgWidth.value = container.clientWidth
svgHeight.value = container.clientHeight
}
//
const imageElement = d3.select(svgRef.value).select('image') const imageElement = d3.select(svgRef.value).select('image')
if (imageElement) { if (imageElement) {
imageElement.on('load', initMap) imageElement.on('load', initMap)
@ -187,6 +269,8 @@ onMounted(() => {
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.removeEventListener('fullscreenchange', handleFullscreenChange)
window.removeEventListener('resize', handleResize)
d3.select(svgRef.value).selectAll('*').remove() d3.select(svgRef.value).selectAll('*').remove()
}) })
</script> </script>
@ -198,6 +282,36 @@ onBeforeUnmount(() => {
height: 600px; height: 600px;
overflow: hidden; overflow: hidden;
margin-top: 10px; margin-top: 10px;
transition: all 0.3s ease;
}
.map-container.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
margin: 0;
}
.fullscreen-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 10;
padding: 6px 12px;
background: rgba(255, 255, 255, 0.8);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
}
.fullscreen-btn:hover {
background: #1890FF;
color: white;
} }
.trajectory-svg { .trajectory-svg {
@ -206,6 +320,9 @@ onBeforeUnmount(() => {
left: 0; left: 0;
z-index: 1; z-index: 1;
cursor: grab; cursor: grab;
width: 100%;
height: 100%;
transition: all 0.3s ease;
} }
.trajectory-svg:active { .trajectory-svg:active {

View File

@ -1594,18 +1594,20 @@ const imgor = ref(true)
function openMap(row: any) { function openMap(row: any) {
pngobj.vlaue = { pngobj.vlaue = {
pngurl: '', pngurl: '',
conid: '',
textcontent: [] textcontent: []
} }
pngform.value = JSON.parse(JSON.stringify(row)) pngform.value = JSON.parse(JSON.stringify(row))
fredid.value = row.id fredid.value = row.id
getSSELink()
getpngdata() getpngdata()
tabbas.value = '1' tabbas.value = '1'
mapTrajectory.value = true mapTrajectory.value = true
maptime.value = 300 maptime.value = 60
if (row.custom1) { console.log(JSON.parse(row.custom1))
if (JSON.parse(row.custom1).pngurl && JSON.parse(row.custom1).textcontent && JSON.parse(row.custom1).conid) {
ElMessageBox.confirm( ElMessageBox.confirm(
'当前轨迹文件已有关联图片,是否直接加载轨迹?', '当前轨迹文件已有关联轨迹配置文件和图片,是否直接加载轨迹?',
'提示', '提示',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
@ -1615,13 +1617,18 @@ function openMap(row: any) {
) )
.then(() => { .then(() => {
pngobj.value = JSON.parse(row.custom1) pngobj.value = JSON.parse(row.custom1)
configradio.value = pngobj.value.conid
imgor.value = false imgor.value = false
getSSELink()
})
.catch(() => {
}) })
} }
} }
const pngArr = ref([]) const pngArr = ref([])
const txtArr = ref([]) const txtArr = ref([])
const conarr = ref([])
const pngloading = ref(false) const pngloading = ref(false)
function getpngdata() { function getpngdata() {
pngloading.value = true pngloading.value = true
@ -1629,52 +1636,61 @@ function getpngdata() {
pngloading.value = false pngloading.value = false
pngArr.value = res.data.tsFilesListPng pngArr.value = res.data.tsFilesListPng
txtArr.value = res.data.tsFilesListTxt txtArr.value = res.data.tsFilesListTxt
conarr.value = res.data.tsFilesListConfig
}) })
} }
const pngobj: any = ref({ const pngobj: any = ref({
pngurl: '', pngurl: '',
conid: '',
textcontent: [] textcontent: []
}) })
function pngsure() { function pngsure() {
if (!(pngradio.value && txtradio.value)) { if (!(pngradio.value && txtradio.value && configradio.value)) {
ElMessage.warning('请选择底图或地理信息文件') ElMessage.warning('请选择底图或地理信息文件')
return return
}
//
const newPngobj = {
pngurl: '',
textcontent: []
}
// URL
pngArr.value.forEach((item: any) => {
if (pngradio.value == item.id) {
newPngobj.pngurl = item.url
} }
})
// //
txtArr.value.forEach((item: any) => { const newPngobj = {
if (txtradio.value == item.id) { pngurl: '',
newPngobj.textcontent = JSON.parse(JSON.stringify(item.fileContent)).split(",") textcontent: [],
conid: '',
} }
})
// 使 // URL
pngobj.value = { pngArr.value.forEach((item: any) => {
...pngobj.value, if (pngradio.value == item.id) {
...newPngobj newPngobj.pngurl = item.url
} }
})
// //
pngform.value.custom1 = JSON.stringify(pngobj.value) txtArr.value.forEach((item: any) => {
if (txtradio.value == item.id) {
newPngobj.textcontent = JSON.parse(JSON.stringify(item.fileContent)).split(",")
}
})
conarr.value.forEach((item: any) => {
if (configradio.value == item.id) {
newPngobj.conid = item.id
}
})
// // 使
updateTsFiles(pngform.value).then((res: any) => { }) pngobj.value = {
imgor.value = false ...pngobj.value,
...newPngobj
}
//
pngform.value.custom1 = JSON.stringify(pngobj.value)
//
updateTsFiles(pngform.value).then((res: any) => { })
imgor.value = false
} }
function mapClose() { function mapClose() {
configradio.value = null
txtradio.value = null txtradio.value = null
pngradio.value = null pngradio.value = null
imgor.value = true imgor.value = true
@ -1687,6 +1703,18 @@ function mapClose() {
} }
// 1s/10s/30s/1m/2m/5m // 1s/10s/30s/1m/2m/5m
const options = ref([ const options = ref([
{
name: '10秒钟'
, id: 10
},
{
name: '1分钟'
, id: 60
},
{
name: '2分钟'
, id: 120
},
{ {
name: '5分钟' name: '5分钟'
, id: 300 , id: 300
@ -1694,15 +1722,15 @@ const options = ref([
{ {
name: '10分钟' name: '10分钟'
, id: 600 , id: 600
}, }
]) ])
const maptime: any = ref(300) const maptime: any = ref(60)
// //
const qvehuan: any = ref(false) const qvehuan: any = ref(false)
const qvehuan1: any = ref(false) const qvehuan1: any = ref(false)
function frequency(row: any) { function frequency(row: any) {
startSimpleNavi({ startSimpleNavi({
samTimes: maptime.value, id: fredid.value, token: userStore.userId, taskId: projectId.value samTimes: maptime.value, id: fredid.value, token: userStore.userId, taskId: projectId.value, configId: configradio.value
}).then((res: any) => { }).then((res: any) => {
if (res.code == '0' && row) { if (res.code == '0' && row) {
ElMessage.success("切换成功") ElMessage.success("切换成功")
@ -1813,6 +1841,7 @@ const formatFileSize = (size: number): string => {
const txtradio: any = ref(null) const txtradio: any = ref(null)
const pngradio: any = ref(null) const pngradio: any = ref(null)
const configradio: any = ref(null)
</script> </script>
<template> <template>
@ -1850,7 +1879,7 @@ const pngradio: any = ref(null)
</template> </template>
</el-tree> </el-tree>
<el-button type="primary" @click="scan()" style="width: 100%;" :loading="tonloading">{{ buttonmsg <el-button type="primary" @click="scan()" style="width: 100%;" :loading="tonloading">{{ buttonmsg
}}</el-button> }}</el-button>
</div> </div>
<div class="moveBtn" v-move> <div class="moveBtn" v-move>
<div class="moveBtn-line"></div> <div class="moveBtn-line"></div>
@ -2201,52 +2230,65 @@ const pngradio: any = ref(null)
</el-dialog> </el-dialog>
<!-- 轨迹地图 --> <!-- 轨迹地图 -->
<el-dialog title="轨迹地图/图表" v-model="mapTrajectory" :close-on-click-modal="false" :before-close="mapClose" <el-dialog title="轨迹地图/图表" v-model="mapTrajectory" :close-on-click-modal="false" :before-close="mapClose"
top="30px" draggable destroy-on-close> top="30px" draggable destroy-on-close>
<div style="margin:0px 0px 15px 0px ;"> <div v-loading="pngloading">
<span>设定采样频率</span> <div class="map_select" style="margin-top: 0px;">
<span> <div style="width: 220px;margin-top: 5px;">请选择配置文件(trj_config*.txt)</div>
<el-select v-model="maptime" placeholder=" " size="large" @change="frequency(true)" <el-radio-group v-model="configradio">
style="width: 240px"> <el-radio v-for="(item, index) in conarr" :value="item.id">{{ item.fileName }}</el-radio>
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" /> </el-radio-group>
</el-select> </div>
</span> <div style="margin:0px 0px 15px 0px ;">
</div> <span>设定采样频率</span>
<div class="mapbox"> <span>
<div @click="tabbas = '1'" :class="tabbas == '1' ? 'mapbox_border' : 'mapbox_border1'">载体运动轨迹</div> <el-select v-model="maptime" placeholder=" " size="large" @change="frequency(true)"
<div @click="tabbas = '2'" :class="tabbas == '2' ? 'mapbox_border' : 'mapbox_border1'">高程变化动态图</div> style="width: 240px">
<div @click="tabbas = '3'" :class="tabbas == '3' ? 'mapbox_border' : 'mapbox_border1'">卫星叠加轨迹</div> <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
</div> </el-select>
<div v-show="tabbas == '1'" style="width:800px;height:600px;overflow: hidden;"> <el-button style="margin-left: 15px;" type="primary" @click="getSSELink()">确定</el-button>
<MapChart :coordinates="dynamicCoordinates" :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" /> </span>
</div> </div>
<div v-show="tabbas == '2'" style="width: 800px;height:600px;overflow: hidden;">
<Echart :chart-data="lineData" /> <div class="mapbox">
</div> <div @click="tabbas = '1'" :class="tabbas == '1' ? 'mapbox_border' : 'mapbox_border1'">载体运动轨迹</div>
<div v-show="tabbas == '3'" v-loading="pngloading"> <div @click="tabbas = '2'" :class="tabbas == '2' ? 'mapbox_border' : 'mapbox_border1'">高程变化动态图</div>
<div v-if="imgor"> <div @click="tabbas = '3'" :class="tabbas == '3' ? 'mapbox_border' : 'mapbox_border1'">卫星叠加轨迹</div>
<div class="map_select"> </div>
<div style="width: 210px;margin-top: 5px;">请选择底图(支持png/jpg格式)</div> <div v-show="tabbas == '1'" style="width:800px;height:600px;overflow: hidden;">
<el-radio-group v-model="pngradio"> <MapChart :coordinates="dynamicCoordinates" :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
<el-radio v-for="(item, index) in pngArr" :value="item.id">{{ item.fileName }}</el-radio> </div>
</el-radio-group> <div v-show="tabbas == '2'" style="width: 800px;height:600px;overflow: hidden;">
<Echart :chart-data="lineData" />
</div>
<div v-show="tabbas == '3'">
<div v-if="imgor" style="width: 800px;height:600px;overflow: hidden;margin: auto;">
<div class="map_select">
<div style="width: 210px;margin-top: 5px;">请选择底图(支持png/jpg格式)</div>
<el-radio-group v-model="pngradio">
<el-radio v-for="(item, index) in pngArr" :value="item.id">{{ item.fileName
}}</el-radio>
</el-radio-group>
</div>
<div class="map_select">
<div style="width: 220px;margin-top: 5px;">请选择地理信息文件(maps*.txt)</div>
<el-radio-group v-model="txtradio">
<el-radio v-for="(item, index) in txtArr" :value="item.id">{{ item.fileName
}}</el-radio>
</el-radio-group>
</div>
<div style="width: 100%;display: flex;">
<el-button type="primary" @click="pngsure">确定</el-button>
<el-button @click="">取消</el-button>
</div>
</div> </div>
<div class="map_select"> <div style="width: 800px;height:600px;overflow: hidden;margin: auto;" v-else>
<div style="width: 220px;margin-top: 5px;">请选择地理信息文件(maps*.txt)</div> <Imggui :imageUrl="pngobj.pngurl" :bounds="pngobj.textcontent" :trajectory="imgarrdata"
<el-radio-group v-model="txtradio"> :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
<el-radio v-for="(item, index) in txtArr" :value="item.id">{{ item.fileName }}</el-radio>
</el-radio-group>
</div>
<div style="width: 100%;display: flex;">
<el-button type="primary" @click="pngsure">确定</el-button>
<el-button @click="">取消</el-button>
</div> </div>
</div> </div>
<div style="width: 800px;height:600px;overflow: hidden;margin: auto;" v-else>
<Imggui :imageUrl="pngobj.pngurl" :bounds="pngobj.textcontent" :trajectory="imgarrdata"
:qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
</div>
</div> </div>
</el-dialog> </el-dialog>
<!-- 组件预览 --> <!-- 组件预览 -->
<!-- 视频播放器 --> <!-- 视频播放器 -->