地图组件修改-根据经纬度计算距离
This commit is contained in:
parent
ba94e48c35
commit
86999cc440
60956
web/src/components/trajectory/chinese.json
Normal file
60956
web/src/components/trajectory/chinese.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,10 +10,9 @@
|
|||||||
import { ref, onMounted, watch, defineEmits, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, watch, defineEmits, onBeforeUnmount } from 'vue'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
import chinaData from './newJson.json'
|
import chinaData from './newJson.json'
|
||||||
|
|
||||||
// ==================== 常量与类型定义 ====================
|
// ==================== 常量与类型定义 ====================
|
||||||
const DEFAULT_MAP_SIZE = { width: 800, height: 600 }
|
const DEFAULT_MAP_SIZE = { width: 800, height: 600 }
|
||||||
const PROJECTION_CENTER = [104, 37] // 中国地理中心近似坐标
|
const PROJECTION_CENTER = [104, 37]
|
||||||
const SCALE_FACTORS = { normal: 600, fullscreen: 1000 }
|
const SCALE_FACTORS = { normal: 600, fullscreen: 1000 }
|
||||||
|
|
||||||
// ==================== 组件Props/Emits ====================
|
// ==================== 组件Props/Emits ====================
|
||||||
@ -21,8 +20,8 @@ const props = defineProps({
|
|||||||
coordinates: {
|
coordinates: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [
|
default: () => [
|
||||||
[116.405285, 39.904989], // 北京
|
[116.405285, 39.904989],
|
||||||
[121.472644, 31.231706] // 上海
|
[121.472644, 31.231706]
|
||||||
],
|
],
|
||||||
validator: value => value.every(coord =>
|
validator: value => value.every(coord =>
|
||||||
Array.isArray(coord) && coord.length === 2 &&
|
Array.isArray(coord) && coord.length === 2 &&
|
||||||
@ -40,7 +39,7 @@ const emit = defineEmits(['qvehuan1'])
|
|||||||
// ==================== 响应式状态 ====================
|
// ==================== 响应式状态 ====================
|
||||||
const mapContainer = ref(null)
|
const mapContainer = ref(null)
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false)
|
||||||
const isRouteInitialized = ref(false) // 原initqie,重命名以明确含义
|
const isRouteInitialized = ref(false)
|
||||||
|
|
||||||
// ==================== D3相关变量 ====================
|
// ==================== D3相关变量 ====================
|
||||||
let svgInstance = null
|
let svgInstance = null
|
||||||
@ -49,53 +48,57 @@ let markersGroup = null
|
|||||||
let zoomBehavior = null
|
let zoomBehavior = null
|
||||||
|
|
||||||
// ==================== 地图核心逻辑 ====================
|
// ==================== 地图核心逻辑 ====================
|
||||||
/**
|
|
||||||
* 创建地理投影配置
|
|
||||||
* @returns {d3.GeoProjection} D3地理投影实例
|
|
||||||
*/
|
|
||||||
const createProjection = () => {
|
const createProjection = () => {
|
||||||
const { width, height } = getContainerSize()
|
const { width, height } = getContainerSize()
|
||||||
return d3.geoMercator()
|
const projection = d3.geoMercator()
|
||||||
|
|
||||||
|
if (props.coordinates?.length >= 2) {
|
||||||
|
const validCoords = props.coordinates.filter(c => isValidCoordinate(c))
|
||||||
|
if (validCoords.length >= 2) {
|
||||||
|
const routeFeature = {
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: {
|
||||||
|
type: 'LineString',
|
||||||
|
coordinates: validCoords
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
projection.fitExtent(
|
||||||
|
[[50, 50], [width - 50, height - 50]], // 增加边距
|
||||||
|
routeFeature
|
||||||
|
)
|
||||||
|
return projection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projection
|
||||||
.center(PROJECTION_CENTER)
|
.center(PROJECTION_CENTER)
|
||||||
.scale(isFullscreen.value ? SCALE_FACTORS.fullscreen : SCALE_FACTORS.normal)
|
.scale(isFullscreen.value ? SCALE_FACTORS.fullscreen : SCALE_FACTORS.normal)
|
||||||
.translate([width / 2, height / 2])
|
.translate([width / 2, height / 2])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化地图基础结构
|
|
||||||
*/
|
|
||||||
const initializeMap = () => {
|
const initializeMap = () => {
|
||||||
// 清理旧实例
|
|
||||||
if (svgInstance) svgInstance.remove()
|
if (svgInstance) svgInstance.remove()
|
||||||
|
|
||||||
const { width, height } = getContainerSize()
|
const { width, height } = getContainerSize()
|
||||||
const projection = createProjection()
|
const projection = createProjection()
|
||||||
const pathGenerator = d3.geoPath().projection(projection)
|
const pathGenerator = d3.geoPath().projection(projection)
|
||||||
|
|
||||||
// 创建SVG容器
|
|
||||||
svgInstance = d3.select(mapContainer.value)
|
svgInstance = d3.select(mapContainer.value)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', width)
|
.attr('width', width)
|
||||||
.attr('height', height)
|
.attr('height', height)
|
||||||
|
.style('background-color', '#f0f8ff')
|
||||||
|
|
||||||
// 创建分层结构
|
|
||||||
const mainGroup = svgInstance.append('g')
|
const mainGroup = svgInstance.append('g')
|
||||||
mapGroup = mainGroup.append('g').attr('class', 'map-layer')
|
mapGroup = mainGroup.append('g').attr('class', 'map-layer')
|
||||||
markersGroup = mainGroup.append('g').attr('class', 'markers-layer')
|
markersGroup = mainGroup.append('g').attr('class', 'markers-layer')
|
||||||
|
|
||||||
// 绘制中国地图
|
|
||||||
renderChinaMap(pathGenerator)
|
renderChinaMap(pathGenerator)
|
||||||
|
|
||||||
// 初始化缩放行为
|
|
||||||
initializeZoomBehavior(mainGroup)
|
initializeZoomBehavior(mainGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染中国地图路径和文字
|
|
||||||
* @param {d3.GeoPath} path - 路径生成器
|
|
||||||
*/
|
|
||||||
const renderChinaMap = (path) => {
|
const renderChinaMap = (path) => {
|
||||||
// 绘制省份路径
|
|
||||||
mapGroup.append('g')
|
mapGroup.append('g')
|
||||||
.selectAll('path')
|
.selectAll('path')
|
||||||
.data(chinaData.features)
|
.data(chinaData.features)
|
||||||
@ -104,8 +107,8 @@ const renderChinaMap = (path) => {
|
|||||||
.attr('d', path)
|
.attr('d', path)
|
||||||
.attr('fill', '#e7e7e7')
|
.attr('fill', '#e7e7e7')
|
||||||
.attr('stroke', '#fff')
|
.attr('stroke', '#fff')
|
||||||
|
.attr('stroke-width', 0.3)
|
||||||
|
|
||||||
// 添加省份名称
|
|
||||||
mapGroup.append('g')
|
mapGroup.append('g')
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.data(chinaData.features)
|
.data(chinaData.features)
|
||||||
@ -114,36 +117,20 @@ const renderChinaMap = (path) => {
|
|||||||
.attr('transform', d => `translate(${path.centroid(d)})`)
|
.attr('transform', d => `translate(${path.centroid(d)})`)
|
||||||
.text(d => d.properties.name || '')
|
.text(d => d.properties.name || '')
|
||||||
.attr('text-anchor', 'middle')
|
.attr('text-anchor', 'middle')
|
||||||
.attr('dominant-baseline', 'central')
|
|
||||||
.style('font', '12px Arial')
|
.style('font', '12px Arial')
|
||||||
.attr('fill', '#333')
|
.attr('fill', '#333')
|
||||||
.style('pointer-events', 'none')
|
.style('pointer-events', 'none')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化缩放行为
|
|
||||||
* @param {d3.Selection} group - 需要应用缩放的分组
|
|
||||||
*/
|
|
||||||
const initializeZoomBehavior = (group) => {
|
|
||||||
zoomBehavior = d3.zoom()
|
|
||||||
.scaleExtent([0.2, 68])
|
|
||||||
.on('zoom', ({ transform }) => {
|
|
||||||
group.attr('transform', transform)
|
|
||||||
})
|
|
||||||
svgInstance.call(zoomBehavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 路线相关逻辑 ====================
|
// ==================== 路线相关逻辑 ====================
|
||||||
/**
|
|
||||||
* 更新路线和标记点
|
|
||||||
*/
|
|
||||||
const updateRoute = () => {
|
const updateRoute = () => {
|
||||||
if (!props.coordinates?.length) return
|
if (!props.coordinates?.length) return
|
||||||
if (!mapGroup) initializeMap()
|
|
||||||
|
initializeMap()
|
||||||
|
|
||||||
const projection = createProjection()
|
const projection = createProjection()
|
||||||
const pathGenerator = d3.geoPath().projection(projection)
|
const pathGenerator = d3.geoPath().projection(projection)
|
||||||
|
|
||||||
// 清理旧数据
|
|
||||||
if (isRouteInitialized.value) {
|
if (isRouteInitialized.value) {
|
||||||
mapGroup.selectAll('.route-path').remove()
|
mapGroup.selectAll('.route-path').remove()
|
||||||
markersGroup.selectAll('*').remove()
|
markersGroup.selectAll('*').remove()
|
||||||
@ -151,15 +138,10 @@ const updateRoute = () => {
|
|||||||
emit('qvehuan1', isRouteInitialized.value)
|
emit('qvehuan1', isRouteInitialized.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制新路线
|
|
||||||
renderRoutePath(pathGenerator)
|
renderRoutePath(pathGenerator)
|
||||||
renderMarkers(projection)
|
renderMarkers(projection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制路径动画
|
|
||||||
* @param {d3.GeoPath} path - 路径生成器
|
|
||||||
*/
|
|
||||||
const renderRoutePath = (path) => {
|
const renderRoutePath = (path) => {
|
||||||
const routeData = {
|
const routeData = {
|
||||||
type: "LineString",
|
type: "LineString",
|
||||||
@ -171,35 +153,29 @@ const renderRoutePath = (path) => {
|
|||||||
.attr('class', 'route-path')
|
.attr('class', 'route-path')
|
||||||
.attr('d', path)
|
.attr('d', path)
|
||||||
.attr('fill', 'none')
|
.attr('fill', 'none')
|
||||||
.attr('stroke', '#f00')
|
.attr('stroke', '#ff4757')
|
||||||
.attr('stroke-width', 0.1)
|
.attr('stroke-width', 2)
|
||||||
.attr('stroke-dasharray', function() {
|
.attr('stroke-linecap', 'round')
|
||||||
return this.getTotalLength()
|
|
||||||
})
|
|
||||||
.attr('stroke-dashoffset', function() {
|
|
||||||
return this.getTotalLength()
|
|
||||||
})
|
|
||||||
.transition()
|
|
||||||
.duration(2000)
|
|
||||||
.attr('stroke-dashoffset', 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制位置标记点
|
|
||||||
* @param {d3.GeoProjection} projection - 地理投影实例
|
|
||||||
*/
|
|
||||||
const renderMarkers = (projection) => {
|
const renderMarkers = (projection) => {
|
||||||
props.coordinates.forEach(coord => {
|
props.coordinates.forEach((coord, index) => {
|
||||||
if (!isValidCoordinate(coord)) return
|
if (!isValidCoordinate(coord)) return
|
||||||
|
|
||||||
const [x, y] = projection(coord)
|
const [x, y] = projection(coord)
|
||||||
|
// 标记点增强显示
|
||||||
markersGroup.append('circle')
|
markersGroup.append('circle')
|
||||||
.attr('cx', x)
|
.attr('cx', x)
|
||||||
.attr('cy', y)
|
.attr('cy', y)
|
||||||
.attr('r', 0.3)
|
.attr('r', 4) // 增大半径
|
||||||
.attr('fill', '#ff4757')
|
.attr('fill', '#ff4757')
|
||||||
.attr('stroke', 'white')
|
.attr('stroke', 'white')
|
||||||
.attr('stroke-width', 0)
|
.attr('stroke-width', 1)
|
||||||
|
.raise() // 确保标记点在最上层
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,9 +189,17 @@ const isValidCoordinate = (coord) =>
|
|||||||
Array.isArray(coord) && coord.length === 2 &&
|
Array.isArray(coord) && coord.length === 2 &&
|
||||||
!isNaN(coord[0]) && !isNaN(coord[1])
|
!isNaN(coord[0]) && !isNaN(coord[1])
|
||||||
|
|
||||||
// ==================== 全屏处理 ====================
|
// ==================== 交互逻辑 ====================
|
||||||
|
const initializeZoomBehavior = (group) => {
|
||||||
|
zoomBehavior = d3.zoom()
|
||||||
|
.scaleExtent([0.2, 68])
|
||||||
|
.on('zoom', ({ transform }) => {
|
||||||
|
group.attr('transform', transform)
|
||||||
|
})
|
||||||
|
svgInstance.call(zoomBehavior)
|
||||||
|
}
|
||||||
|
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
if (!isFullscreen.value) return
|
|
||||||
initializeMap()
|
initializeMap()
|
||||||
updateRoute()
|
updateRoute()
|
||||||
}
|
}
|
||||||
@ -225,25 +209,22 @@ const toggleFullscreen = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initializeMap()
|
initializeMap()
|
||||||
updateRoute()
|
updateRoute()
|
||||||
}, 100) // 等待DOM更新
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 生命周期钩子 ====================
|
// ==================== 生命周期钩子 ====================
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', handleWindowResize)
|
window.addEventListener('resize', handleWindowResize)
|
||||||
// 强制初始化地图基础结构
|
initializeMap()
|
||||||
initializeMap() // [!code ++]
|
if (props.coordinates?.length) {
|
||||||
// 仅在coordinates存在时更新路线
|
updateRoute()
|
||||||
if (props.coordinates?.length) { // [!code ++]
|
}
|
||||||
updateRoute() // [!code ++]
|
|
||||||
} // [!code ++]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', handleWindowResize)
|
window.removeEventListener('resize', handleWindowResize)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 响应式监听 ====================
|
|
||||||
watch(() => props.qvehuan, (newVal) => {
|
watch(() => props.qvehuan, (newVal) => {
|
||||||
isRouteInitialized.value = newVal
|
isRouteInitialized.value = newVal
|
||||||
updateRoute()
|
updateRoute()
|
||||||
@ -264,6 +245,8 @@ watch(() => props.coordinates, () => {
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen {
|
.fullscreen {
|
||||||
@ -274,23 +257,26 @@ watch(() => props.coordinates, () => {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-btn {
|
.fullscreen-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 20px;
|
||||||
right: 25px;
|
right: 30px;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
padding: 8px 16px;
|
padding: 10px 20px;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s;
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-btn:hover {
|
.fullscreen-btn:hover {
|
||||||
opacity: 0.9;
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
10
web/src/views/testdata/datamanagement/index.vue
vendored
10
web/src/views/testdata/datamanagement/index.vue
vendored
@ -1462,10 +1462,11 @@ function frequency(row: any) {
|
|||||||
const eventSource = ref(null)
|
const eventSource = ref(null)
|
||||||
let index = 0
|
let index = 0
|
||||||
const dynamicCoordinates = ref([])
|
const dynamicCoordinates = ref([])
|
||||||
|
let SSEclose
|
||||||
function closeSSE() {
|
function closeSSE() {
|
||||||
dynamicCoordinates.value.length = 0
|
dynamicCoordinates.value.length = 0
|
||||||
// SSEclose :any
|
// SSEclose :any
|
||||||
let SSEclose = new EventSource(userStore.webApiBaseUrl + '/sse/disconnect/' + userStore.userId)
|
SSEclose = new EventSource(userStore.webApiBaseUrl + '/sse/disconnect/' + userStore.userId)
|
||||||
eventSource.value?.close()
|
eventSource.value?.close()
|
||||||
}
|
}
|
||||||
function getSSELink() {
|
function getSSELink() {
|
||||||
@ -1482,10 +1483,9 @@ function getSSELink() {
|
|||||||
console.log('SSE消息:', data)
|
console.log('SSE消息:', data)
|
||||||
if (data) {
|
if (data) {
|
||||||
dynamicCoordinates.value.push([data.lon, data.lat])
|
dynamicCoordinates.value.push([data.lon, data.lat])
|
||||||
if (dynamicCoordinates.value.length > 2) {
|
// if (dynamicCoordinates.value.length > 2) {
|
||||||
dynamicCoordinates.value.shift()
|
// dynamicCoordinates.value.shift()
|
||||||
}
|
// }
|
||||||
|
|
||||||
lineData.value.push({ x: data.UtcTime, y: data.alt })
|
lineData.value.push({ x: data.UtcTime, y: data.alt })
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Loading…
Reference in New Issue
Block a user