Initial commit for first version of project

This commit is contained in:
Dennis Nemec
2026-09-02 20:54:36 +02:00
parent 3d9f1ab6bc
commit 992bb32025
56 changed files with 5139 additions and 31 deletions

View File

@ -0,0 +1,107 @@
//
// PhotoEncoder.swift
// GasTankAR
//
// Created by Dennis Nemec on 28.08.26.
//
import ImageIO
import UniformTypeIdentifiers
import CoreLocation
import UIKit
/// Kodiert einen Snapshot als JPEG und bettet dabei EXIF-Aufnahmezeit sowie – falls vorhanden –
/// GPS-Metadaten direkt in die Bilddatei ein (ImageIO). So tragen auch exportierte Bilder ihren
/// Aufnahmeort mit sich.
enum PhotoEncoder {
/// Erzeugt JPEG-Daten mit eingebetteten Metadaten. Fällt bei Fehlern auf `UIImage.jpegData` zurück.
static func jpegData(
from image: UIImage,
coordinate: CLLocationCoordinate2D?,
date: Date,
compressionQuality: CGFloat = 0.9
) -> Data? {
guard let cgImage = image.cgImage else {
return image.jpegData(compressionQuality: compressionQuality)
}
let output = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(
output, UTType.jpeg.identifier as CFString, 1, nil
) else {
return image.jpegData(compressionQuality: compressionQuality)
}
var properties: [CFString: Any] = [
kCGImageDestinationLossyCompressionQuality: compressionQuality,
kCGImagePropertyOrientation: CGImagePropertyOrientation(image.imageOrientation).rawValue,
kCGImagePropertyExifDictionary: [
kCGImagePropertyExifDateTimeOriginal: Self.exifDateFormatter.string(from: date),
kCGImagePropertyExifDateTimeDigitized: Self.exifDateFormatter.string(from: date),
],
]
if let coordinate {
properties[kCGImagePropertyGPSDictionary] = gpsProperties(for: coordinate, date: date)
}
CGImageDestinationAddImage(destination, cgImage, properties as CFDictionary)
guard CGImageDestinationFinalize(destination) else {
return image.jpegData(compressionQuality: compressionQuality)
}
return output as Data
}
private static func gpsProperties(for coordinate: CLLocationCoordinate2D, date: Date) -> [CFString: Any] {
[
kCGImagePropertyGPSLatitude: abs(coordinate.latitude),
kCGImagePropertyGPSLatitudeRef: coordinate.latitude >= 0 ? "N" : "S",
kCGImagePropertyGPSLongitude: abs(coordinate.longitude),
kCGImagePropertyGPSLongitudeRef: coordinate.longitude >= 0 ? "E" : "W",
kCGImagePropertyGPSDateStamp: Self.gpsDateFormatter.string(from: date),
kCGImagePropertyGPSTimeStamp: Self.gpsTimeFormatter.string(from: date),
]
}
/// EXIF-Zeitstempel in lokaler Zeit („yyyy:MM:dd HH:mm:ss").
private static let exifDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
return formatter
}()
/// GPS-Datum in UTC („yyyy:MM:dd").
private static let gpsDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "yyyy:MM:dd"
return formatter
}()
/// GPS-Uhrzeit in UTC („HH:mm:ss").
private static let gpsTimeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "HH:mm:ss"
return formatter
}()
}
private extension CGImagePropertyOrientation {
init(_ orientation: UIImage.Orientation) {
switch orientation {
case .up: self = .up
case .down: self = .down
case .left: self = .left
case .right: self = .right
case .upMirrored: self = .upMirrored
case .downMirrored: self = .downMirrored
case .leftMirrored: self = .leftMirrored
case .rightMirrored: self = .rightMirrored
@unknown default: self = .up
}
}
}