(数据科学学习手札59)从抓取数据到生成shp文件并展示
一、简介
shp格式的文件是地理信息领域最常见的文件格式之一,很好的结合了矢量数据与对应的标量数据,而在Python中我们可以使用pyshp来完成创建shp文件的过程,本文将从如何从高德地图获取矢量信息开始,最终构造出相应的shp文件,并利用R中的leaflet进行可视化;
二、数据获取及清洗
2.1 数据获取
首先我们需要从高德地图获取所关注对象的矢量信息,这里点数据我们选择重庆轨道交通站点,线我们选择重庆轨道交通线路,面我们选择重庆市三峡博物馆,考虑到只是简单演示小规模采集数据,因此选择selenium作为数据爬取的工具,首先我们需要操纵模拟浏览器打开高德地图查找内容的页面(即query带有关键词),这样做的目的是让我们的浏览器加载所需接口对应的cookies,方便之后直接进行矢量信息的采集,如下面这个页面:
运行下面的代码启动浏览器,接着观察会不会出现滑块验证码(笔者亲测有很大的概率触发验证码):
from selenium import webdriver
from tqdm import tqdm
import time option = webdriver.ChromeOptions()
'''这个实验参数用于避免被高德检测到为driver驱动的浏览器'''
option.add_experimental_option('excludeSwitches', ['enable-automation'])
browser = webdriver.Chrome(options=option)
'''访问指定网址拿到cookies'''
browser.get('https://www.amap.com/search?query=轨道交通3号线&city=500000&geoobj=106.477496%7C29.407019%7C106.642291%7C29.665101&zoom=12')
这时若出现下列验证码则手动接触即可(考虑到爬虫并不是本文重点因此没有花费时间编写模拟滑动滑块的代码):
在滑块解除后,我们就可以批量获取轨道线路矢量信息,代码如下,注意每轮运行间隔调久一些防止被ban:
'''这个字典存放所有原始的json数据'''
rawSHP = {}
crtLines = ['轨道交通1号线','轨道交通2号线','轨道交通3号线','轨道交通4号线','轨道交通5号线','轨道交通6号线','轨道交通10号线',
'轨道交通3号线北延伸段(空港线)','轨道交通6号线支线','轨道交通环线'] for line in tqdm(crtLines):
browser.get(f'https://www.amap.com/service/poiInfo?query_type=TQUERY&pagesize=20&pagenum=1&qii=true&cluster_state=5&need_utd=true&utd_sceneid=1000&div=PC1000&addr_poi_merge=true&is_classify=true&zoom=12&city=500000&geoobj=106.477496%7C29.394307%7C106.642291%7C29.677779&keywords={line}')
'''这里从网页内容标签中抽取json部分内容'''
rawSHP[line] = eval(browser.find_elements_by_xpath("//pre")[0].text)
time.sleep(8)
这样我们就得到对应重庆轨道交通线路和站点的原始json数据,接下来类似上面的做法,获取中国三峡博物馆矢量信息:
browser.get('https://www.amap.com/service/poiInfo?query_type=TQUERY&pagesize=20&pagenum=1&qii=true&cluster_state=5&need_utd=true&utd_sceneid=1000&div=PC1000&addr_poi_merge=true&is_classify=true&zoom=12&city=500000&geoobj=106.477496%7C29.394307%7C106.642291%7C29.677779&keywords=中国三峡博物馆')
'''这里从网页内容标签中抽取json部分内容'''
museumSX = eval(browser.find_elements_by_xpath("//pre")[0].text)
经过上面的步骤我们就得到了所需内容的原始格式,接下来进行清洗;
2.2 数据清洗
首先提取点数据,rawSHP为字典,键为线路名称,值为所对应包含的全部内容,我们需要的经纬度信息就包含在其中,以环线为例:
按照上图箭头所指的路径便可找到对应的站点名称name和经纬度xy_coords,而对于线数据,如下图:
同样可以找到对应每个折点的经度xs与纬度ys,对于面数据,在museumSX变量下data->poi_list->domain_list中name属性为'aoi'的元素中可以找到其对应的面矢量信息:
获悉所需数据的位置之后,接下来我们在写入shp文件的过程中同时完成清洗过程,在此之间首先需要介绍pyshp中写出shp文件相关的用法;
三、写出shp文件
3.1 用pyshp写出shp文件
pyshp是以纯Python代码的方式对ESRI shapefiles文件进行读写、编辑等操作的模块,以用法方便快捷功能高效强大著称,写出时使用到其中的Writer类,其主要有三个参数:
target:文件最终存出的具体位置及文件名称
shapeType:int型,用于决定文件类型,类型与数字对应关系如下:
autoBalance:int型,建议传入1,即定义的属性有秩序的自动跟随定义的要素之后,避免出现错乱;
而pyshp中的Writer对象有如下常用方法:
field:用于创建跟随矢量要素的属性表字段,其name参数用于定义字段名;fieldType参数用于控制数据类型,'C'代表字符串,‘N’代表数值型,‘F’代表浮点型,‘L’代表bool型,‘D’代表日期;参数size为字符型,用于控制数据长度,最大限制为‘2046’
point:传入点的经度与纬度
line:传入单条或多条线每个折点的经纬度
poly:传入面中对应每个边界点的经纬度
除了上述三种最基本的,还有很多传入其他格式矢量信息的方法,本文未使用到不再赘述;
record:传入属性表对应字段的值
close:在最后存出文件时调用
因为我们爬取的数据来自高德地图,因此如果有转换坐标系的需求,可以使用下列代码完成百度坐标、火星坐标系、wgs84之间的互转:
import math
x_pi = 3.14159265358979324 * 3000.0 / 180.0
pi = 3.1415926535897932384626 # π
a = 6378245.0 # 长半轴
ee = 0.00669342162296594323 # 偏心率平方 class LngLatTransfer():
def __init__(self):
pass def gcj02_to_bd09(self, lng, lat):
"""
火星坐标系(GCJ-02)转百度坐标系(BD-09)
谷歌、高德——>百度
:param lng:火星坐标经度
:param lat:火星坐标纬度
:return:
"""
z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi)
theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi)
bd_lng = z * math.cos(theta) + 0.0065
bd_lat = z * math.sin(theta) + 0.006
return [bd_lng, bd_lat] def bd09_to_gcj02(self, bd_lon, bd_lat):
"""
百度坐标系(BD-09)转火星坐标系(GCJ-02)
百度——>谷歌、高德
:param bd_lat:百度坐标纬度
:param bd_lon:百度坐标经度
:return:转换后的坐标列表形式
"""
x = bd_lon - 0.0065
y = bd_lat - 0.006
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi)
theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi)
gg_lng = z * math.cos(theta)
gg_lat = z * math.sin(theta)
return [gg_lng, gg_lat] def wgs84_to_gcj02(self, lng, lat):
"""
WGS84转GCJ02(火星坐标系)
:param lng:WGS84坐标系的经度
:param lat:WGS84坐标系的纬度
:return:
"""
if self.out_of_china(lng, lat): # 判断是否在国内
return [lng, lat]
dlat = self._transformlat(lng - 105.0, lat - 35.0)
dlng = self._transformlng(lng - 105.0, lat - 35.0)
radlat = lat / 180.0 * pi
magic = math.sin(radlat)
magic = 1 - ee * magic * magic
sqrtmagic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
mglat = lat + dlat
mglng = lng + dlng
return [mglng, mglat] def gcj02_to_wgs84(self, lng, lat):
"""
GCJ02(火星坐标系)转GPS84
:param lng:火星坐标系的经度
:param lat:火星坐标系纬度
:return:
"""
if self.out_of_china(lng, lat):
return [lng, lat]
dlat = self._transformlat(lng - 105.0, lat - 35.0)
dlng = self._transformlng(lng - 105.0, lat - 35.0)
radlat = lat / 180.0 * pi
magic = math.sin(radlat)
magic = 1 - ee * magic * magic
sqrtmagic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
mglat = lat + dlat
mglng = lng + dlng
return [lng * 2 - mglng, lat * 2 - mglat] def bd09_to_wgs84(self, bd_lon, bd_lat):
lon, lat = self.bd09_to_gcj02(bd_lon, bd_lat)
return self.gcj02_to_wgs84(lon, lat) def wgs84_to_bd09(self, lon, lat):
lon, lat = self.wgs84_to_gcj02(lon, lat)
return self.gcj02_to_bd09(lon, lat) def _transformlat(self, lng, lat):
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lat * pi) + 40.0 *
math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
math.sin(lat * pi / 30.0)) * 2.0 / 3.0
return ret def _transformlng(self, lng, lat):
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lng * pi) + 40.0 *
math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
return ret def out_of_china(self, lng, lat):
"""
判断是否在国内,不在国内不做偏移
:param lng:
:param lat:
:return:
"""
return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
3.2 写出shp文件
点文件:
思路是初始化Writer对象之后,利用循环从rawSHP字典中抽取所有的站点名称、经纬度以及对应线路,因此属性表中创建字段name用于保存站点名称,route字段用于存放线路信息,具体代码如下(注意导入名需为shapefile,即pyshp):
'''shp文件写出部分'''
import shapefile
'''抽取经纬度-站点名称-线路名称三元组'''
all_points = []
for line in tqdm(rawSHP.keys()):
for line_ in rawSHP[line]['data']['busline_list']:
for station in line_['stations']:
'''这里完成火星坐标系向WGS84的转换'''
all_points.append([transfer.gcj02_to_wgs84(lng=float(station['xy_coords'].split(';')[0]),
lat=float(station['xy_coords'].split(';')[1])),
station['name'],
line])
'''去重'''
all_points_ = []
for item in all_points:
if item not in all_points_:
all_points_.append(item) '''初始化Writer对象'''
w_point = shapefile.Writer(r'C:\Users\hp\Desktop\shp写出\重庆轨道交通站点矢量数据',
autoBalance=shapefile.POINT) '''创建站点名称字段'''
w_point.field('name','C') '''创建线路名称字段'''
w_point.field('route','C')
for item in all_points_: '''写入点要素'''
w_point.point(item[0][0],item[0][1]) '''追加属性值'''
w_point.record(name=item[1],route=item[2]) '''关闭存出文件'''
w_point.close()
输出目录中也包含了我们所需的文件:
在arcgis中查看:
成功~
接下来是线文件:
'''shp文件写出部分'''
import shapefile w_line = shapefile.Writer(r'C:\Users\hp\Desktop\shp写出\重庆轨道交通线路矢量数据',
autoBalance=1) w_line.field('name','C')
for key in rawSHP.keys():
lines = []
for idx in range(len(rawSHP[key]['data']['busline_list'])):
lines.append([])
for lng,lat in zip(rawSHP[key]['data']['busline_list'][idx]['xs'].split(','),rawSHP[key]['data']['busline_list'][idx]['ys'].split(',')):
lines[-1].append(transfer.gcj02_to_wgs84(lng=float(lng),lat=float(lat))) for l in lines:
'''每段线路要素单独写出'''
w_line.line([l])
'''追加对应的线路名称'''
w_line.record(name=key) w_line.close()
在arcgis中查看线文件:
面文件
rawPoly = museumSX['data']['poi_list'][]['domain_list'][]['value'].split('_')
rawPoly = [transfer.gcj02_to_wgs84(float(rawPoly[i].split(',')[]),float(rawPoly[i].split(',')[])) for i in range(len(rawPoly))] w_polygon = shapefile.Writer(r'C:\Users\hp\Desktop\shp写出\三峡博物馆面矢量数据',
autoBalance=shapefile.POLYGON)
w_polygon.field('name','C')
w_polygon.poly([rawPoly])
w_polygon.record(name='中国三峡博物馆')
w_polygon.close()
在arcgis中查看:
可以与高德网页上的形状对比,非常吻合,至此,我们就完成了shp文件的生成,下面我们简单的在R中用leaflet进行可视化,这里选用Carto的底图(WGS84坐标系),对应的R代码如下:
rm(list=ls())
library(rgdal)
library(leaflet)
library(ggplot2)
library(readxl)
setwd('C:\\Users\\hp\\Desktop\\shp写出') crt <- readOGR('重庆轨道交通线路矢量数据.shp')
crt_station <- readOGR('重庆轨道交通站点矢量数据.shp')
museum <- readOGR('三峡博物馆面矢量数据.shp') #用循环的方式叠加线
m <- leaflet() %>%
addTiles(urlTemplate = '//{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png')
for(i in :length(crt@lines)){
m <- m %>% addPolylines(fortify(crt@lines[[i]])$long,lat=fortify(crt@lines[[i]])$lat,fillColor = sample(colors(),),color = sample(colors(),),weight=)
} #叠加点
m <- m %>% addCircleMarkers(lng=data.frame(crt_station@coords)$coords.x1,lat=data.frame(crt_station@coords)$coords.x2,
radius=,
weight = ,
opacity = ,
color = 'red',
fillOpacity = ,
label=crt_station@data
)
#叠加面
m <- m %>% addPolygons(lng=fortify(museum)$long,
lat=fortify(museum)$lat)
m
放大后可以看到位于中山四路附近的三峡博物馆,跟高德地图上的对比一下,还是我们的底图比较素雅~:
以上就是本文的全部内容,如有疏漏之处望指出。
(数据科学学习手札59)从抓取数据到生成shp文件并展示的更多相关文章
- (数据科学学习手札07)R在数据框操作上方法的总结(初级篇)
上篇我们了解了Python中pandas内封装的关于数据框的常用操作方法,而作为专为数据科学而生的一门语言,R在数据框的操作上则更为丰富精彩,本篇就R处理数据框的常用方法进行总结: 1.数据框的生成 ...
- (数据科学学习手札06)Python在数据框操作上的总结(初级篇)
数据框(Dataframe)作为一种十分标准的数据结构,是数据分析中最常用的数据结构,在Python和R中各有对数据框的不同定义和操作. Python 本文涉及Python数据框,为了更好的视觉效果, ...
- (数据科学学习手札47)基于Python的网络数据采集实战(2)
一.简介 马上大四了,最近在暑期实习,在数据挖掘的主业之外,也帮助同事做了很多网络数据采集的内容,接下来的数篇文章就将一一罗列出来,来续写几个月前开的这个网络数据采集实战的坑. 二.马蜂窝评论数据采集 ...
- (数据科学学习手札61)xpath进阶用法
一.简介 xpath作为对网页.对xml文件进行定位的工具,速度快,语法简洁明了,在网络爬虫解析内容的过程中起到很大的作用,除了xpath的基础用法之外(可参考我之前写的(数据科学学习手札50)基于P ...
- (数据科学学习手札50)基于Python的网络数据采集-selenium篇(上)
一.简介 接着几个月之前的(数据科学学习手札31)基于Python的网络数据采集(初级篇),在那篇文章中,我们介绍了关于网络爬虫的基础知识(基本的请求库,基本的解析库,CSS,正则表达式等),在那篇文 ...
- (数据科学学习手札42)folium进阶内容介绍
一.简介 在上一篇(数据科学学习手札41)中我们了解了folium的基础内容,实际上folium在地理信息可视化上的真正过人之处在于其绘制图像的高度可定制化上,本文就将基于folium官方文档中的一些 ...
- (数据科学学习手札36)tensorflow实现MLP
一.简介 我们在前面的数据科学学习手札34中也介绍过,作为最典型的神经网络,多层感知机(MLP)结构简单且规则,并且在隐层设计的足够完善时,可以拟合任意连续函数,而除了利用前面介绍的sklearn.n ...
- (数据科学学习手札69)详解pandas中的map、apply、applymap、groupby、agg
*从本篇开始所有文章的数据和代码都已上传至我的github仓库:https://github.com/CNFeffery/DataScienceStudyNotes 一.简介 pandas提供了很多方 ...
- (数据科学学习手札80)用Python编写小工具下载OSM路网数据
本文对应脚本已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们平时在数据可视化或空间数据分析的过程中经常会 ...
随机推荐
- 力扣算法题—144Binary Tree Preorder Traversal
Given a binary tree, return the preorder traversal of its nodes' values. Example: Input: [1,null,2,3 ...
- activiti7配置文件activiti.cfg.xml,日志文件log4j.properties及pom文件
activiti.cfg.xml:<?xml version="1.0" encoding="UTF-8"?><beans xmlns=&qu ...
- 在apache hadoop2.6 上部署hive 并将hive数据源存储于Mysql
集成hive 的前提是apache hadoop 集群能够正常启动. hadoop 版本 apach2.6.0 hive 版本:1.2.1 1.安装mysql 并赋予权限: 1.1:创建hive 用 ...
- BUUCTF 派大星的烦恼
这道题做的累死了,题目关键在于思路,这里将做题的完整思路记下来.题目给了一张bmp,用010打开可以看出题目关键就在于这一段D和“,保存出来 "DD"DD""& ...
- python之求字典最值
本例子求字典最小值 首先字典分为键和值 eg: {键:值} prices = { 'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, ...
- mysql在插入数据前判断是否存在数据
记录一次mysql插入数据的操作,要先判断表中是否存在对应的值. 以往我们的操作都是先select,再insert,今天给大家分享另一种操作,一条sql语句来实现: inset into user(u ...
- MySQL中orderby和limit分页数据重复的问题
背景 读取规则是按照某表中sequence字段排序的,而这个字段是让人手工填写的.那么,可想而知,数据一多,难免会出现填写的值相同的情况. 综上所述,可能就会导致以下两条sql出现数据重叠的情况: s ...
- Vue_子级组件调用主组件函数
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- koa 的 Context
context上下文对象: 该对象类似原生http中的 req + res - 该对象的req,res属性也存在,就是原生没有包装过的req,res - 简单说: context 对象就是从请求到响应 ...
- 外部操作获取iframe的东西
原生js document.iframe[id].contentWindow.document.querySelector(el).innerHTML jq $(window.ifram ...