129 lines
4.1 KiB
Swift
129 lines
4.1 KiB
Swift
import Foundation
|
|
import AppKit
|
|
import MapKit
|
|
|
|
struct MapImageCreator {
|
|
|
|
let locations: [CLLocation]
|
|
|
|
func createMapSnapshot(
|
|
size layoutSize: CGSize,
|
|
scale: CGFloat = 2.0,
|
|
lineWidth: CGFloat = 5,
|
|
paddingFactor: Double = 1.2,
|
|
lineColor: NSColor = .systemBlue
|
|
) -> (image: NSImage, imagePoints: [CGPoint])? {
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
|
|
var result: (image: NSImage, imagePoints: [CGPoint])?
|
|
|
|
self.createMapSnapshot(
|
|
size: layoutSize,
|
|
scale: scale,
|
|
lineWidth: lineWidth,
|
|
paddingFactor: paddingFactor,
|
|
lineColor: lineColor
|
|
) { res in
|
|
result = res
|
|
semaphore.signal()
|
|
}
|
|
|
|
semaphore.wait()
|
|
return result
|
|
}
|
|
|
|
func createMapSnapshot(
|
|
size layoutSize: CGSize,
|
|
scale: CGFloat = 2.0,
|
|
lineWidth: CGFloat = 5,
|
|
paddingFactor: Double = 1.2,
|
|
lineColor: NSColor = .systemBlue
|
|
) async -> (image: NSImage, imagePoints: [CGPoint])? {
|
|
await withCheckedContinuation { c in
|
|
self.createMapSnapshot(
|
|
size: layoutSize,
|
|
scale: scale,
|
|
lineWidth: lineWidth,
|
|
paddingFactor: paddingFactor,
|
|
lineColor: lineColor
|
|
) { c.resume(returning: $0) }
|
|
}
|
|
}
|
|
|
|
func createMapSnapshot(
|
|
size layoutSize: CGSize,
|
|
scale: CGFloat = 2.0,
|
|
lineWidth: CGFloat = 5,
|
|
paddingFactor: Double = 1.2,
|
|
lineColor: NSColor = .systemBlue,
|
|
completion: @escaping ((image: NSImage, imagePoints: [CGPoint])?) -> Void
|
|
) {
|
|
guard !locations.isEmpty else {
|
|
completion(nil)
|
|
return
|
|
}
|
|
let coordinates = locations.map { $0.coordinate }
|
|
|
|
let pixelSize = CGSize(width: layoutSize.width * scale, height: layoutSize.height * scale)
|
|
|
|
let options = MKMapSnapshotter.Options()
|
|
options.size = pixelSize
|
|
options.preferredConfiguration = MKHybridMapConfiguration(elevationStyle: .flat)
|
|
|
|
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
|
let boundingMapRect = polyline.boundingMapRect
|
|
let region = MKCoordinateRegion(boundingMapRect)
|
|
|
|
let latDelta = region.span.latitudeDelta * paddingFactor
|
|
let lonDelta = region.span.longitudeDelta * paddingFactor
|
|
let paddedRegion = MKCoordinateRegion(
|
|
center: region.center,
|
|
span: MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
|
|
)
|
|
|
|
options.region = paddedRegion
|
|
|
|
let snapshotter = MKMapSnapshotter(options: options)
|
|
snapshotter.start { snapshotOrNil, error in
|
|
guard let snapshot = snapshotOrNil, error == nil else {
|
|
print("Snapshot error: \(error?.localizedDescription ?? "unknown error")")
|
|
completion(nil)
|
|
return
|
|
}
|
|
|
|
let image = NSImage(size: pixelSize)
|
|
image.lockFocus()
|
|
|
|
snapshot.image.draw(in: CGRect(origin: .zero, size: pixelSize))
|
|
|
|
let path = NSBezierPath()
|
|
path.lineJoinStyle = .round
|
|
let imagePoints = coordinates.map { snapshot.point(for: $0) }
|
|
|
|
if let first = imagePoints.first {
|
|
path.move(to: first)
|
|
for point in imagePoints.dropFirst() {
|
|
path.line(to: point)
|
|
}
|
|
|
|
lineColor.setStroke()
|
|
path.lineWidth = lineWidth * scale
|
|
path.stroke()
|
|
}
|
|
|
|
image.unlockFocus()
|
|
|
|
// Recalculate imagePoints since they were inside the drawing block
|
|
let widthFactor = 1 / pixelSize.width
|
|
let heightFactor = 1 / pixelSize.height
|
|
let finalImagePoints = coordinates.map { coordinate in
|
|
let point = snapshot.point(for: coordinate)
|
|
return CGPoint(x: point.x * widthFactor,
|
|
y: point.y * heightFactor)
|
|
}
|
|
|
|
completion((image, finalImagePoints))
|
|
}
|
|
}
|
|
}
|