LAsset Location Asset - kirseia/study GitHub Wiki
import AVFoundation
import CoreLocation
import Photos
class LAsset {
static let avassetDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return formatter
}()
static let gpsExifDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
return formatter
}()
// 한 번에 한 개만 요청 가능
static let geocoder = CLGeocoder()
var location: CLLocation?
var createdDate: Date?
var modifiedDate: Date?
var date: Date {
return createdDate ?? modifiedDate ?? Date(timeIntervalSince1970: 0)
}
var country: String?
var city: String?
var geocodingDone: Bool = false
var isVideo: Bool = false
var item: Any // url, PHAsset, AVAsset
init?(item: Any) {
self.item = item
var result = true
switch item {
case let url as URL:
result = extractInfo(from: url)
case let asset as AVAsset:
result = extractInfo(from: asset)
case let phAsset as PHAsset:
result = extractInfo(from: phAsset)
default:
return nil
}
if result == false || (location == nil && createdDate == nil && modifiedDate == nil) {
return nil
}
if let location = location {
LAsset.geocoder.reverseGeocodeLocation(location) { [weak self] (placeMarkList, error) in
if error == nil {
self?.geocodingDone = true
return
}
if let placeMarkList = placeMarkList, placeMarkList.count > 0 {
self?.country = placeMarkList[0].country
self?.city = placeMarkList[0].locality
print(placeMarkList[0])
}
self?.geocodingDone = true
}
}
}
// MARK: default func
func distance(from lAsset: LAsset) -> Double {
guard let aLocation = self.location, let bLocation = lAsset.location else {
return 0.0
}
return aLocation.distance(from: bLocation)
}
func distance(from location: CLLocation?) -> Double {
guard let aLocation = self.location, let bLocation = location else {
return 0.0
}
return aLocation.distance(from: bLocation)
}
// MARK: private func
private func extractInfo(from asset: AVAsset) -> Bool {
let metadataList = asset.metadata
for meta in metadataList {
guard let key = meta.commonKey else {
continue
}
switch key {
case .commonKeyLocation:
// "+37.3594+127.1048+061.813/"
if let locationString = meta.stringValue {
location = convert(from: locationString)
}
case .commonKeyCreationDate:
// "2019-01-25T19:49:47+0900"
createdDate = LAsset.avassetDateFormatter.date(from: meta.stringValue ?? "")
case .commonKeyLastModifiedDate:
// "2019-01-25T19:49:47+0900"
modifiedDate = LAsset.avassetDateFormatter.date(from: meta.stringValue ?? "")
default:
continue
}
}
isVideo = true
return true
}
private func extractInfo(from phAsset: PHAsset) -> Bool {
location = phAsset.location
createdDate = phAsset.creationDate
modifiedDate = phAsset.modificationDate
isVideo = phAsset.mediaType == .video
return true
}
private func extractInfo(from url: URL) -> Bool {
let videoExtensionList: [String] = ["mov", "mp4", "ts"]
let pathExtension = url.pathExtension.lowercased()
if videoExtensionList.contains(pathExtension) {
let asset = AVAsset(url: url)
return extractInfo(from: asset)
} else {
return extractImageInfo(from: url)
}
}
private func extractImageInfo(from url: URL) -> Bool {
// 아래는 image 처리
isVideo = false
guard let data = try? Data(contentsOf: url) else {
return false
}
guard let imageData = CGImageSourceCreateWithData(data as CFData, nil) else {
return false
}
guard let metaData = CGImageSourceCopyPropertiesAtIndex(imageData, 0, nil) else {
return false
}
// TODO: 이전에 실패했을 때 작성 시간 같은걸 따로 처리할까...
let metaDictionary = metaData as NSDictionary
if let exifDictionary = metaDictionary.object(forKey: kCGImagePropertyExifDictionary as String) as? NSDictionary {
let createdTimeValue = exifDictionary.value(forKey: kCGImagePropertyExifDateTimeOriginal as String)
if let createdTimeString = createdTimeValue as? String {
// 찍은 위치의 시간, 어디서 찍었는지 gps 가 있으면 UTC 계산이 가능. gps 없으면 불가
createdDate = LAsset.gpsExifDateFormatter.date(from: createdTimeString)
}
}
if let gpsDictionary = metaDictionary.object(forKey: kCGImagePropertyGPSDictionary as String) as? NSDictionary {
// Ref. https://github.com/devongovett/exif-reader
// Ref. https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
let latitudeOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSLatitude as String) as? NSNumber
let longitudeOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSLongitude as String) as? NSNumber
let altitudeOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSAltitude as String) as? NSNumber
let speedOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSSpeed as String) as? NSNumber
let dateStampOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSDateStamp as String) as? String
let timeStampOpt = gpsDictionary.value(forKey: kCGImagePropertyGPSTimeStamp as String) as? String
if createdDate == nil {
// exif 데이터 없을 때만 확인
if let dateStamp = dateStampOpt, let timeStamp = timeStampOpt {
// UTC 0 시간임, gps latitude / 15 해서 timezone 계산해서 맞춰야 함
createdDate = LAsset.gpsExifDateFormatter.date(from: dateStamp + " " + timeStamp)
}
}
if let latitude = latitudeOpt, let longitude = longitudeOpt {
let locationCoordinate = CLLocationCoordinate2D(latitude: latitude.doubleValue, longitude: longitude.doubleValue)
let defaultDate = Date(timeIntervalSince1970: 0)
location = CLLocation(coordinate: locationCoordinate, altitude: altitudeOpt?.doubleValue ?? 0.0,
horizontalAccuracy: 0.0, verticalAccuracy: 0.0,
course: 0.0, speed: speedOpt?.doubleValue ?? 0.0,
timestamp: createdDate ?? defaultDate)
}
}
return true
}
private func convert(from locationString: String) -> CLLocation? {
if locationString.count < 17 {
return nil
}
guard let latitude = Double(locationString.substring(from: 0, to: 8)) else {
return nil
}
guard let longitude = Double(locationString.substring(from: 8, to: 17)) else {
return nil
}
return CLLocation(latitude: latitude, longitude: longitude)
}
}
extension String {
func substring(from: Int, to: Int) -> String {
let start = index(startIndex, offsetBy: from)
let end = index(start, offsetBy: to - from)
return String(self[start ..< end])
}
}
// GPS dictionary
/*
▿ 15 elements
▿ 0 : 2 elements
- key : ImgDirection
- value : 58.91798400595128
▿ 1 : 2 elements
- key : LatitudeRef
- value : N
▿ 2 : 2 elements
- key : HPositioningError
- value : 65
▿ 3 : 2 elements
- key : DestBearingRef
- value : M
▿ 4 : 2 elements
- key : Latitude
- value : 34.84813
▿ 5 : 2 elements
- key : Speed
- value : 0
▿ 6 : 2 elements
- key : TimeStamp
- value : 05:43:09 // 현재 타임 + 9 하면 14시 = 오후 2시 43분.
▿ 7 : 2 elements
- key : LongitudeRef
- value : E
▿ 8 : 2 elements
- key : AltitudeRef
- value : 0
▿ 9 : 2 elements
- key : Longitude
- value : 127.34468
▿ 10 : 2 elements
- key : Altitude
- value : 11.90512752625541
▿ 11 : 2 elements
- key : DateStamp
- value : 2018:12:15
▿ 12 : 2 elements
- key : DestBearing
- value : 58.91798400595128
▿ 13 : 2 elements
- key : ImgDirectionRef
- value : M
▿ 14 : 2 elements
- key : SpeedRef
- value : K
*/
// TIFF dictionary
/*
▿ 13 elements
▿ 0 : 2 elements
- key : {TIFF}
▿ value : 10 elements
▿ 0 : 2 elements
- key : ResolutionUnit
- value : 2
▿ 1 : 2 elements
- key : Software
- value : 12.1
▿ 2 : 2 elements
- key : TileLength
- value : 512
▿ 3 : 2 elements
- key : DateTime
- value : 2018:12:15 14:43:15
▿ 4 : 2 elements
- key : XResolution
- value : 72
▿ 5 : 2 elements
- key : TileWidth
- value : 512
▿ 6 : 2 elements
- key : Orientation
- value : 3
▿ 7 : 2 elements
- key : YResolution
- value : 72
▿ 8 : 2 elements
- key : Model
- value : iPhone 8
▿ 9 : 2 elements
- key : Make
- value : Apple
▿ 1 : 2 elements
- key : Orientation
- value : 3
▿ 2 : 2 elements
- key : PixelWidth
- value : 4032
▿ 3 : 2 elements
- key : PixelHeight
- value : 3024
▿ 4 : 2 elements
- key : {Exif}
▿ value : 31 elements
▿ 0 : 2 elements
- key : DateTimeOriginal
- value : 2018:12:15 14:43:15
▿ 1 : 2 elements
- key : MeteringMode
- value : 5
▿ 2 : 2 elements
- key : ComponentsConfiguration
▿ value : 4 elements
- 0 : 1
- 1 : 2
- 2 : 3
- 3 : 0
▿ 3 : 2 elements
- key : BrightnessValue
- value : 3.201931423611111
▿ 4 : 2 elements
- key : FocalLenIn35mmFilm
- value : 28
▿ 5 : 2 elements
- key : LensMake
- value : Apple
▿ 6 : 2 elements
- key : FNumber
- value : 1.8
▿ 7 : 2 elements
- key : FocalLength
- value : 3.99
▿ 8 : 2 elements
- key : ShutterSpeedValue
- value : 4.58536651192918
▿ 9 : 2 elements
- key : SubjectArea
▿ value : 4 elements
- 0 : 2015
- 1 : 1511
- 2 : 2217
- 3 : 1330
▿ 10 : 2 elements
- key : ApertureValue
- value : 1.69599381283836
▿ 11 : 2 elements
- key : SceneType
- value : 1
▿ 12 : 2 elements
- key : SceneCaptureType
- value : 0
▿ 13 : 2 elements
- key : ColorSpace
- value : 65535
▿ 14 : 2 elements
- key : LensSpecification
▿ value : 4 elements
- 0 : 3.99
- 1 : 3.99
- 2 : 1.8
- 3 : 1.8
▿ 15 : 2 elements
- key : PixelYDimension
- value : 3024
▿ 16 : 2 elements
- key : WhiteBalance
- value : 0
▿ 17 : 2 elements
- key : FlashPixVersion
▿ value : 2 elements
- 0 : 1
- 1 : 0
▿ 18 : 2 elements
- key : DateTimeDigitized
- value : 2018:12:15 14:43:15
▿ 19 : 2 elements
- key : ISOSpeedRatings
▿ value : 1 element
- 0 : 32
▿ 20 : 2 elements
- key : ExposureMode
- value : 0
▿ 21 : 2 elements
- key : ExifVersion
▿ value : 3 elements
- 0 : 2
- 1 : 2
- 2 : 1
▿ 22 : 2 elements
- key : PixelXDimension
- value : 4032
▿ 23 : 2 elements
- key : LensModel
- value : iPhone 8 back camera 3.99mm f/1.8
▿ 24 : 2 elements
- key : ExposureProgram
- value : 2
▿ 25 : 2 elements
- key : ExposureTime
- value : 0.04166666666666666
▿ 26 : 2 elements
- key : SubsecTimeDigitized
- value : 632
▿ 27 : 2 elements
- key : Flash
- value : 16
▿ 28 : 2 elements
- key : SubsecTimeOriginal
- value : 632
▿ 29 : 2 elements
- key : SensingMethod
- value : 2
▿ 30 : 2 elements
- key : ExposureBiasValue
- value : 0
▿ 5 : 2 elements
- key : {GPS}
▿ value : 15 elements
▿ 0 : 2 elements
- key : ImgDirection
- value : 58.91798400595128
▿ 1 : 2 elements
- key : LatitudeRef
- value : N
▿ 2 : 2 elements
- key : HPositioningError
- value : 65
▿ 3 : 2 elements
- key : DestBearingRef
- value : M
▿ 4 : 2 elements
- key : Latitude
- value : 34.84813
▿ 5 : 2 elements
- key : Speed
- value : 0
▿ 6 : 2 elements
- key : TimeStamp
- value : 05:43:09
▿ 7 : 2 elements
- key : LongitudeRef
- value : E
▿ 8 : 2 elements
- key : AltitudeRef
- value : 0
▿ 9 : 2 elements
- key : Longitude
- value : 127.34468
▿ 10 : 2 elements
- key : Altitude
- value : 11.90512752625541
▿ 11 : 2 elements
- key : DateStamp
- value : 2018:12:15
▿ 12 : 2 elements
- key : DestBearing
- value : 58.91798400595128
▿ 13 : 2 elements
- key : ImgDirectionRef
- value : M
▿ 14 : 2 elements
- key : SpeedRef
- value : K
▿ 6 : 2 elements
- key : PrimaryImage
- value : 1
▿ 7 : 2 elements
- key : ProfileName
- value : Display P3
▿ 8 : 2 elements
- key : DPIWidth
- value : 72
▿ 9 : 2 elements
- key : DPIHeight
- value : 72
▿ 10 : 2 elements
- key : ColorModel
- value : RGB
▿ 11 : 2 elements
- key : {MakerApple}
▿ value : 22 elements
▿ 0 : 2 elements
- key : 25
- value : 0
▿ 1 : 2 elements
- key : 33
- value : 0
▿ 2 : 2 elements
- key : 26
- value : q825s
▿ 3 : 2 elements
- key : 12
▿ value : 2 elements
- 0 : 2.460938
- 1 : 5.484375
▿ 4 : 2 elements
- key : 1
- value : 10
▿ 5 : 2 elements
- key : 20
- value : 5
▿ 6 : 2 elements
- key : 2
- value : <9d01db00 05016501 73007200 9500c400 61009000 d0009300 3f003700 3d008f00 4a01f300 1301b800 4b003900 1e008500 8800b300 82007600 40003b00 54007300 30016e01 d900e000 fe004d00 18006600 6700bb00 4a017b00 46001900 34007a00 8801d301 d8004a01 a700ff00 52005300 ad002c01 de01f100 2f003800 7b007c00 ce00ed01 7c01e100 ac004e01 7e004900 79003101 c701ff00 4b004900 d1008600 98005b01 bd00b600 c400eb00 99007800 8e00a700 4c01dc00 a7006000 b7008a00 5500fd00 1a01fd00 d400d500 dc005e00 48005300 77004100 5500cf00 f600ab00 34007800 4f01f100 2401d100 e4004d00 9c006100 3b004600 45009500 6c00ef00 46007c00 f4001701 5801a600 dc005300 77005e00 29003900 4600bc00 e5009e01 4e01f200 ce008601 e400ca00 dc005300 6f004b00 3c002e00 8700b600 42010401 43013b01 9e017500 3800a500 d8007b00 42004400 9100f400 d8000901 fc007100 dc007801 e501e400 46001801 97001701 5201da00 1f01d400 c1002600 e0002a01 fb007001 a501be00 9800df00 8200aa01 f0001a01 03010901 af00d400 a400a700 ac006200 68008600 a600ed00 ae00e200 fd00b300 06012c01 7f009100 9a009000 6f003400 5e008d00 8f009000 8b007900 93006a00 64004e00 82009600 a1002201 5b003d00 6f006a00 6a010e01 72006200 67004d00 1e002c00 6b008700 80005d01>
▿ 7 : 2 elements
- key : 13
- value : 5
▿ 8 : 2 elements
- key : 3
▿ value : 4 elements
▿ 0 : 2 elements
- key : flags
- value : 1
▿ 1 : 2 elements
- key : value
- value : 199027025838083
▿ 2 : 2 elements
- key : timescale
- value : 1000000000
▿ 3 : 2 elements
- key : epoch
- value : 0
▿ 9 : 2 elements
- key : 14
- value : 0
▿ 10 : 2 elements
- key : 4
- value : 1
▿ 11 : 2 elements
- key : 37
- value : 0
▿ 12 : 2 elements
- key : 5
- value : 180
▿ 13 : 2 elements
- key : 15
- value : 2
▿ 14 : 2 elements
- key : 6
- value : 172
▿ 15 : 2 elements
- key : 38
- value : 0
▿ 16 : 2 elements
- key : 23
- value : 8192
▿ 17 : 2 elements
- key : 16
- value : 1
▿ 18 : 2 elements
- key : 7
- value : 1
▿ 19 : 2 elements
- key : 39
- value : 0
▿ 20 : 2 elements
- key : 31
- value : 0
▿ 21 : 2 elements
- key : 8
▿ value : 3 elements
- 0 : 0.09030848
- 1 : 0.009026065
- 2 : -1.017984
▿ 12 : 2 elements
- key : Depth
- value : 8
*/