Visvalingam & Whyatt和Douglas-Peucker是两种具有代表性的路径简化算法,在场景如GPS路径记录、地图绘制时,能够有效减少绘制的数据点数量,提升绘制效率,降低运行成本。
名词解释
- GIS(地理信息系统) 是一种用于捕获、存储、管理、分析和展示地理空间数据的系统。它通过地理位置将数据与地图结合,用于解决空间问题和支持决策。GIS 技术广泛应用于城市规划、交通管理、环境保护和灾害应对等领域。
- 概化(Generalization) 是一种简化地图内容的过程,通过去掉次要细节并突出主要特征,使地图在不同的缩放比例下易于理解。
Visvalingam & Whyatt 算法
原理
Visvalingam & Whyatt 算法通过计算路径中三点居点所成三角形的有效面积(Effective Area),从小到大删除最小面积的点,以进行简化。
代码实现(Kotlin)
// 类代表路径中的点的坐标
data class Point(val x: Double, val y: Double)
// 已知顶点坐标计算三角形面积
fun calculateEffectiveArea(p1: Point, p2: Point, p3: Point): Double {
return Math.abs((p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2.0)
}
// 实现 Visvalingam & Whyatt 算法
fun simplifyPathVW(points: List<Point>, threshold: Double): List<Point> {
if (points.size <= 2) return points
val areas = points.mapIndexed { index, point ->
if (index == 0 || index == points.size - 1) Double.MAX_VALUE
else calculateEffectiveArea(points[index - 1], point, points[index + 1])
}.toMutableList()
val mutablePoints = points.toMutableList()
while (areas.minOrNull()!! < threshold) {
val minIndex = areas.indexOf(areas.minOrNull()!!)
areas.removeAt(minIndex)
mutablePoints.removeAt(minIndex)
if (minIndex > 0 && minIndex < mutablePoints.size - 1) {
areas[minIndex - 1] = calculateEffectiveArea(
mutablePoints[minIndex - 1], mutablePoints[minIndex], mutablePoints[minIndex + 1]
)
}
}
return mutablePoints
}
Douglas-Peucker算法
原理
Douglas-Peucker 算法通过设定一个距离阈值,递归地找到离起始点和终止点构成的直线距离最远的点,如果该距离大于阈值,则保留该点并继续细分。否则,删除该点。
代码实现(Kotlin)
// 计算点到直线的垂直距离
fun perpendicularDistance(point: Point, start: Point, end: Point): Double {
val numerator = Math.abs((end.y - start.y) * point.x - (end.x - start.x) * point.y + end.x * start.y - end.y * start.x)
val denominator = Math.sqrt(Math.pow((end.y - start.y), 2.0) + Math.pow((end.x - start.x), 2.0))
return numerator / denominator
}
// 实现 Douglas-Peucker 算法
fun simplifyPathDP(points: List<Point>, epsilon: Double): List<Point> {
if (points.size <= 2) return points
var maxDistance = 0.0
var index = 0
for (i in 1 until points.size - 1) {
val distance = perpendicularDistance(points[i], points.first(), points.last())
if (distance > maxDistance) {
maxDistance = distance
index = i
}
}
return if (maxDistance > epsilon) {
val firstSegment = simplifyPathDP(points.subList(0, index + 1), epsilon)
val secondSegment = simplifyPathDP(points.subList(index, points.size), epsilon)
firstSegment + secondSegment.drop(1)
} else {
listOf(points.first(), points.last())
}
}