优化地图节点遮挡选中策略
This commit is contained in:
@@ -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], {
|
||||||
|
|||||||
Reference in New Issue
Block a user