Files
ChWebsiteApp/CHDataManagement/Workouts/MapImageCreator.swift
2025-08-31 16:27:32 +02:00

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))
}
}
}