轨迹图逻辑修改
This commit is contained in:
parent
694e16240c
commit
d76271da24
@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<div class="map-container">
|
||||
<svg ref="svgRef" class="trajectory-svg" width="800" height="600">
|
||||
<!-- ✅ 将 <image> 放入 zoom-container 内,使其与轨迹同步缩放 -->
|
||||
<div class="map-container" :class="{ fullscreen: isFullscreen }">
|
||||
<!-- 全屏按钮 -->
|
||||
<button class="fullscreen-btn" @click="toggleFullscreen">
|
||||
{{ isFullscreen ? '退出全屏' : '全屏' }}
|
||||
</button>
|
||||
|
||||
<svg ref="svgRef" class="trajectory-svg" :width="svgWidth" :height="svgHeight">
|
||||
<g class="zoom-container">
|
||||
<image
|
||||
:xlink:href="imageUrl"
|
||||
width="800"
|
||||
height="600"
|
||||
:width="svgWidth"
|
||||
:height="svgHeight"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@ -39,6 +43,13 @@ const props = defineProps({
|
||||
// === Emits 定义 ===
|
||||
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 引用 ===
|
||||
const svgRef = ref(null)
|
||||
const xScale = ref(null)
|
||||
@ -47,15 +58,22 @@ const zoomTransform = ref(d3.zoomIdentity)
|
||||
|
||||
// === 初始化比例尺 ===
|
||||
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
|
||||
|
||||
xScale.value = d3.scaleLinear()
|
||||
.domain([minLng, maxLng])
|
||||
.range([0, 800])
|
||||
.range([0, svgWidth.value])
|
||||
|
||||
yScale.value = d3.scaleLinear()
|
||||
.domain([minLat, maxLat])
|
||||
.range([600, 0])
|
||||
.range([svgHeight.value, 0])
|
||||
|
||||
initZoom()
|
||||
updateTrajectory()
|
||||
@ -65,7 +83,7 @@ const initMap = () => {
|
||||
const initZoom = () => {
|
||||
const mapZoom = d3.zoom()
|
||||
.scaleExtent([0.5, 3]) // 缩放范围
|
||||
.translateExtent([[-800, -600], [1600, 1200]]) // 平移范围
|
||||
.translateExtent([[-svgWidth.value, -svgHeight.value], [2*svgWidth.value, 2*svgHeight.value]])
|
||||
.on("zoom", (event) => {
|
||||
d3.select(svgRef.value).select('.zoom-container')
|
||||
.attr("transform", event.transform)
|
||||
@ -79,9 +97,8 @@ const initZoom = () => {
|
||||
const updateTrajectory = () => {
|
||||
if (!props.trajectory?.length || !xScale.value || !yScale.value) return
|
||||
|
||||
if (props.qvehuan) {
|
||||
clearOldElements()
|
||||
}
|
||||
// 每次更新前主动清除原有轨迹
|
||||
// clearOldElements()
|
||||
|
||||
if (shouldRedraw()) {
|
||||
renderTrajectoryPath()
|
||||
@ -160,12 +177,65 @@ const renderTrajectoryMarkers = () => {
|
||||
|
||||
// 绘制条件判断
|
||||
const shouldRedraw = () => {
|
||||
// 全屏状态变化时强制重新绘制
|
||||
if (isFullscreen.value !== prevFullscreenState.value) {
|
||||
prevFullscreenState.value = isFullscreen.value
|
||||
return true
|
||||
}
|
||||
|
||||
if (props.qvehuan && props.trajectory.length > 0) {
|
||||
return true
|
||||
}
|
||||
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) => {
|
||||
if (newVal.length) {
|
||||
@ -180,6 +250,18 @@ watch(() => props.qvehuan, (newVal) => {
|
||||
})
|
||||
|
||||
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')
|
||||
if (imageElement) {
|
||||
imageElement.on('load', initMap)
|
||||
@ -187,6 +269,8 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
d3.select(svgRef.value).selectAll('*').remove()
|
||||
})
|
||||
</script>
|
||||
@ -198,6 +282,36 @@ onBeforeUnmount(() => {
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
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 {
|
||||
@ -206,6 +320,9 @@ onBeforeUnmount(() => {
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
cursor: grab;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.trajectory-svg:active {
|
||||
|
204
web/src/views/testdata/datamanagement/index.vue
vendored
204
web/src/views/testdata/datamanagement/index.vue
vendored
@ -1594,18 +1594,20 @@ const imgor = ref(true)
|
||||
function openMap(row: any) {
|
||||
pngobj.vlaue = {
|
||||
pngurl: '',
|
||||
conid: '',
|
||||
textcontent: []
|
||||
}
|
||||
pngform.value = JSON.parse(JSON.stringify(row))
|
||||
fredid.value = row.id
|
||||
getSSELink()
|
||||
|
||||
getpngdata()
|
||||
tabbas.value = '1'
|
||||
mapTrajectory.value = true
|
||||
maptime.value = 300
|
||||
if (row.custom1) {
|
||||
maptime.value = 60
|
||||
console.log(JSON.parse(row.custom1))
|
||||
if (JSON.parse(row.custom1).pngurl && JSON.parse(row.custom1).textcontent && JSON.parse(row.custom1).conid) {
|
||||
ElMessageBox.confirm(
|
||||
'当前轨迹文件已有关联图片,是否直接加载轨迹?',
|
||||
'当前轨迹文件已有关联轨迹配置文件和图片,是否直接加载轨迹?',
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
@ -1615,13 +1617,18 @@ function openMap(row: any) {
|
||||
)
|
||||
.then(() => {
|
||||
pngobj.value = JSON.parse(row.custom1)
|
||||
configradio.value = pngobj.value.conid
|
||||
imgor.value = false
|
||||
getSSELink()
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
const pngArr = ref([])
|
||||
const txtArr = ref([])
|
||||
const conarr = ref([])
|
||||
const pngloading = ref(false)
|
||||
function getpngdata() {
|
||||
pngloading.value = true
|
||||
@ -1629,52 +1636,61 @@ function getpngdata() {
|
||||
pngloading.value = false
|
||||
pngArr.value = res.data.tsFilesListPng
|
||||
txtArr.value = res.data.tsFilesListTxt
|
||||
conarr.value = res.data.tsFilesListConfig
|
||||
})
|
||||
}
|
||||
const pngobj: any = ref({
|
||||
pngurl: '',
|
||||
conid: '',
|
||||
textcontent: []
|
||||
})
|
||||
function pngsure() {
|
||||
if (!(pngradio.value && txtradio.value)) {
|
||||
ElMessage.warning('请选择底图或地理信息文件')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新的响应式对象
|
||||
const newPngobj = {
|
||||
pngurl: '',
|
||||
textcontent: []
|
||||
}
|
||||
|
||||
// 更新图片URL
|
||||
pngArr.value.forEach((item: any) => {
|
||||
if (pngradio.value == item.id) {
|
||||
newPngobj.pngurl = item.url
|
||||
if (!(pngradio.value && txtradio.value && configradio.value)) {
|
||||
ElMessage.warning('请选择底图或地理信息文件')
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// 更新文本内容
|
||||
txtArr.value.forEach((item: any) => {
|
||||
if (txtradio.value == item.id) {
|
||||
newPngobj.textcontent = JSON.parse(JSON.stringify(item.fileContent)).split(",")
|
||||
// 创建新的响应式对象
|
||||
const newPngobj = {
|
||||
pngurl: '',
|
||||
textcontent: [],
|
||||
conid: '',
|
||||
}
|
||||
})
|
||||
|
||||
// 使用解构创建新对象保证响应式特性
|
||||
pngobj.value = {
|
||||
...pngobj.value,
|
||||
...newPngobj
|
||||
}
|
||||
// 更新图片URL
|
||||
pngArr.value.forEach((item: any) => {
|
||||
if (pngradio.value == item.id) {
|
||||
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) => { })
|
||||
imgor.value = false
|
||||
// 使用解构创建新对象保证响应式特性
|
||||
pngobj.value = {
|
||||
...pngobj.value,
|
||||
...newPngobj
|
||||
}
|
||||
|
||||
// 修正拼写错误
|
||||
pngform.value.custom1 = JSON.stringify(pngobj.value)
|
||||
|
||||
// 发起更新请求
|
||||
updateTsFiles(pngform.value).then((res: any) => { })
|
||||
imgor.value = false
|
||||
}
|
||||
function mapClose() {
|
||||
configradio.value = null
|
||||
txtradio.value = null
|
||||
pngradio.value = null
|
||||
imgor.value = true
|
||||
@ -1687,6 +1703,18 @@ function mapClose() {
|
||||
}
|
||||
// 1s/10s/30s/1m/2m/5m
|
||||
const options = ref([
|
||||
{
|
||||
name: '10秒钟'
|
||||
, id: 10
|
||||
},
|
||||
{
|
||||
name: '1分钟'
|
||||
, id: 60
|
||||
},
|
||||
{
|
||||
name: '2分钟'
|
||||
, id: 120
|
||||
},
|
||||
{
|
||||
name: '5分钟'
|
||||
, id: 300
|
||||
@ -1694,15 +1722,15 @@ const options = ref([
|
||||
{
|
||||
name: '10分钟'
|
||||
, id: 600
|
||||
},
|
||||
}
|
||||
])
|
||||
const maptime: any = ref(300)
|
||||
const maptime: any = ref(60)
|
||||
//频率
|
||||
const qvehuan: any = ref(false)
|
||||
const qvehuan1: any = ref(false)
|
||||
function frequency(row: any) {
|
||||
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) => {
|
||||
if (res.code == '0' && row) {
|
||||
ElMessage.success("切换成功")
|
||||
@ -1813,6 +1841,7 @@ const formatFileSize = (size: number): string => {
|
||||
|
||||
const txtradio: any = ref(null)
|
||||
const pngradio: any = ref(null)
|
||||
const configradio: any = ref(null)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -1850,7 +1879,7 @@ const pngradio: any = ref(null)
|
||||
</template>
|
||||
</el-tree>
|
||||
<el-button type="primary" @click="scan()" style="width: 100%;" :loading="tonloading">{{ buttonmsg
|
||||
}}</el-button>
|
||||
}}</el-button>
|
||||
</div>
|
||||
<div class="moveBtn" v-move>
|
||||
<div class="moveBtn-line"></div>
|
||||
@ -2201,52 +2230,65 @@ const pngradio: any = ref(null)
|
||||
</el-dialog>
|
||||
<!-- 轨迹地图 -->
|
||||
<el-dialog title="轨迹地图/图表" v-model="mapTrajectory" :close-on-click-modal="false" :before-close="mapClose"
|
||||
top="30px" draggable destroy-on-close>
|
||||
<div style="margin:0px 0px 15px 0px ;">
|
||||
<span>设定采样频率:</span>
|
||||
<span>
|
||||
<el-select v-model="maptime" placeholder=" " size="large" @change="frequency(true)"
|
||||
style="width: 240px">
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mapbox">
|
||||
<div @click="tabbas = '1'" :class="tabbas == '1' ? 'mapbox_border' : 'mapbox_border1'">载体运动轨迹</div>
|
||||
<div @click="tabbas = '2'" :class="tabbas == '2' ? 'mapbox_border' : 'mapbox_border1'">高程变化动态图</div>
|
||||
<div @click="tabbas = '3'" :class="tabbas == '3' ? 'mapbox_border' : 'mapbox_border1'">卫星叠加轨迹</div>
|
||||
</div>
|
||||
<div v-show="tabbas == '1'" style="width:800px;height:600px;overflow: hidden;">
|
||||
<MapChart :coordinates="dynamicCoordinates" :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
|
||||
</div>
|
||||
<div v-show="tabbas == '2'" style="width: 800px;height:600px;overflow: hidden;">
|
||||
<Echart :chart-data="lineData" />
|
||||
</div>
|
||||
<div v-show="tabbas == '3'" v-loading="pngloading">
|
||||
<div v-if="imgor">
|
||||
<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>
|
||||
top="30px" draggable destroy-on-close>
|
||||
<div v-loading="pngloading">
|
||||
<div class="map_select" style="margin-top: 0px;">
|
||||
<div style="width: 220px;margin-top: 5px;">请选择配置文件(trj_config*.txt):</div>
|
||||
<el-radio-group v-model="configradio">
|
||||
<el-radio v-for="(item, index) in conarr" :value="item.id">{{ item.fileName }}</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div style="margin:0px 0px 15px 0px ;">
|
||||
<span>设定采样频率:</span>
|
||||
<span>
|
||||
<el-select v-model="maptime" placeholder=" " size="large" @change="frequency(true)"
|
||||
style="width: 240px">
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-button style="margin-left: 15px;" type="primary" @click="getSSELink()">确定</el-button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mapbox">
|
||||
<div @click="tabbas = '1'" :class="tabbas == '1' ? 'mapbox_border' : 'mapbox_border1'">载体运动轨迹</div>
|
||||
<div @click="tabbas = '2'" :class="tabbas == '2' ? 'mapbox_border' : 'mapbox_border1'">高程变化动态图</div>
|
||||
<div @click="tabbas = '3'" :class="tabbas == '3' ? 'mapbox_border' : 'mapbox_border1'">卫星叠加轨迹</div>
|
||||
</div>
|
||||
<div v-show="tabbas == '1'" style="width:800px;height:600px;overflow: hidden;">
|
||||
<MapChart :coordinates="dynamicCoordinates" :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
|
||||
</div>
|
||||
<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 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 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 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>
|
||||
|
||||
|
||||
</el-dialog>
|
||||
<!-- 组件预览 -->
|
||||
<!-- 视频播放器 -->
|
||||
|
Loading…
Reference in New Issue
Block a user