优化地图节点遮挡选中策略

This commit is contained in:
2026-06-06 03:16:38 +08:00
parent 63465f13e6
commit 9d3f007cb8
+88 -31
View File
@@ -201,12 +201,13 @@ function renderMarkers(forceFit: boolean) {
} }
const node = item const node = item
const selected = node.node_id === props.selectedNodeId const rawSelected = node.node_id === props.selectedNodeId
const overlapGroupKey = nodeOverlapGroupKey(node) const shuffledSelected = rawSelected && shuffledSelectedNodeIds.has(node.node_id)
const selected = rawSelected && !shuffledSelected
const overlapGroupKey = nodeOverlapGroupKey(node, overlapGroups)
const overlapGroup = overlapGroupKey ? overlapGroups.get(overlapGroupKey) : undefined const overlapGroup = overlapGroupKey ? overlapGroups.get(overlapGroupKey) : undefined
const overlapIndex = overlapGroup ? nodeOverlapIndex(node, overlapGroup) : 0 const overlapIndex = overlapGroup ? nodeOverlapIndex(node, overlapGroup) : 0
const shuffledSelected = selected && shuffledSelectedNodeIds.has(node.node_id) const raised = selected || node.node_id === lastRaisedNodeId.value
const raised = !shuffledSelected && (selected || node.node_id === lastRaisedNodeId.value)
const zIndexOffset = raised ? 1000 : overlapIndex const zIndexOffset = raised ? 1000 : overlapIndex
const nodeIcon = L.divIcon({ const nodeIcon = L.divIcon({
className: `node-marker${selected ? ' selected' : ''}`, className: `node-marker${selected ? ' selected' : ''}`,
@@ -248,6 +249,7 @@ function renderMarkers(forceFit: boolean) {
if (moveSelectedNodeBehindOverlap(node, overlapGroups)) { if (moveSelectedNodeBehindOverlap(node, overlapGroups)) {
shuffledSelectedNodeIds.add(node.node_id) shuffledSelectedNodeIds.add(node.node_id)
marker?.closePopup() marker?.closePopup()
emit('clear-node')
renderMarkers(false) renderMarkers(false)
} }
return return
@@ -258,7 +260,7 @@ function renderMarkers(forceFit: boolean) {
}) })
marker.on('contextmenu', (event) => openNodeMenu(node, event)) marker.on('contextmenu', (event) => openNodeMenu(node, event))
if (selected && !shuffledSelected && !marker.getPopup()?.isOpen()) { if (selected && !marker.getPopup()?.isOpen()) {
marker.openPopup() marker.openPopup()
} }
bounds.extend([node.latitude, node.longitude]) bounds.extend([node.latitude, node.longitude])
@@ -286,36 +288,55 @@ function mapMarkerKey(item: MapRenderable): string {
function buildOverlapGroups(items: MapRenderable[]): Map<string, string[]> { function buildOverlapGroups(items: MapRenderable[]): Map<string, string[]> {
const groups = new Map<string, string[]>() const groups = new Map<string, string[]>()
for (const item of items) { if (!map) {
if (item.type === 'cluster') { return groups
continue
}
const key = nodeOverlapGroupKey(item)
const group = groups.get(key)
if (group) {
group.push(item.node_id)
} else {
groups.set(key, [item.node_id])
}
} }
for (const [key, nodeIds] of groups) { const capsules = items
if (nodeIds.length < 2) { .filter((item): item is MapNode => item.type !== 'cluster')
groups.delete(key) .map((node) => ({ node, bounds: nodeCapsuleBounds(node) }))
overlapShuffleOrders.delete(key) const visited = new Set<string>()
for (const capsule of capsules) {
if (visited.has(capsule.node.node_id)) {
continue continue
} }
const existingOrder = overlapShuffleOrders.get(key) ?? [] const stack = [capsule]
const activeIds = new Set(nodeIds) const group: string[] = []
const ordered = existingOrder.filter((nodeId) => activeIds.has(nodeId)) visited.add(capsule.node.node_id)
for (const nodeId of nodeIds) {
if (!ordered.includes(nodeId)) { while (stack.length > 0) {
ordered.push(nodeId) const current = stack.pop()
if (!current) {
continue
}
group.push(current.node.node_id)
for (const candidate of capsules) {
if (visited.has(candidate.node.node_id)) {
continue
}
if (capsuleBoundsOverlap(current.bounds, candidate.bounds)) {
visited.add(candidate.node.node_id)
stack.push(candidate)
}
} }
} }
overlapShuffleOrders.set(key, ordered)
groups.set(key, ordered) if (group.length >= 2) {
const key = overlapGroupKey(group)
const existingOrder = overlapShuffleOrders.get(key) ?? []
const activeIds = new Set(group)
const ordered = existingOrder.filter((nodeId) => activeIds.has(nodeId))
for (const nodeId of group) {
if (!ordered.includes(nodeId)) {
ordered.push(nodeId)
}
}
overlapShuffleOrders.set(key, ordered)
groups.set(key, ordered)
}
} }
for (const key of overlapShuffleOrders.keys()) { for (const key of overlapShuffleOrders.keys()) {
@@ -327,8 +348,13 @@ function buildOverlapGroups(items: MapRenderable[]): Map<string, string[]> {
return groups return groups
} }
function nodeOverlapGroupKey(node: MapNode): string { function nodeOverlapGroupKey(node: MapNode, overlapGroups: Map<string, string[]>): string | null {
return `${node.latitude.toFixed(6)}:${node.longitude.toFixed(6)}` for (const [key, nodeIds] of overlapGroups) {
if (nodeIds.includes(node.node_id)) {
return key
}
}
return null
} }
function nodeOverlapIndex(node: MapNode, group: string[]): number { function nodeOverlapIndex(node: MapNode, group: string[]): number {
@@ -337,7 +363,10 @@ function nodeOverlapIndex(node: MapNode, group: string[]): number {
} }
function moveSelectedNodeBehindOverlap(node: MapNode, overlapGroups: Map<string, string[]>): boolean { function moveSelectedNodeBehindOverlap(node: MapNode, overlapGroups: Map<string, string[]>): boolean {
const groupKey = nodeOverlapGroupKey(node) const groupKey = nodeOverlapGroupKey(node, overlapGroups)
if (!groupKey) {
return false
}
const group = overlapGroups.get(groupKey) const group = overlapGroups.get(groupKey)
if (!group || group.length < 2) { if (!group || group.length < 2) {
return false return false
@@ -349,6 +378,34 @@ function moveSelectedNodeBehindOverlap(node: MapNode, overlapGroups: Map<string,
return true return true
} }
function overlapGroupKey(nodeIds: string[]): string {
return [...nodeIds].sort().join('|')
}
function nodeCapsuleBounds(node: MapNode): { left: number; right: number; top: number; bottom: number } {
const point = map!.latLngToLayerPoint([node.latitude, node.longitude])
const width = nodeCapsuleWidth(node)
const height = 22
return {
left: point.x - width / 2,
right: point.x + width / 2,
top: point.y - height / 2,
bottom: point.y + height / 2,
}
}
function nodeCapsuleWidth(node: MapNode): number {
const label = node.label || 'N'
return Math.max(34, Math.ceil(label.length * 6 + 10))
}
function capsuleBoundsOverlap(
left: { left: number; right: number; top: number; bottom: number },
right: { left: number; right: number; top: number; bottom: number },
): boolean {
return left.left <= right.right && left.right >= right.left && left.top <= right.bottom && left.bottom >= right.top
}
function buildClusterMarker(cluster: MapClusterNode): L.Marker { function buildClusterMarker(cluster: MapClusterNode): L.Marker {
const size = clusterIconSize(cluster.count) const size = clusterIconSize(cluster.count)
const marker = L.marker([cluster.latitude, cluster.longitude], { const marker = L.marker([cluster.latitude, cluster.longitude], {