本文想要实现的功能很久之前就有想法了,但网上的资料太少,就没花太多时间研究这个,一直搁置到现在。前两天突然发现有个大佬实现了我当初预期的功能,尝试了他的方法后觉得还可以接着改进,于是就有了这篇文章。

我很喜欢旅行,旅行过程中自然会有很多照片,如何管理照片就是很大的问题。之前尝试很多方法后,我认为将照片与地图结合会是一种很直观方法。我用的小米手机有类似的功能,但要想使用此功能就只能使用小米手机,绑定死了硬件平台,不够灵活。Obsidian Leaflet 是 Obsidian 的一个插件,可以将在线地图或图片引入 ob 中,设置标记点,并将标记点与 md 文件或照片绑定。插件默认用的是 OpenStreetMap,国内用的话加载会比较慢,细节信息也会缺失,所以最好还是用国内地图。具体设置请看大佬的文章,我就不再赘述了。

本文改进的地方如下:

  • 批量创建每张照片对应的标记点文件
  • 自动创建旅行记录文件和文件夹
  • 采用取平均的方式自动设置地图中心点的经纬度


使用本文脚本之前需要做的工作

  • python 相关
    • 安装 Python3.6 及以上版本
    • 安装如下 Python 模块,安装命令 pip install 模块名
      • exifread
      • requests
  • obsidian 相关
    • 安装 obsidian leaflet 插件
    • 按照大佬文章中的方法修改缩放限制
  • 脚本相关
    • 申请一个高德地图的 KEY,方法见大佬的文章
    • 将脚本中的路径和 KEY 替换成你自己的路径和 KEY
  • 照片相关
    • 拍照时打开 GPS 定位
    • 手机向电脑发送照片文件时关闭默认抹除照片位置的设置
    • 将照片与脚本放在同一目录

完成上述设置后,执行本文的脚本就可以生成旅行记录的完整文件夹,将文件夹复制到你的 ob 库的指定路径下,将照片移动到 ob 库中即可。

脚本的完整代码如下:

# coding=utf-8
import exifread
import json
import urllib.request
import requests
import os

gaodeconvert_enable = 1
gaodeapi_key = "换成你的高德KEY"
gaodeapi_sitehead = "https://restapi.amap.com/v3/assistant/coordinate/convert?locations="

global_lat = 0
global_long = 0
global_imgNum = 0

#获取经纬度
def getLatOrLng(refKey, tudeKey, tags):
    if refKey not in tags:
        return None
    ref=tags[refKey].printable
    LatOrLng=tags[tudeKey].printable[1:-1].replace(" ","").replace("/",",").split(",")
    LatOrLng=float(LatOrLng[0])+float(LatOrLng[1])/60+float(LatOrLng[2])/float(LatOrLng[3])/3600
    if refKey == 'GPS GPSLatitudeRef' and tags[refKey].printable != "N":
        LatOrLng=LatOrLng*(-1)
    if refKey == 'GPS GPSLongitudeRef' and tags[refKey].printable != "E":
        LatOrLng=LatOrLng*(-1)
    return LatOrLng

#为照片创建md文件
def saveAsMD(imageName,travelFolder_path):
    global global_lat
    global global_long
    f = open(imageName, 'rb')
    tags = exifread.process_file(f)
    lat = getLatOrLng('GPS GPSLatitudeRef','GPS GPSLatitude',tags)
    lng = getLatOrLng('GPS GPSLongitudeRef','GPS GPSLongitude',tags)
    if lat==None or lng==None:
        print("no gps")
        exit()
    gaodeapi_site = gaodeapi_sitehead + str(lng)+","+str(lat)+"&coordsys=gps&output=json&key="+gaodeapi_key
    response = requests.get(gaodeapi_site)
    locations = response.json()['locations']
    locations_list = locations.split(',')
    gn=locations_list[1]+','+locations_list[0]
    global_lat = global_lat + float(locations_list[1])
    global_long = global_long + float(locations_list[0])
    output_file_name = "{:.10f},{:.10f}.md".format(lat, lng)
    output_file_path = os.path.join(travelFolder_path,"markers",output_file_name)
    with open(output_file_path, "w", encoding="utf-8") as markdown_file:
        markdown_file.write("---\n") 
        markdown_file.write("mapmarker: default\n") 
        markdown_file.write("date: {}\n".format(tags['EXIF DateTimeOriginal'])) 
        markdown_file.write("device: {} {}\n".format(str(tags['Image Make']), str(tags['Image Model'])))
        markdown_file.write("place: \n")
        markdown_file.write('gps: [{},{}]\n'.format(lat, lng))
        markdown_file.write("gn: [{}]\n".format(gn)) 
        if gaodeconvert_enable == 1:
            markdown_file.write("location: [{}]\n".format(gn)) 
        else:
            markdown_file.write("location: [{},{}]\n".format(lat, lng)) 
        markdown_file.write("---\n") 
        markdown_file.write("![["+imageName+']]\n') 

#为每个照片创建标记点文件           
def CreateMarkers(travelFolder_path):
    global global_imgNum
    jpg_files = []
    for file in os.listdir('.'):
        if file.endswith('.jpg'):
            jpg_files.append(file)
            global_imgNum = global_imgNum + 1

    for file in jpg_files:
        saveAsMD(file,travelFolder_path)

print("执行本脚本前务必保证所有照片都在脚本所在目录下,且所有照片都有位置信息\n")
print("执行完成后,将照片和生成的文件夹移入ob库中即可\n")     
travel_name = input("输入本次旅行的名称\n")
date_id = input("输入旅行日期(YYYYMMDD)\n")

os.mkdir(travel_name)
travelMD_path = os.path.join(travel_name,travel_name)

markers_path = os.path.join(travel_name,"markers")
os.mkdir(markers_path)

CreateMarkers(travel_name)

global_lat = global_lat / global_imgNum
global_long = global_long / global_imgNum

with open(travelMD_path+".md", "w", encoding="utf-8") as markdown_file:
    markdown_file.write("\n")
    markdown_file.write("```leaflet\n")
    markdown_file.write("id: {}\n".format(date_id))
    markdown_file.write("osmLayer: false\n")
    markdown_file.write("tileServer: http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}\n")
    markdown_file.write("tileSubdomains: [\"1\", \"2\", \"3\", \"4\"]\n")
    markdown_file.write(f"lat: {global_lat:.6f}\n")
    markdown_file.write(f"long: {global_long:.6f}\n")
    markdown_file.write("height: 800px\n")
    markdown_file.write("width: 100%\n")
    markdown_file.write("defaultZoom: 16\n")
    markdown_file.write("maxzoom: 18\n")
    markdown_file.write("minzoom: 1\n")
    markdown_file.write("unit: meters\n")
    markdown_file.write("scale: 1\n")
    markdown_file.write("markerFolder: 换成你的路径/{}/markers\n".format(travel_name))
    markdown_file.write("```\n")

效果如图所示:

效果图


2024-05-11更新:我将写好的脚本分享给了朋友,朋友在此脚本的基础上用pyQt做了一个GUI版的小程序,用起来更加直观方便,需要的可以通过邮件联系我:1165011707@qq.com


2024-12-03更新:我用Fyne框架重构了之前写的那个GUI版的小程序,新增了更多功能,包括编辑YAML属性、压缩图片等,详情见下面这篇 blog。

最后修改:2024 年 12 月 03 日
如果觉得我的文章对你有用,请随意赞赏