事情起因#
很多媒體都報導過在社交媒體聊天時,發送圖片如果勾選到 “原圖” 選項,
則有可能根據圖片中攜帶的 GPS 信息暴露個人位置隱私
那麼你有沒有想過自己動手實現查詢地理位置信息呢?
今天剛好看到一篇文章,復現一下
申請 key#
首先註冊並登錄進高德開放平台
創建一個應用並點擊右邊的+
號申請key
:
給key
取個名稱並選擇web服務
提交:
得到了逆地理編碼的API
:
通過 GPS 獲取的經度、緯度和高德地圖的坐標存在一定的誤差,高德官方提供了坐標轉換的方法
用的key
也是屬於這個WEB
服務,所以不用再申請,這個key
會被使用到兩次
圖片處理#
使用exifread
庫讀取圖片的exif
信息,官方GitHub有常見參數獲取方法
先用pip
安裝:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple exifread
基本使用方法如下:
with open("test.jpg", 'rb') as f:
image_exif = exifread.process_file(f)
for tag, value in image_exif.items():
print(tag, value)
這是為了打印出所有的信息才這樣使用,實際上exif
讀出來即dict
,直接鍵值方式讀就可以,沒必要for
循環
(** 這裡說明一下,如果你想測試一下,用自己的手機拍張照片來測試,使用微信和 QQ 上傳到電腦的圖片是無效的 **
可以通過手機自帶相機拍照
-> 通過網盤或文件快傳等方式傳輸
,例如,可使用奶牛快傳)
但是這裡參數太多了,大部分不需要,我們只需要獲取以下信息:
Image Make:手機品牌
Image Model:手機型號
EXIF LensModel:攝像頭信息
GPS GPSLongitudeRef:經度標誌
GPS GPSLongitude:經度
GPS GPSLatitudeRef:緯度標誌
GPS GPSLatitude:緯度
其中
寫程序時經度和維度都以N方向為正值
GPS 轉高德#
我們從圖片中讀取到的 GPS 信息還是不太恰當的,高德api
提供了轉換功能
也是使用相同的api
去構造函數並請求即可:
查詢#
獲得高德坐標之後使用相同的api
再構造一個url
可以對gps
請求逆地理編碼轉換
將gps
坐標轉換為街道地址
全碼如下:
# -*- coding: utf-8 -*-
'''
@author: soapffz
@fucntion: 用高德地圖api查詢原圖信息和地理位置(可單張或文件夾批量)
@time: 2019-09-03
'''
from os import path, listdir
import exifread
import requests
import json
"""
如果提示庫未安裝,請參考:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple exifread
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple json
高德開放平台:https://lbs.amap.com/dev/key/app
文章教程:https://soapffz.com/383.html
如果批量查詢建議放到一個文件夾內,初始化時填寫文件夾路徑
"""
class Location_query(object):
def __init__(self, key, img_path):
# 傳入參數,申請到的高德api和圖片路徑(也可能是文件夾路徑)
self.key = key
if len(self.key) != 32:
print("你的key好像不對,大兄弟...")
exit(0)
self.img_path = img_path
self.judge_dir()
def judge_dir(self):
# 判斷是否為文件夾
if path.isdir(self.img_path):
print("你選擇的是一個文件夾")
self.pic_l = []
for item in listdir(self.img_path):
if path.splitext(item)[1].lower() in [".jpg", ".png", ".jpeg"]:
self.pic_l.append(path.join(self.img_path, item))
if len(self.pic_l) == 0:
print("你選的文件夾裡面一張圖片都沒有,查你大爺")
exit(0)
elif len(self.pic_l) == 1:
self.singal_image_info(self.pic_l[0])
else:
print("共獲取到{}張圖片\n".format(len(self.pic_l)))
self.multi_pics_query()
else:
self.singal_image_info(self.img_path)
def singal_image_info(self, img):
# 單張圖片查詢
if type(img) != "list":
image_l = []
image_l.append(img)
else:
image_l = img
singal_pic_info_d = self.get_image_info(image_l)
for key, value in singal_pic_info_d.items():
print(key)
for info in value:
print(info)
def multi_pics_query(self):
# 處理多張圖片,由於逆地理編碼一次只能處理20對坐標,所以把所有坐標按每20個切割為一個列表
cut_pic_l = [self.pic_l[i:i + 20]
for i in range(0, len(self.pic_l), 20)]
info_l = {} # 存儲所有的查詢信息
for cut in cut_pic_l:
batch_info = self.get_image_info(cut)
info_l = {**info_l, **batch_info}
for key, value in info_l.items():
print(key)
for info in value:
print(info)
print("\n")
def parse_gps_express(self, gps_express):
"""
GPS坐標值轉數值
:param gps_express:GPS坐標表達式 [1,2,3/4]
:return: GPS坐標數值 1.035542
"""
try:
express = str(gps_express).replace(
" ", "").replace("[", "").replace("]", "")
parts = express.split(",")
subpart = parts[2].split("/")
degress = float(parts[0])
minutes = float(parts[1])
seconds = float(subpart[0]) / float(subpart[1])
return degress + minutes / 60 + seconds / 3600
except:
raise Exception("圖片信息錯誤")
def get_image_info(self, images):
"""
照片拍攝地GPS坐標
:param photo_path: 多張照片的磁碟路徑(為了配合逆地理編碼api一次只能查詢20對,這裡也設置為最多20張圖片)
:return: 多張照片的詳細信息 (手機品牌,手機型號,攝像頭信息,地理位置)
"""
multi_image_info = {} # 存儲圖片的信息,鍵為圖片,值為圖片的信息
gps_coordinates = {} # 存儲多個gps信息批量查詢,避免查詢次數的浪費,鍵為圖片,值為gps值
for image in images:
singal_image_info = []
image_name = image.split("\\")[-1]
with open(image, 'rb') as f:
image_exif = exifread.process_file(f)
if "Image Make" in image_exif.keys():
# 如果手機信息存在,則獲得圖片的手機信息
singal_image_info.append(
"手機品牌為:{}".format(str(image_exif["Image Make"])))
singal_image_info.append("手機型號為:{}".format(
str(image_exif["Image Model"])))
singal_image_info.append("攝像頭信息為:{}".format(
str(image_exif["EXIF LensModel"])))
else:
singal_image_info.append("未獲取到手機信息")
if "GPS GPSLongitudeRef" in image_exif.keys():
# 如果GPS信息存在,獲得圖片的GPS信息
#longitude_ref = str(image_exif["GPS GPSLongitudeRef"]).strip()
longitude = self.parse_gps_express(
str(image_exif["GPS GPSLongitude"]))
#latitude_ref = str(image_exif["GPS GPSLatitudeRef"]).strip()
latitude = self.parse_gps_express(
str(image_exif["GPS GPSLatitude"]))
#lng = longitude if "E" == longitude_ref else 0 - longitude
#lat = latitude if "E" == latitude_ref else 0 - latitude
gps_coordinates[image] = str(longitude) + "," + str(latitude)
# ["116.487585177952,39.991754014757","116.487585177952,39.991653917101"]
else:
singal_image_info.append("未獲取到gps信息")
multi_image_info[image] = singal_image_info
for image, location in zip(gps_coordinates.keys(), self.convert_gps(gps_coordinates.values())):
multi_image_info[image].append("位置為:{}".format(location))
return multi_image_info
def convert_gps(self, gps_coordinates):
"""
坐標轉換:GPS轉高德
:param gps_coordinates: 多個位置"GPS經度,GPS緯度"的集合(元組或數組都可以)
:return: 多個位置"高德經度,高德緯度"的集合列表,最多一次接收40對坐標
"""
try:
coordinates = "|".join(gps_coordinates)
url = "https://restapi.amap.com/v3/assistant/coordinate/convert?key={0}&locations={1}&coordsys=gps&output=json".format(
self.key, coordinates)
response = requests.get(url)
result = json.loads(response.text)
if "1" == result["status"]:
gd_coordinates = result["locations"].split(";")
return self.get_map_address((gd_coordinates))
else:
print("error:", result["infocode"], result["info"])
except Exception as e:
raise e
def get_map_address(self, gd_coordinates):
"""
逆地理編碼(高德坐標轉地址)
:param gd_coordinates:多個位置"高德經度,高德緯度"的集合(元組或數組都可以)
:return:多個位置地址信息的列表,最多一次查詢20個經緯度點
"""
try:
coordinates = "|".join(gd_coordinates)
batch = "true" if len(gd_coordinates) > 1 else "false"
url = "https://restapi.amap.com/v3/geocode/regeo?key={0}&location={1}&batch={2}&radius=500&extensions=base&output=json".format(
self.key, coordinates, batch)
response = requests.get(url)
result = json.loads(response.text)
if "1" == result["status"]:
address = []
if batch == "true":
address.extend([add["formatted_address"]
for add in list(result["regeocodes"])])
else:
fmt_add = result["regeocode"]["formatted_address"]
address.append(fmt_add)
return address
else:
print(result)
except Exception as e:
raise e
if __name__ == "__main__":
key = "" # 填寫你申請到的高德開放平台api
img_path = "" # 填寫圖片或者文件夾地址
Location_query(key, img_path)
單個圖片查詢效果如下:
文件夾查詢效果如下:
代辦
其中有一個地方沒搞清楚,就是從圖片讀取到的 gps 信息:longitude
和latitude
這兩個longitude_ref
和latitude_ref
的關係,從參考文章裡看到的是如果方向是N
需要轉為負數
但是負數是不符合高德地圖 gps 規範的,不加負號是能查詢出來的,所以這兩行註釋掉了,暫時沒看懂
EXIF 查看軟件#
查資料的時候看到的,感覺還不做,從zdfan
上找到一個最新的破解版,實測能用
也可以看到我們查詢的效果是正確的:
本文完。
參考文章: