因为博主所从事的工作,需要对一些点位进行拍照,然后将这些点位输入到谷歌地球中留作记录,如果照片少的情况下还可以逐一输入,但是有时拍摄的照片很多,再一张一张输入就变的很麻烦,所以就用AI编写了这个软件-图片GPS经纬度提取工具,功能很简单就是从拍摄的照片中快速提取经纬度,方便快速导入谷歌地球中。
使用说明
1、首先将照片导入到电脑中,不要用微信,微信会将GPS数据给抹除,推荐使用QQ传送文件或者数据线。
2、选择照片文件夹,然后点击「开始提取GPS」按钮即可。
工具会自动在当前目录生成一个CSV文件,里面包含了提取的经纬度。
本人因为需要导入到谷歌地球中,所以加入了「一键生成KML」功能。
谷歌地球直接导入CSV
如果你也有导入谷歌地球的需要,可以直接导入KML文件,当然生成的CSV文件也可以导入,步骤如下:
1、打开谷歌地球→ 点击顶部 文件 → 导入,文件类型选 CSV,选中你要导入的csv文件。
2、导入向导设置:
分隔符:逗号
字段映射:
纬度 → 选 纬度 列
经度 → 选 经度 列
名称 → 选 图片文件名 列(方便识别)
3、点完成,所有 GPS 点位一键显示在地球中。
进制问题:
这里提取的经纬度转换成了十进制,可以通过复制粘贴的方式快速搜索查找点位,如果以原始的度分秒形式搜索会出现问题。
你可以自行将十进制转换为度分秒的格式,不懂就问AI。
下面提供提取度分秒形式的原代码,需要的自取。
- import os
- import csv
- import threading
- from PIL import Image
- from PIL.ExifTags import TAGS, GPSTAGS
- import ttkbootstrap as ttk
- from ttkbootstrap.constants import *
- from tkinter import scrolledtext
- from tkinter import filedialog, messagebox
- # ====================== EXIF与GPS原始数据提取(不转十进制) ======================
- def get_exif_data(image_path):
- """提取图片EXIF元数据,返回原始信息"""
- exif_data = {}
- try:
- with Image.open(image_path) as img:
- exif_info = img._getexif()
- if exif_info is None:
- return exif_data
- for tag_id, value in exif_info.items():
- tag = TAGS.get(tag_id, tag_id)
- if tag == "GPSInfo":
- gps_data = {}
- for gps_tag_id, gps_value in value.items():
- gps_tag = GPSTAGS.get(gps_tag_id, gps_tag_id)
- gps_data[gps_tag] = gps_value
- exif_data[tag] = gps_data
- else:
- exif_data[tag] = value
- except Exception:
- pass
- return exif_data
- def get_original_gps(exif_data):
- """直接提取原始GPS度分秒,不做十进制转换"""
- gps_info = exif_data.get("GPSInfo", {})
- if not gps_info:
- return None, None, None, None
- lat_dms = gps_info.get("GPSLatitude")
- lat_ref = gps_info.get("GPSLatitudeRef")
- lon_dms = gps_info.get("GPSLongitude")
- lon_ref = gps_info.get("GPSLongitudeRef")
- return lat_dms, lat_ref, lon_dms, lon_ref
- def format_dms(dms_data):
- """格式化原始度分秒为可读字符串"""
- if not dms_data or not isinstance(dms_data, (tuple, list)):
- return "无"
- try:
- degrees = f"{dms_data[0][0]}/{dms_data[0][1]}" if isinstance(dms_data[0], tuple) else dms_data[0]
- minutes = f"{dms_data[1][0]}/{dms_data[1][1]}" if isinstance(dms_data[1], tuple) else dms_data[1]
- seconds = f"{dms_data[2][0]}/{dms_data[2][1]}" if isinstance(dms_data[2], tuple) else dms_data[2]
- return f"{degrees}°{minutes}'{seconds}\""
- except:
- return "格式错误"
- # ====================== 可视化GUI主程序 ======================
- class GPSExtractorGUI:
- def __init__(self, root):
- self.root = root
- self.root.title("图片GPS原始数据提取工具")
- self.root.geometry("750x550")
- self.root.resizable(True, True)
- self.folder_path = ttk.StringVar()
- # 创建界面组件
- self.create_frame_select()
- self.create_frame_log()
- self.create_frame_btn()
- def create_frame_select(self):
- """文件夹选择框架"""
- frame = ttk.LabelFrame(self.root, text="文件夹选择")
- # 修复:直接使用 X/LEFT 常量,不加 ttk. 前缀
- frame.pack(fill=X, padx=20, pady=10)
- ttk.Label(frame, text="图片目录:").pack(side=LEFT, padx=10, pady=8)
- ttk.Entry(frame, textvariable=self.folder_path, width=50).pack(side=LEFT, padx=5, pady=8, fill=X, expand=YES)
- ttk.Button(frame, text="浏览选择", command=self.select_folder, bootstyle=PRIMARY).pack(side=LEFT, padx=10, pady=8)
- def create_frame_log(self):
- """日志显示框架"""
- frame = ttk.LabelFrame(self.root, text="提取日志")
- frame.pack(fill=BOTH, expand=YES, padx=20, pady=10)
- # 使用原生滚动文本框,全版本兼容
- self.log_text = scrolledtext.ScrolledText(frame, height=20, font=("微软雅黑", 9))
- self.log_text.pack(fill=BOTH, expand=YES, padx=8, pady=8)
- def create_frame_btn(self):
- """操作按钮框架"""
- frame = ttk.Frame(self.root)
- frame.pack(fill=X, padx=20, pady=10)
- self.start_btn = ttk.Button(
- frame, text="开始提取GPS原始数据", command=self.start_extract_thread, bootstyle=SUCCESS
- )
- self.start_btn.pack(side=RIGHT, padx=5)
- def select_folder(self):
- """选择图片文件夹(支持中文)"""
- path = filedialog.askdirectory(title="选择图片文件夹")
- if path:
- self.folder_path.set(path)
- def log(self, msg):
- """实时输出日志"""
- self.log_text.insert(END, msg + "\n")
- self.log_text.see(END)
- self.root.update_idletasks()
- def save_to_csv(self, results):
- """保存结果到CSV"""
- csv_path = os.path.join(os.getcwd(), "图片GPS原始数据.csv")
- headers = ["图片文件名", "文件完整路径", "纬度(DMS)", "纬度方向", "经度(DMS)", "经度方向", "状态"]
- with open(csv_path, "w", newline="", encoding="utf-8-sig") as f:
- writer = csv.writer(f)
- writer.writerow(headers)
- writer.writerows(results)
- self.log(f"\n✅ 结果已保存至:{csv_path}")
- messagebox.showinfo("完成", f"提取完成!\n结果已保存到:{csv_path}")
- def batch_extract(self):
- """批量提取核心逻辑"""
- folder = self.folder_path.get().strip()
- if not folder or not os.path.isdir(folder):
- messagebox.showerror("错误", "请选择有效的图片文件夹!")
- self.start_btn.config(state=NORMAL)
- return
- # 支持的图片格式
- IMG_FORMATS = (".jpg", ".jpeg", ".JPG", ".JPEG", ".png", ".PNG")
- results = []
- self.log("===== 开始批量提取原始GPS数据 =====")
- self.log(f"扫描目录:{folder}\n")
- count = 0
- for root_dir, _, files in os.walk(folder):
- for file in files:
- if file.endswith(IMG_FORMATS):
- count += 1
- file_path = os.path.join(root_dir, file)
- exif = get_exif_data(file_path)
- lat_dms, lat_ref, lon_dms, lon_ref = get_original_gps(exif)
- lat_str = format_dms(lat_dms)
- lon_str = format_dms(lon_dms)
- lat_ref = lat_ref if lat_ref else "无"
- lon_ref = lon_ref if lon_ref else "无"
- if lat_dms and lon_dms:
- status = "提取成功"
- self.log(f"✅ {file} | 纬度:{lat_str}{lat_ref} | 经度:{lon_str}{lon_ref}")
- else:
- status = "无GPS信息"
- self.log(f"❌ {file} | 未找到GPS数据")
- results.append([file, file_path, lat_str, lat_ref, lon_str, lon_ref, status])
- self.log(f"\n===== 提取完成 =====")
- self.log(f"共处理图片:{count} 张")
- self.save_to_csv(results)
- self.start_btn.config(state=NORMAL)
- def start_extract_thread(self):
- """子线程运行,界面不卡顿"""
- self.start_btn.config(state=DISABLED)
- self.log_text.delete(1.0, END)
- thread = threading.Thread(target=self.batch_extract, daemon=True)
- thread.start()
- # ====================== 启动程序 ======================
- if __name__ == "__main__":
- app = ttk.Window(themename="cosmo")
- gui = GPSExtractorGUI(app)
- app.mainloop()






