用可视化地图讲照片的故事(Python+Leaflet)

手机和数码相机拍的照片里除了我们能看到的RGB像元数据,还包含了拍摄时间、图像分辨率、感光值、GPS坐标等属性,记录在Exif(Exchangeable image file format)模块里。

随着手机像素越来越高,用手机记录身边的事(和自拍)已经变成很自然的动作,在一年里我们的手机肯定存了很多照片,照片和Exif数据块中的位置可以做哪些有趣的事情?一张图片和对应的拍摄位置如果没那么多可能性,那一系列照片和位置呢?

我们可以直观看近些年都去了哪里;可以制作和(男/女)朋友一起出去玩的地图故事;可以根据拍照时间和位置动态可视化游览路线;可以基于坐标的聚类整理照片,如拍了800张照片,把每个城市的照片批量整理到各自文件夹;……

地理位置属于个人隐私数据,相关应用需要注意隐私问题,之前挺火的一个谣言是可以根据别人朋友圈发的图知道别人的具体位置,但实际上微信会对朋友圈的图片进行压缩,Exif里的坐标数据是会删除掉的,所以朋友圈的图片是提取不了坐标的。以下实践基于部分自己这些年拍的照片,避免侵犯其他人隐私。

查看照片中的Exif信息

本文主要做的:批量提照片中的坐标->可视化照片位置->制作游历故事地图

所用到的工具:

  • Python和exifread库
  • Leaflet和两个插件

1,批量提照片中的坐标

照片中的地理坐标记录在Exif块里,Exif信息以0xFFE1作为开头标记,采用TIFF格式,可以自己解析或直接用轮子exifread库,exifread是一个很方便使用的读取tiff和jpeg格式图片的Python库,在pypi上的介绍是:

Easy to use Python module to extract Exif metadata from tiff and jpeg files.

通过 pip install exifread安装后就可以使用了,我们现在只关心照片的坐标和拍摄时间,根据其教程探索参数和用法。

exifread库的使用

写代码提取这部分数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def extractExif(fpath):#提取坐标
try:
with open(fpath,'rb') as rf:
exif=exifread.process_file(rf)
eDate=exif['EXIF DateTimeOriginal'].printable
eLon=exif['GPS GPSLongitude'].printable
eLat=exif['GPS GPSLatitude'].printable
lon=eLon[1:-1].replace(' ','').replace('/',',').split(',')
#'[116, 29, 10533/500]' to [116,29,10533,500] type==(list)
lon=float(lon[0])+float(lon[1])/60+float(lon[2])/float(lon[3])/3600
lat=eLat[1:-1].replace(' ','').replace('/',',').split(',')
lat=float(lat[0])+float(lat[1])/60+float(lat[2])/float(lat[3])/3600
return [lon,lat,eDate] #经度,纬度,拍摄时间
except Exception as e:
print(e,fpath)
return None

注意的是如果拍照时没有读取地理位置权限那就不好记录拍照时的坐标了,所以使用时需要做一个判断。调用上面的函数批量取一个文件夹下照片的坐标:

1
2
3
4
5
6
7
8
9
10
11
wpt='J:/DS_refine/SQL-lyn/exifExtract/image'
latLons=[]
for root, dirs, files in os.walk(wpt):
print(len(files))
for f in files:
exif=extractExif('{0}/{1}'.format(wpt,f))
if exif:
exif[2]=exif[2]+' '+f
latLons.append(exif)
else:
print(f,'exif is None')

有了照片和对应的位置,可以做可视化讲故事了。下面的实践需要了解一些前端HTML和JavaScript知识。

2,在地图中展示坐标

直接展示地理点坐标有很多工具,百度/高德地图的API、Echarts、Leaflet、OpenLayers等。

这里用Leaflet框架和 marker-clustering.js 实现坐标点展示和缩小时点聚合的效果,这样能适应各种缩放层级。效果如下:

markerClustring-可视化效果

实现方式是在前端的html页面里引入 leaflet.js和 leaflet.markercluster-src.js,对map元素进行配置和设置好坐标数据,把html文件配置好之后,把数据写入js文件再调用就好。基于1中提取的坐标,保存为js文件,然后在浏览器打开html文件,就是上图中的效果了。另外需要说明的是,这些标记点(marker)点击之后都是能看到具体的文本的,展示的文本就是title里的内容,是有交互效果的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript">
var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}),
latlng = L.latLng(37.552897,115.60571); //设置地图的坐标中心点
var map = L.map('map', {center: latlng, zoom: 5, layers: [tiles]});
var markers = L.markerClusterGroup();

for (var i = 0; i < addressPoints.length; i++) {
var a = addressPoints[i];
var title = a[2];
var marker = L.marker(new L.LatLng(a[0], a[1]), { title: title });
marker.bindPopup(title);
markers.addLayer(marker);
}
map.addLayer(markers);
</script>

所调用文件及结构展示

而把这些坐标放到百度地图的效果如下:

基于百度地图的点坐标可视化

坐标多的话就是密密麻麻的红点。

:百度地图中采用的坐标需要是百度坐标系(bd-09),而我们提取的坐标是GPS坐标,用的是WGS84坐标系,需要做转换,可以调用**coordTransform_py**进行转换,高德地图采用的是火星坐标系,也需要进行转换。

只是展示坐标不怎么有趣,下面做一个左侧图文描述右侧可视化坐标的效果。

3,游历故事地图

给那些年去过的地方写一个地图游记。示例效果如下:

那些年去过的地方

还是用之前提取的坐标和Leaflet框架。用到的插件是storymap.js,同样引用js之后,改变其中的坐标数据,因为是讲一个故事,具体内容当然按自己想讲述的写,将 <sectiondata-place="bodo">中的bodo和js代码中markers里的bodo对应好就好,例如bodo改为beijing。

1
2
3
4
5
6
7
8
var markers = {
beijing: {lat:39.886426, lon: 116.404762, zoom: 6},
tianjin: {lat:39.134594, lon: 117.191961, zoom: 7},
fuyang: {lat:32.645140, lon: 116.268333, zoom: 7},
ningbo: {lat:29.763531, lon: 121.898233, zoom: 8},
liuzhou: {lat:24.313703, lon: 109.406884, zoom: 7}
};

4,整合聚类点到游历地图里

在我们做的游历地图里增加点聚类的效果,一个简单做法是在storymap.js里增加对markercluster.js的调用,从而可以用markerClusterGroup() 重写基本的marker标记点类型。效果如下:

地图故事效果图

在html里可以根据自己的想法增加更多的内容,例如具体的地址文本,只需要调用百度/高德地图的Web服务 API中的逆地理编码服务就可以实现,逆地理编码就是指将经纬度转换为详细结构化的地址,如把WGS84坐标系的坐标[116.421046,39.903004]逆地理编码对应北京市东城区北京站。

也可以继续探索更多的Leaflet插件。

另外可以换底图,例如换成Satellite卫星底图,改map初始化时地图瓦片图层的调用url就行 L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',效果如下,是不是也很生动呢?

用Mapbox的Satellite底图的效果1

用Mapbox的Satellite底图的效果2

空间位置可以做很多分析和很多有趣的事情,Python也是很强大的工具,仅需要发挥想象力。

其他可视化形式效果如图:

可视化形式之下菜单栏

可视化形式之照片直接显示

参考资料

以上的一些代码同步 更新于QLWeilcf/VisualizedLyn