一篇文章教你搞定JSON素材,从此告别SHP时代~

最近几天推送频率之所以下降了,不是因为偷懒,是在攻克一个难题~

还记得前一篇推送,关于山东省财政数据可视化那一篇,因为没有精准、最新的山东省县级市边界地图素材数据,花了好多冤枉功夫,搜地图素材各种碰壁,最后的得到的地图数据并不尽如人意。

现在shp的素材相比json整体都不太流行了,无论是制作成本上还是占用内存上以及与实际行政区划的更新速度上,json地图素材轻便、时效、易获取,很多网站都提供这种轻量级的数据文件。

可是json文件遵循的JS语法,导入R中之后,全部被强制转化为各种嵌套的list、data.frame、array等混合体,如果没有对R数据结构很好的把握,基本看上一眼就绝望了。

json数据预览:

记事本打开的json数据

R中打开的json数据

网页渲染后的json数据代码

虽然难以理解,但是又不得不用,所以再难也得拿下~

json数据类型:

这里先说明一下,Json数据格式分为两类,一类是geojson,内部的数据类型显示FeatureCollection,这种类型数据文件里面直接存储的是解码后的经纬度数据,另一类是topojson,这种类型是需要通过坐标转换后才能使用,因为每一个点不是真实经纬度,所以下载的时候一定要看清楚。

这里提供给大家三个网址:
geojson.io

以上网址自选、也可以通过导入shp数据转换格式(其中就可以将topojson转化为geojson)。
mapshaper
datav

数据准备:

以下过程我用两个示例展示提取json地图数据的过程:

1
2
3
4
5
library("jsonlite")
library("ggplot2")
library(plyr)
library(dplyr)
setwd("D:/R/mapdata/City/")

提取济南市json地图数据:

1
2
3
4
5
json_data <- fromJSON("370100.json")
city<-json_data$features$properties
names(city)[2]<-"code"
city$id<-1:nrow(city)
city$sale<-round(rnorm(nrow(city),100,20),0)

这里提取了济南市各区的名称、代码,并生成了虚拟指标

济南市各区边界点坐标:

1
2
3
4
5
6
7
8
9
10
11
citydata<-json_data$features$geometry$coordinates
mapdata<-data.frame()
for( i in 1:length(citydata)){
citymapdata<-citydata[[i]]
dim(citymapdata)=c(length(citymapdata)/2,2)
citymapdata<-data.frame(citymapdata);names(citymapdata)[1:2]<-c("lon","lat")
citymapdata$id<-i
citymapdata$group<-as.numeric(paste0(i,".",1))
citymapdata$order<-1:dim(citymapdata)[1]
mapdata<-rbind(mapdata,citymapdata)
}

以上过程通过循环函数提取了济南市各区的边界点经纬度坐标,并生成了分组依据group、指定了单个区边界点顺序,生成id变量便于和各区合并

1
mymapdata<-merge(mapdata,city)

合并边界点数据和各区名称与分组依据(主要是ggplot映射时作为分组变量使用)

因为各区的行政中心经纬度未知,这里暂时提取多边形中心作为其参考值

1
2
midpos <- function(x) mean(range(x,na.rm=TRUE))
centres <- ddply(dongsansheng_map_data,.(city),colwise(midpos,.(long,lat)))

以上过程展示了如何从json格式的数据文件中提取我们制作数据地图所需要的指标(核心指标由三个:lon、lat、group),但是以上只够我们画出一幅单色地图,因为没有指定任何指标,在素材提取过程中,之所以先提各区的代码和id,目的是之后与边界经纬度信息合并,这样,所有指标都可以通过合并进入整体的边界点经纬度信息数据文件中,指标(无论是连续还是分类型)可以作为映射规则(大小、颜色、形状)。

1
2
3
4
5
6
7
8
9
10
11
ggplot(dongsansheng_map_data,aes(long,lat)) +
geom_polygon(aes(group=group,fill=zhibiao),colour="grey95") +
scale_fill_gradient(low="white",high="steelblue") +
geom_text(aes(label=city),data=centres) +
theme(
panel.grid = element_blank(),
panel.background = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
axis.title = element_blank()
)

因为只是讲解数据提取过程, 这里就不展示最终的图形了。

但是针对省级边界的json数据文件,相对就要复杂得多,因为很多省份内的城市辖区可能地域上是分割开的(比如河北的廊坊、安徽的铜陵等),但是R语言通过多边形映射的时候,是将分离的多边形分别定义(依据就是上面的group变量),然后通过将具有相同行政隶属关系的多边形指定一个相同的ID(我们的所有指标型数据都是跟id挂钩的,与group无关,只有在该地区行政辖区内各子行政单位没有出现地域分割的情况,此时基于行政单位编号的id和基于多边形编号的group才会一一对应,否则不会出现严格对应关系)。

1
2
setwd("D:/R/mapdata/Province/")
anhui_data <- fromJSON("anhui.json")

接下来以安徽省的json数据结构为例来说明:

我们可以看到经纬度数据都存在名称为properties的子list里面,首先提取出来安徽市级行政单位的属性信息(代码、名称)。

1
2
3
4
5
6
anhui_city_data1<-anhui_data$features$properties[,1:2]
anhui_city_data2<-anhui_data$features$properties$center
anhui_city<-cbind(anhui_city_data1,anhui_city_data2)
names(anhui_city)[2]<-"code"
anhui_city$id<-1:nrow(anhui_city)
anhui_city$sale<-round(rnorm(nrow(anhui_city),100,20),0)

接下来问题来了,安徽省的各市级单位经纬度信息数据看起来在list不是同级的,即有些城市是单独一个list,有些城市是一个list里面嵌套好几个子list(这就解释了上面所讲过的,有些城市辖区不接壤,需要分别对其进行多边形描述和定义)。

这里写了个自定义函数,具体示意呢,不太好讲,全凭感觉写的,这个还真的看具体情况来分析,如果作为模板使用,换一个省份可能不一定还能用,但是可以作为参考,修修改改也能省不少事儿!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
anhui_map_data<-anhui_data $features$geometry$coordinates
mapdata1<-data.frame()
mapdata2<-data.frame()
for( i in 1:length(anhui_map_data)){
citymapdata<-anhui_map_data[[i]]
if (length(citymapdata)<50){
for(m in 1:length(citymapdata)){
citymapdata1<-data.frame(citymapdata[[m]]);names(citymapdata1)<-c("lon","lat")
citymapdata1$id<-i
citymapdata1$group<-as.numeric(paste0(i,".",m,1))
citymapdata1$order<-1:dim(citymapdata1)[1]
mapdata1<-rbind(mapdata1,citymapdata1)
}
}else{
dim(citymapdata)=c(length(citymapdata)/2,2)
citymapdata2<-data.frame(citymapdata);names(citymapdata2)<-c("lon","lat")
citymapdata2$id<-i
citymapdata2$group<-as.numeric(paste0(i,".",1))
citymapdata2$order<-1:dim(citymapdata2)[1]
mapdata2<-rbind(mapdata2,citymapdata2)
}
mydatanew<-rbind(mapdata1,mapdata2)
}
mydatanew<-arrange(mydatanew,id,order)
mydatanew_map_data<-merge(mydatanew,anhui_city[,c(-3,-4)],by="id")
ggplot(mydatanew_map_data,aes(lon,lat,group=group,fill=sale))+geom_polygon(col="white")+
theme(
panel.grid = element_blank(),
panel.background = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
axis.title = element_blank()
)

啊噢,完美的搞定json数据,你肯定看不出来这根使用shp导入的地图数据做出来的图有啥区别,因为根本就没有任何区别(排除两者在经纬度算法上的差异),因为我们并没有使用shp或者json中声明的任何关于地图素材的格式属性,我们只是提取了有用的经纬度变量信息。

下一篇,跟大家细讲关于ggplot在制作数据地图过程中的变量映射规则和注意事项。


联系方式:
wechat:ljty1991
Mail:578708965@qq.com
个人公众号:数据小魔方(datamofang)
团队公众号:EasyCharts
qq交流群:[魔方学院]298236508

个人简介:
杜雨
财经专业研究僧;
伪数据可视化达人;
文科背景的编程小白;
喜欢研究商务图表与地理信息数据可视化,爱倒腾PowerBI、SAP DashBoard、Tableau、R ggplot2、Think-cell chart等诸如此类的数据可视化软件,创建并运营微信公众号“数据小魔方”。
Mail:578708965@qq.com


备注信息:
知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议

坚持原创技术分享,您的支持将鼓励我继续创作!