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