用 Python 做个 GPS 照片搜索器!
Original...About 2 min
用 Python 做个 GPS 照片搜索器!
需求
给点 GPS 经纬度,查找给点目录下地理坐标接近的照片。
GPS 解析
现在大部分手机拍照会记录 GPS 信息(如果启用功能),该信息会被记录到 EXIF 元数据中。在 Python 可以使用 exifread
包来读取 EXIF 信息。一个简单的示例:
with open(path, 'rb') as f:
exif = exifread.process_file(f)
这里 exif
是一个 dict
,value 类型为一个聚合体,包含该属性的 tag、offset 等。原始值需要通过 exif[...].printble
获取。
GPS 的主要属性储存在以下 4 个域中:
'GPS GPSLatitude'
:纬度。'GPS GPSLatitudeRef'
:纬度符号,为'N'
或'S'
。'GPS GPSLongitude'
:经度。'GPS GPSLongitudeRef'
:经度符号,为'E'
或'W'
。
其中经纬度的格式为 [D, M, S1/S2]
,即度分秒,表示为 。例如:'[118, 46, 114514/125000]'
。通常度和分都是整数,秒是分数,但可能有例外,度和分也是分数形式。
当然也有高度 GPSAltitude
,不过一般手机不会记录,这里直接忽略。
最简单的提取方式就是正则表达式了。
import re
def parse_gps(gps: Any):
M = re.match(r"\[(\d+)(/(\d+))?, (\d+)(/(\d+))?, (\d+)(/(\d+))?\]", gps.printable)
assert M
d = int(M[1]) / (int(M[3] or 1))
m = int(M[4]) / (int(M[6] or 1))
s = int(M[7]) / (int(M[9] or 1))
return d + m / 60 + s / 3600
这样识别 GPS 的代码如下:
import exifread
def read_gps(path: Any):
try:
with open(path, 'rb') as f:
exif = exifread.process_file(f)
except:
return None
if 'GPS GPSLatitudeRef' not in exif:
return None
lat = parse_gps(exif['GPS GPSLatitude']) * (1 if exif['GPS GPSLatitudeRef'].printable == 'N' else -1)
lon = parse_gps(exif['GPS GPSLongitude']) * (1 if exif['GPS GPSLongitudeRef'].printable == 'E' else -1)
return (lat, lon)
经纬度距离
最简单的方法就是使用 geopy
包了,准确度也有保障。
from geopy.distance import geodesic
dist = geodesic((lat1, lon1), (lat2, lon2)) # Distance对象,表示两个坐标间的距离
dist.m # 距离数值大小,单位为m
dist.km # 距离数值大小,单位为km
完成搜索器
这步就很简单了,遍历目录读照片的 GPS,找距离上邻近的就行。
from more_itertools import ilen
from tqdm import tqdm
def find_neighbors(dir: Any,
anchor: tuple[float, float],
distance: float = 10):
for f in tqdm(Path(dir).iterdir(), total=ilen(Path(dir).iterdir())):
if gps := read_gps(f):
if geodesic(gps, anchor).m < distance:
print(f)
find_neighbors("path/to/photos", (32.0, 120.0))
我这里处理速度大概是 350it/s。人工看了一下结果,有些照片的拍摄地距离 anchor 似乎超过了限制,可能是 GPS 记录问题?
可以考虑在 jupyter 里显示满足条件的照片。