【代码】从照片中提取GPS信息并创建Shapefile

在地理信息系统中,获取精确的地理位置数据对于各种分析至关重要。随着数码摄影的普及,越来越多的照片包含了EXIF元数据,其中就包含了拍摄地点的GPS信息。本文将介绍如何使用Python编程语言从照片中提取这些GPS信息,并将其转换为Shapefile文件,以便在GIS软件中进行进一步的分析和处理。

一、准备工作

在开始之前,需要确保已经安装了以下Python库:

  • PIL(或Pillow):用于处理图像文件。
  • pyshp:用于读写Shapefile文件。

可以使用pip来安装这些库:

pip install pillow pyshp

二、准备工作

首先,我们需要编写一个函数来从照片中提取EXIF元数据中的GPS信息。这个函数将打开照片文件,解析EXIF标签,并提取出经度和纬度信息。

from PIL import Image 
def exif(img):
    """
    从图片中返回EXIF元数据
    """
    exif_data = {}

    try:
        i = Image.open(img)  # 使用PIL库打开图片
        tags = i._getexif()  # 获取图片的EXIF标签

        for tag, value in tags.items():
            decoded = TAGS.get(tag, tag)  # 尝试从预定义的TAGS字典中获取标签的中文描述,否则使用标签ID
            exif_data[decoded] = value  # 将标签及其值存储到exif_data字典中

    except:
        pass  # 捕获所有异常并忽略,这通常不是一个好的做法,应该明确指定要捕获的异常

    return exif_data

三、处理GPS信息

由于EXIF中的GPS信息是以度、分、秒(DMS)的格式存储的,并且可能包含方向信息(东、西、南、北),我们需要编写一个函数dms2dd()来将这些信息转换为十进制度(DD)的格式。

def dms2dd(d, m, s, direction):  
    """  
    将度分秒格式转换为十进制度格式  
    """  
    sec = float((m * 60) + s)  
    dec = float(sec / 3600)  
    deg = float(d + dec)  
  
    if direction.upper() == 'W' or direction.upper() == 'S':  
        deg = deg * -1  
  
    return float(deg)  

接下来,我们需要编写一个函数gps()来解析EXIF元数据中的GPS信息,并返回经度和纬度值。这个函数将首先检查是否存在GPSInfo标签,然后提取度、分、秒和方向信息,并使用dms2dd()函数进行转换。

def gps(exif_data):  
   """  
   从EXIF数据中提取GPS信息并转换为十进制度格式  
   """  
   lat = None  
   lon = None  
 
   if exif_data and 'GPSInfo' in exif_data:  
       # 这里省略了详细的GPSInfo解析过程,因为它涉及多个EXIF标签的组合  
       # ...  
       # 假设我们已经从exif_data中提取了经纬度信息(度、分、秒和方向)  
       # 这里只是模拟赋值  
       lat_d, lat_m, lat_s, lat_dir = (30, 15, 30, 'N')  
       lon_d, lon_m, lon_s, lon_dir = (120, 20, 45, 'E')  
 
       lat = dms2dd(lat_d, lat_m, lat_s, lat_dir)  
       lon = dms2dd(lon_d, lon_m, lon_s, lon_dir)  
 
   return lat, lon

四、将GPS信息转换为Shapefile格式

一旦我们从照片中提取了GPS信息,就可以将其转换为Shapefile文件,这是一种常用于地理信息系统中的矢量数据格式。我们将使用Python的pyshp库来创建Shapefile文件。

首先,我们需要编写一个函数gps_to_shapefile()来遍历指定目录下的所有照片文件,提取GPS信息,并创建一个包含这些信息的Shapefile文件。

photos = {}
photo_dir = ".\photos"

# 查找指定目录下的所有JPG照片
files = glob.glob(os.path.join(photo_dir, "*.jpg"))

# 从文件中提取GPS元数据
for f in files:
    e = exif(f)
    lat, lon = gps(e)
    photos[f] = [lon, lat]  # 注意:这里通常经度在前,纬度在后,但此处按照您的代码保持原样

# 构建一个包含照片文件名作为属性的点shapefile
with shapefile.Writer("photos1", shapefile.POINT) as w:
    w.field("NAME", "C", 80)  # 创建一个名为NAME的字符型字段,最大长度为80

    for f, coords in photos.items():
        w.point(*coords)  # 使用经度和纬度(注意顺序)创建一个点要素
        w.record(f)  # 为点要素添加文件名属性

五、完整代码

import glob
import os
try:
    import Image
    import ImageDraw
except:
    from PIL import Image
    from PIL.ExifTags import TAGS
import shapefile


def exif(img):
    """
    从图片中返回EXIF元数据
    """
    exif_data = {}

    try:
        i = Image.open(img)  # 使用PIL库打开图片
        tags = i._getexif()  # 获取图片的EXIF标签

        for tag, value in tags.items():
            decoded = TAGS.get(tag, tag)  # 尝试从预定义的TAGS字典中获取标签的中文描述,否则使用标签ID
            exif_data[decoded] = value  # 将标签及其值存储到exif_data字典中

    except:
        pass  # 捕获所有异常并忽略,这通常不是一个好的做法,应该明确指定要捕获的异常

    return exif_data


def dms2dd(d, m, s, i):
    """
    将度/分/秒转换为十进制度
    """
    sec = float((m * 60) + s)  # 将分和秒转换为秒
    dec = float(sec / 3600)  # 将秒转换为小数度
    deg = float(d + dec)  # 将度和小数度相加

    if i.upper() == 'W':  # 如果方向是西
        deg = deg * -1  # 将度数变为负数

    elif i.upper() == 'S':  # 如果方向是南
        deg = deg * -1  # 将度数变为负数

    return float(deg)


def gps(exif):
    """
    从EXIF元数据中提取GPS信息
    """
    lat = None  # 纬度
    lon = None  # 经度

    if exif.get('GPSInfo'):  # 如果EXIF中包含GPS信息
        # 纬度
        coords = exif['GPSInfo']
        i = coords[1]  # 纬度方向(N/S)
        d = coords[2][0]  # 纬度度数
        m = coords[2][1]  # 纬度分钟
        s = coords[2][2]  # 纬度秒
        lat = dms2dd(d, m, s, i)  # 将纬度转换为十进制度

        # 经度
        i = coords[3]  # 经度方向(E/W)
        d = coords[4][0]  # 经度度数
        m = coords[4][1]  # 经度分钟
        s = coords[4][2]  # 经度秒
        lon = dms2dd(d, m, s, i)  # 将经度转换为十进制度

    return lat, lon


if __name__ == '__main__':
    # 存储照片文件名和GPS坐标的字典
    photos = {}
    photo_dir = ".\photos"

    # 查找指定目录下的所有JPG照片
    files = glob.glob(os.path.join(photo_dir, "*.jpg"))

    # 从文件中提取GPS元数据
    for f in files:
        e = exif(f)
        lat, lon = gps(e)
        photos[f] = [lon, lat]  # 注意:这里通常经度在前,纬度在后,但此处按照您的代码保持原样

    # 构建一个包含照片文件名作为属性的点shapefile
    with shapefile.Writer("photos1", shapefile.POINT) as w:
        w.field("NAME", "C", 80)  # 创建一个名为NAME的字符型字段,最大长度为80

        for f, coords in photos.items():
            w.point(*coords)  # 使用经度和纬度(注意顺序)创建一个点要素
            w.record(f)  # 为点要素添加文件名属性

结果在arcgis打开,如下:

image-20240506091924307