|
|
@@ -0,0 +1,112 @@
|
|
|
+//
|
|
|
+// ArrowPolylineRenderer.swift
|
|
|
+// map_mapkit_ios
|
|
|
+//
|
|
|
+// Created by 诺诺诺的言 on 2025/7/14.
|
|
|
+//
|
|
|
+
|
|
|
+import UIKit
|
|
|
+import MapKit
|
|
|
+import Combine
|
|
|
+
|
|
|
+
|
|
|
+class ArrowPolylineRenderer: MKPolylineRenderer {
|
|
|
+ // 箭头属性
|
|
|
+ var arrowColor: UIColor = .red
|
|
|
+ var arrowSize: CGFloat = 8
|
|
|
+ var arrowSpacing: CGFloat = 30 // 默认值
|
|
|
+
|
|
|
+ // 边框属性
|
|
|
+ var borderWidth: CGFloat = 0
|
|
|
+ var borderColor: UIColor = .clear
|
|
|
+
|
|
|
+ override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
|
|
|
+ // 1. 绘制边框
|
|
|
+ if borderWidth > 0 {
|
|
|
+ context.saveGState()
|
|
|
+ // 边框宽度 = 主线条宽度 + 2倍边框宽度(左右各扩展 borderWidth)
|
|
|
+ let borderLineWidth = lineWidth + borderWidth * 2
|
|
|
+ context.setLineWidth(borderLineWidth)
|
|
|
+ context.setStrokeColor(borderColor.cgColor)
|
|
|
+ context.setLineCap(lineCap) // 保持与主线条一致的线帽样式
|
|
|
+ context.setLineJoin(lineJoin) // 保持与主线条一致的拐角样式
|
|
|
+ // 绘制边框线条
|
|
|
+ super.draw(mapRect, zoomScale: zoomScale, in: context)
|
|
|
+ context.restoreGState()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 绘制主轨迹线
|
|
|
+ super.draw(mapRect, zoomScale: zoomScale, in: context)
|
|
|
+
|
|
|
+ // 3. 绘制箭头(数量减半)
|
|
|
+ drawArrows(mapRect: mapRect, zoomScale: zoomScale, in: context)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func drawArrows(mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
|
|
|
+ guard polyline.pointCount >= 2 else { return }
|
|
|
+
|
|
|
+ let scaledArrowSize = arrowSize / zoomScale
|
|
|
+ let scaledArrowSpacing = arrowSpacing / zoomScale
|
|
|
+
|
|
|
+ var distance: CGFloat = 0
|
|
|
+ let points = polyline.points()
|
|
|
+
|
|
|
+ // 使用stride跳过每隔一个的箭头
|
|
|
+ let step = 2 // 跳过间隔
|
|
|
+ var arrowIndex = 0
|
|
|
+
|
|
|
+ for i in 0..<polyline.pointCount-1 {
|
|
|
+ let startPoint = point(for: points[i])
|
|
|
+ let endPoint = point(for: points[i+1])
|
|
|
+
|
|
|
+ let dx = endPoint.x - startPoint.x
|
|
|
+ let dy = endPoint.y - startPoint.y
|
|
|
+ let segmentLength = hypot(dx, dy)
|
|
|
+
|
|
|
+ guard segmentLength > 2 / zoomScale else { continue }
|
|
|
+
|
|
|
+ let arrowCount = Int(floor((distance + segmentLength) / scaledArrowSpacing))
|
|
|
+
|
|
|
+ for j in 0..<arrowCount {
|
|
|
+ // 只绘制每隔step个箭头
|
|
|
+ if arrowIndex % step == 0 {
|
|
|
+ let position = CGFloat(j) * scaledArrowSpacing - distance
|
|
|
+ let ratio = position / segmentLength
|
|
|
+
|
|
|
+ guard ratio >= 0 && ratio <= 1 else { continue }
|
|
|
+
|
|
|
+ let arrowPoint = CGPoint(
|
|
|
+ x: startPoint.x + dx * ratio,
|
|
|
+ y: startPoint.y + dy * ratio
|
|
|
+ )
|
|
|
+
|
|
|
+ let angle = atan2(dy, dx)
|
|
|
+ drawSingleArrow(at: arrowPoint, angle: angle, size: scaledArrowSize, in: context)
|
|
|
+ }
|
|
|
+ arrowIndex += 1
|
|
|
+ }
|
|
|
+
|
|
|
+ distance = (distance + segmentLength).truncatingRemainder(dividingBy: scaledArrowSpacing)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func drawSingleArrow(at point: CGPoint, angle: CGFloat, size: CGFloat, in context: CGContext) {
|
|
|
+ context.saveGState()
|
|
|
+ context.translateBy(x: point.x, y: point.y)
|
|
|
+ context.rotate(by: angle)
|
|
|
+
|
|
|
+ let arrowPath = UIBezierPath()
|
|
|
+ arrowPath.move(to: CGPoint(x: 3.2, y: 0))
|
|
|
+ arrowPath.addLine(to: CGPoint(x: 0, y: 2.6))
|
|
|
+ arrowPath.addLine(to: CGPoint(x: -3.2, y: 2.6))
|
|
|
+ arrowPath.addLine(to: CGPoint(x: 0, y: 0))
|
|
|
+ arrowPath.addLine(to: CGPoint(x: -3.2, y: -2.6))
|
|
|
+ arrowPath.addLine(to: CGPoint(x: 0, y: -2.6))
|
|
|
+ arrowPath.close()
|
|
|
+
|
|
|
+ context.setFillColor(arrowColor.cgColor)
|
|
|
+ context.addPath(arrowPath.cgPath)
|
|
|
+ context.fillPath()
|
|
|
+ context.restoreGState()
|
|
|
+ }
|
|
|
+}
|