位置情報付き写真をGoogle Earthに表示する方法

📸 位置情報付き写真をGoogle Earthに表示する方法

🔍 概要

スマートフォンや位置情報付きカメラで撮影した写真(JPEG/HEICなど)を、フォルダを選択するだけGoogle Earth(KMZ)に表示できるツールをご紹介します。


🛠 手順

Step 1|写真をフォルダに入れる

撮影した写真を1つのフォルダにまとめておきます。JPEG, HEIC, HEIF形式に対応しています。

Step 2|PythonスクリプトでKMZ作成

以下のPythonスクリプトを実行すると、フォルダを選ぶだけで、Google Earthに表示できるKMZが自動作成されます。

✅ 必要ライブラリ(初回のみ):

pip install pillow pillow-heif simplekml tqdm exifread

✅ スクリプトファイル例: photos_to_kmz_gui_v5.py

💻 Pythonスクリプト全文

#!/usr/bin/env python3
"""
photos_to_kmz_gui_v5.py
---------------------------------------
* ExifRead → Pillow fallback で GPS を抽出
* HEIC / HEIF 対応 (pillow-heif)
* リサイズ(長辺 maxsize px、デフォルト 1600)
* tqdm 進捗バー付き
依存:
    pip install pillow pillow-heif simplekml tqdm exifread
"""

import sys, zipfile
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox

import exifread
from PIL import Image, ExifTags
try:
    import pillow_heif
    pillow_heif.register_heif_opener()
except ImportError:
    pass

import simplekml
from tqdm import tqdm

# ---------- GPS extraction ----------
def gps_from_exifread(path):
    try:
        with open(path, 'rb') as f:
            tags = exifread.process_file(f, details=False, stop_tag='GPS GPSLongitude')
        if 'GPS GPSLatitude' not in tags or 'GPS GPSLongitude' not in tags:
            return None, None
        def conv(tag):
            vals = tag.values
            d = vals[0].num / vals[0].den
            m = vals[1].num / vals[1].den
            s = vals[2].num / vals[2].den
            return d + m/60 + s/3600
        lat = conv(tags['GPS GPSLatitude'])
        lon = conv(tags['GPS GPSLongitude'])
        if tags.get('GPS GPSLatitudeRef', 'N').values != 'N':
            lat = -lat
        if tags.get('GPS GPSLongitudeRef', 'E').values != 'E':
            lon = -lon
        return lat, lon
    except Exception:
        return None, None

def gps_from_pillow(path):
    try:
        with Image.open(path) as im:
            exif = im._getexif()
        if not exif:
            return None, None
        gps = {}
        for k, v in exif.items():
            if ExifTags.TAGS.get(k) == 'GPSInfo':
                gps = {ExifTags.GPSTAGS.get(t, t): v[t] for t in v}
                break
        if not gps:
            return None, None
        def r(x):
            return x.numerator / x.denominator if hasattr(x,'numerator') else x[0] / x[1]
        def dms(d, ref):
            return (-1 if ref in ('S','W') else 1) * (r(d[0]) + r(d[1])/60 + r(d[2])/3600)
        lat = dms(gps['GPSLatitude'], gps.get('GPSLatitudeRef','N'))
        lon = dms(gps['GPSLongitude'], gps.get('GPSLongitudeRef','E'))
        return lat, lon
    except Exception:
        return None, None

def get_gps(path):
    lat, lon = gps_from_exifread(path)
    if lat is None:
        lat, lon = gps_from_pillow(path)
    return lat, lon

# ---------- GUI ----------
def select_folder():
    root = tk.Tk()
    root.withdraw()
    folder = filedialog.askdirectory(title='写真フォルダを選択してください')
    root.destroy()
    return Path(folder) if folder else None

# ---------- Resize ----------
def save_resized(src: Path, dst: Path, maxsize=1600, quality=80):
    try:
        with Image.open(src) as img:
            img.thumbnail((maxsize,maxsize), Image.LANCZOS)
            if img.mode in ('RGBA','P'):
                img = img.convert('RGB')
            img.save(dst, 'JPEG', quality=quality, optimize=True)
        return True
    except Exception as e:
        print('[WARN] resize failed', src, e)
        return False

# ---------- Main ----------
def main():
    import argparse, shutil
    p = argparse.ArgumentParser(description='Geotagged photos → KMZ v5')
    p.add_argument('folder', nargs='?', help='写真フォルダ')
    p.add_argument('-o','--output', help='KMZ 出力パス')
    p.add_argument('--maxsize', type=int, default=1600, help='長辺px上限')
    p.add_argument('--quality', type=int, default=80, help='JPEG品質')
    args = p.parse_args()

    folder = Path(args.folder).resolve() if args.folder else select_folder()
    if not folder or not folder.is_dir():
        print('フォルダが選択されませんでした'); sys.exit(0)
    out = Path(args.output).resolve() if args.output else folder.with_suffix('.kmz')

    imgs = [p for p in folder.rglob('*') if p.suffix.lower() in ('.jpg','.jpeg','.heic','.heif')]
    if not imgs:
        messagebox.showinfo('結果','画像ファイルが見つかりませんでした'); sys.exit(0)

    tmp = Path('_kmz_tmp'); files = tmp/'files'; files.mkdir(parents=True, exist_ok=True)
    kml = simplekml.Kml(); added = 0

    for img in tqdm(imgs, desc='Processing', unit='img'):
        lat, lon = get_gps(img)
        if lat is None:
            continue
        dst = files / (img.stem + '.jpg')  # すべて JPG として保存
        if not dst.exists():
            save_resized(img, dst, args.maxsize, args.quality)
        p = kml.newpoint(name=img.name, coords=[(lon, lat)])
        p.description = f"<img src='files/{dst.name}' width='600'/>"
        added += 1

    if added == 0:
        messagebox.showinfo('結果','ジオタグ付き画像が見つかりませんでした'); sys.exit(0)

    tmp_kml = tmp/'doc.kml'; kml.save(str(tmp_kml))
    with zipfile.ZipFile(out,'w', zipfile.ZIP_DEFLATED) as z:
        z.write(tmp_kml,'doc.kml')
        for f in files.iterdir():
            z.write(f, f'files/{f.name}')
    shutil.rmtree(tmp, ignore_errors=True)
    messagebox.showinfo('完了', f'{out} を作成しました\\n登録ポイント: {added} 枚')

if __name__ == '__main__':
    main()

📦 補足情報

  • 対応画像:.jpg / .jpeg / .heic / .heif
  • GPSが埋め込まれていない画像はスキップされます
  • KMZには画像がリサイズされて埋め込まれます(デフォルト:長辺1600px)

現場写真の地理情報可視化に、ぜひご活用ください。

コメント

このブログの人気の投稿

qgisからKML出力すると、ラベル、場所の属性が表示されない問題の対処法

ワールドファイル付きラスタ(Raster image with world file)からKMZ作成