画像ファイルからGPSメタデータの抽出
デジタル写真にはExif(Exchangeable Image File Format)規格に基づき、撮影機器や撮影条件、地理位置情報などが埋め込まれています。ただし、SNSへのアップロードや画像編集ソフトによる保存処理を行うと、プライバシー保護の観点からメタデータが削除または改変されるため、元の状態のファイルが必要です。
Pythonのexifreadライブラリを使用すると、バイナリモードで画像ファイルを読み込み、埋め込まれたタグ辞書を直接取得できます。取得したGPSデータは度数分秒(DMS)形式で保存されているため、十進数(Decimal Degrees)に変換する必要があります。
import exifread
from pathlib import Path
from typing import Tuple, Optional
def extract_gps_coordinates(image_path: str) -> Optional[Tuple[float, float]]:
img_file = Path(image_path)
if not img_file.is_file():
return None
with img_file.open('rb') as f_obj:
tags = exifread.process_file(f_obj)
lat_tag = tags.get('GPS GPSLatitude')
lon_tag = tags.get('GPS GPSLongitude')
lat_ref = tags.get('GPS GPSLatitudeRef')
lon_ref = tags.get('GPS GPSLongitudeRef')
if not all([lat_tag, lon_tag]):
return None
def convert_dms_to_dec(dms_val, ref_val):
d, m, s = dms_val.values
decimal = float(d) + float(m) / 60.0 + float(s) / 3600.0
ref_str = str(ref_val).strip() if ref_val else ''
if ref_str in ('S', 'W'):
decimal *= -1
return decimal
latitude = convert_dms_to_dec(lat_tag, lat_ref)
longitude = convert_dms_to_dec(lon_tag, lon_ref)
return longitude, latitude
百度地图APIを用いた逆ジオコーディング
取得した経度・緯度を住所情報に変換するには、地図サービスプロバイダのAPIを利用します。百度地图のReverse Geocoding V3エンドポイントは、GETリクエストによる呼び出し時にAK(アクセスキー)とSK(シークレットキー)を使用したsn(署名)パラメータの検証を要求します。
署名の生成プロセスは、リクエストパラメータをキーのASCII順にソートし、URLエンコードを施したクエリ文字列の末尾にSKを連結し、MD5でハッシュ化します。以下は、パラメータの自動ソートと署名計算をカプセル化した実装例です。
import requests
import hashlib
import urllib.parse
def fetch_address_from_baidu(ak: str, sk: str, lng: float, lat: float) -> str:
base_endpoint = "https://api.map.baidu.com/reverse_geocoding/v3"
req_params = {
"ak": ak,
"output": "json",
"coordtype": "wgs84ll",
"location": f"{lat},{lng}"
}
# パラメータをキー昇順でソートし、クエリ文字列を生成
encoded_query = urllib.parse.urlencode(sorted(req_params.items()))
# SN署名の計算
raw_string = encoded_query + sk
sn_value = hashlib.md5(raw_string.encode('utf-8')).hexdigest()
# 最終的なリクエストURLの構築
final_query = f"{encoded_query}&sn={sn_value}"
target_url = f"{base_endpoint}?{final_query}"
response = requests.get(target_url)
response.raise_for_status()
payload = response.json()
if payload.get("status") == 0:
return payload["result"]["formatted_address"]
else:
raise Exception(f"Geocoding failed: {payload.get('message')}")
ワークフローの統合
両方の処理を組み合わせることで、画像ファイルパスから直接住所文字列を取得するパイプラインが構築できます。エラーハンドリングを組み込み、実用的なスクリプトに仕上げる場合は以下の構造が推奨されます。
if __name__ == "__main__":
TARGET_AK = "your_api_key_here"
TARGET_SK = "your_secret_key_here"
IMAGE_SOURCE = "/path/to/photo_with_exif.jpg"
coords = extract_gps_coordinates(IMAGE_SOURCE)
if coords:
longitude, latitude = coords
try:
address = fetch_address_from_baidu(TARGET_AK, TARGET_SK, longitude, latitude)
print(f"検出された位置: {address}")
except Exception as e:
print(f"住所変換中にエラーが発生しました: {e}")
else:
print("有効なGPSデータが画像に含まれていません。")