Swift 绘制正六边形

正六边形的结构

1
2
3
4
5
6
7
struct Hexagon:{
let points: [CLLocationCoordinate2D]
let centerLatitude: Double
let centerLongitude: Double
let startAngle: Double
let radius: Double
}

正六边形顶点确认

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// startAngle 正六边形的起始角度
// radius 正六边形中心到顶点的距离
func drawHexagon(centerLatitude: Double, centerLongitude: Double, startAngle: Double, radius: Double) -> Hexagon {

let point: MKMapPoint = MKMapPoint(CLLocationCoordinate2D(latitude: centerLatitude, longitude: centerLongitude))
var i = 0
var points: [MKMapPoint] = []
var angle = startAngle / 180 * Double.pi

while i < 6 {
let x = point.x + radius * sin(angle) * 7.612952538285698
let y = point.y - radius * cos(angle) * 7.612952538285698
angle += Double.pi / 3
let p: MKMapPoint = MKMapPoint(x: x, y: y)
points.append(p)
i += 1
}

// 实际距离,单位 m
let physicalDistance = points[0].distance(to: points[1])
// CG 距离,两点的距离公式
let cgDistance = sqrt(pow((points[0].x - points[1].x), 2) + pow((points[0].y - points[1].y), 2))
print(cgDistance)
print(physicalDistance)
print(cgDistance / physicalDistance)

let hexagon = Hexagon(points: [

points[0].coordinate,
points[1].coordinate,
points[2].coordinate,
points[3].coordinate,
points[4].coordinate,
points[5].coordinate

], centerLatitude: centerLatitude, centerLongitude: centerLongitude, startAngle: startAngle, radius: radius)

return hexagon
}

关于 7.612952538285698,是通过 CG 距离除 cllocationdistance 也就是实际物理距离得到的。

在已知正六边形的基础上绘制相邻的正六边形

准确来说,应该分成两步:

  1. 获得正六边形相邻 6 个正六边形的中心;
  2. 以新的中心为基础,确定新正六边形的 6 个顶点。

获得相邻正六边形的中心

由图可以观察到,相邻正六边形的中心位于正六边形顶点 +30 度,距离根号三倍半径的地方。

于是有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func getCenters(hexagon: Hexagon, direction: Int) -> MKMapPoint {
// 取出中心点
let point: MKMapPoint = MKMapPoint(CLLocationCoordinate2D(latitude: hexagon.centerLatitude, longitude: hexagon.centerLongitude))
// 一共有六个相邻 hex
let pointCount = 6
var i = 0
var centerPoints: [MKMapPoint] = []
// 在生成顶点的角度下偏转 30 度即为六个相邻 hex 的中心点方向
var centerAngle = (hexagon.startAngle + 30) / 180 * Double.pi

while i < pointCount {
// 2 分之根号 3 为中心点到边的垂直距离,乘 2 为中心点到相邻 hex 中心点的距离
let x = point.x + hexagon.radius / 2 * sqrt(3) * 2 * sin(centerAngle) * 7.612952538285698
let y = point.y - hexagon.radius / 2 * sqrt(3) * 2 * cos(centerAngle) * 7.612952538285698
centerAngle += 2 * Double.pi / Double(pointCount)
let center: MKMapPoint = MKMapPoint(x: x, y: y)
centerPoints.append(center)
i += 1
}
return centerPoints[direction]
}

绘制相邻正六边形

1
2
3
4
5
6
7
8
9
let hexagon = drawHexagon(...)

// 绘制正六边形相邻右上角的正六边形
let topRighthexagon = drawHexagon(
centerLatitude: getCenters(hexagon: hexagon, direction: 0).latitude,
centerLongitude: getCenters(hexagon: hexagon, direction: 0).longitude,
startAngle: hexagon.startAngle,
radius: hexagon.radius
)

全新的正六边形顶点确定方法

参考:Moving a CLLocation by x meters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func drawHexagon(origin: CLLocationCoordinate2D, distance: Double) -> [CLLocationCoordinate2D]{
let distRadians = distance / (6372797.6) // earth radius in meters

// Degrees To Radians
let lat1 = origin.latitude * .pi / 180
let lon1 = origin.longitude * .pi / 180

var i = 0

// var bearing = 90.0 * .pi / 180
// var bearing = pi/2

var bearing = 0.0
var points: [CLLocationCoordinate2D] = []

while i < 6 {

let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearing))
let lon2 = lon1 + atan2(sin(bearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))

let point = CLLocationCoordinate2D(latitude: lat2 * 180 / .pi, longitude: lon2 * 180 / .pi) // Radians To Degrees
i += 1
bearing += .pi / 3
points.append(point)
}

return points
}

本质上是调用这个方法六次,每次 30 度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension CLLocationCoordinate2D {

/// Get coordinate moved from current to `distanceMeters` meters with azimuth `azimuth` [0, Double.pi)
///
/// - Parameters:
/// - distanceMeters: the distance in meters
/// - azimuth: the azimuth (bearing)
/// - Returns: new coordinate
func shift(byDistance distanceMeters: Double, azimuth: Double) -> CLLocationCoordinate2D {
let bearing = azimuth
let origin = self
let distRadians = distanceMeters / (6372797.6) // earth radius in meters

let lat1 = origin.latitude * Double.pi / 180
let lon1 = origin.longitude * Double.pi / 180

let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearing))
let lon2 = lon1 + atan2(sin(bearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))
return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi)
}
}

point.shift(byDistance: 100.0, azimuth: .pi)

关于 azimuth:

  • 0 为正北(N)
  • pi/2 为正东(90,E)
  • pi 为正南(180,S)
  • 3/2pi为正西(270,W)

为了更好理解,可以将

1
2
3
4
// let bearing = azimuth ->
let bearing = azimuth * .pi / 180

point.shift(byDistance: 100.0, azimuth: 180)