一、前言

最近工作中,客户要求使用地图展示省-市-县区三级业务关系,并且可实现地图三级钻取。过程中遇到了以下几个问题,在此经验分享,希望大家少走弯路。

1.业务地图与实际行政区域关系不完全一致。
2.不同权限的人进入到系统后,由于权限不同,所呈现的对应的地图,这个地图可以是省级地图、市级地图、县区级地图。
3.后期四级区域变更,需要灵活配置,而不是进行地图的二次开发调测。

针对以上问题,我们逐步解决。

二、地图灵活加载

假设 mapJson 为某区域地图的 geojson。我们只需要使用如下方式就可以进行地图加载

1
2
3
4
5
6
7
8
9
10
11
12
echarts.registerMap('my_area', mapJson);
var chart = echarts.init(document.getElementById('area'));

chart.setOption({
……
series: [{
type: 'map',
map: 'my_area', //要和echarts.registerMap()中第一个参数一致
……
}]
……
});

从上面代码可以看到,我们只需要让echarts注册地图的名称和series中map的值保持一致就可以。下面是我的实际代码中进行的函数封装,mapname为地图文件的名称,也是地图对应区域的编码(adcode)。

1
2
3
4
5
6
7
8
9
10
	function mapRefresh(mapname, mapData){
var jsurl = ctx + 'ajax/libs/report/echarts/qinghaimap/' + mapname + '.json';
$.get(jsurl, function(mapJson) {
echarts.registerMap(mapname, mapJson);
optionGS.series[0].map = mapname;
optionGS.geo.map = mapname;
optionGS.series[0].data = mapData;
myChartGS.setOption(optionGS);
});
};

三、地图json文件的获取

3.1 地图基本区域获取

最新行政区划获取,基于高德地图API获取数据,下面是我的代码,基于python获取

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import json
import requests


def parse_district(districtObj: dict, idx=1, parent_id=0):
res = []
if 'name' in districtObj.keys():
if districtObj['level'] == 'street':
return res

lng, lat = districtCenter(districtObj['center'])
level = districtLevel(districtObj['level'])
citycode = districtObj['citycode'] if isinstance(districtObj['citycode'], str) else ''

# {"citycode":"0379","adcode":"410300","name":"洛阳市","center":"112.434468,34.663041","level":"city"}
# idx, districtObj['adcode'], districtObj['name'], level, citycode, lng, lat, parent_id
item = {
'id': idx,
'adcode': districtObj['adcode'],
'name': districtObj['name'],
'level': level,
'citycode': citycode,
'lng': lng,
'lat': lat,
'parent_id': parent_id
}
res.append(item)
parent_id = idx
idx = idx + 1

if isinstance(districtObj.get('districts'), list) and len(districtObj['districts']) > 0:
for subitem in districtObj['districts']:
subs = parse_district(subitem, idx, parent_id)
res += subs
idx += len(subs)
return res


def districtLevel(levelStr):
map_val = {
'country': 0,
'province': 1,
'city': 2,
'district': 3
}
return map_val[levelStr]


def districtCenter(center):
items = center.split(',')
return float(items[0]), float(items[1])


# 结果保存为json数组
def saveJson(data):
with open('area_dict.json', 'w', encoding='utf-8') as fp:
json.dump(data, fp, ensure_ascii=False, indent=4)
print('Save json file: area_dict.json')


# 保存为SQL脚本
def saveSqlFile(data, includeCreate=True):
# +--------------+-------------+------+-----+---------+----------------+
# | Field | Type | Null | Key | Default | Extra |
# +--------------+-------------+------+-----+---------+----------------+
# | area_id | int(11) | NO | PRI | NULL | auto_increment |
# | area_code | char(6) | NO | MUL | NULL | |
# | area_name | varchar(20) | NO | MUL | NULL | |
# | level | tinyint(1) | NO | MUL | 0 | |
# | city_code | char(4) | YES | | NULL | |
# | longitudinal | int(11) | YES | | 0 | |
# | lateral | int(11) | YES | | 0 | |
# | parent_id | int(11) | NO | MUL | -1 | |
# +--------------+-------------+------+-----+---------+----------------+
createCode = """
CREATE TABLE `area_dict` (
`area_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '地区Id',
`area_code` char(6) NOT NULL COMMENT '地区编码',
`area_name` varchar(20) NOT NULL COMMENT '地区名',
`level` tinyint(1) NOT NULL DEFAULT '0' COMMENT '地区级别(1:省份province,2:市city,3:区县district,4:街道street)',
`city_code` char(4) DEFAULT NULL COMMENT '城市编码',
`lng` int(11) DEFAULT '0' COMMENT '城市中心经度',
`lat` int(11) DEFAULT '0' COMMENT '城市中心纬度',
`parent_id` int(11) NOT NULL DEFAULT '-1' COMMENT '地区父节点',
PRIMARY KEY (`area_id`),
KEY `areaCode` (`area_code`),
KEY `parentId` (`parent_id`),
KEY `level` (`level`),
KEY `areaName` (`area_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3261 DEFAULT CHARSET=utf8 COMMENT='地区码表';
"""
with open('area_dict.sql', 'w', encoding='utf-8') as fp:
if includeCreate:
fp.write(createCode)
for item in data:
item['lng'] = int(item['lng'] * 1e6)
item['lat'] = int(item['lat'] * 1e6)
sql = "INSERT INTO area_dict(`area_id`,`area_code`,`area_name`,`level`,`city_code`,`lng`,`lat`,`parent_id`) " + \
"VALUES({id},'{adcode}','{name}',{level},'{citycode}',{lng},{lat},{parent_id});\n".format(**item)

fp.write(sql)

print('Save sql file: area_dict.sql')


if __name__ == "__main__":
url = 'https://restapi.amap.com/v3/config/district?keywords=中国&subdistrict=3&key=[你的高德地图key]'

response = requests.get(url)
if response.ok and response.status_code == 200:
data = response.json()
data = parse_district(data)
print('Download data successful, total:{0}!'.format(len(data)))
saveJson(data)
saveSqlFile(data)
else:
print('Request error!')

程序执行结束,会生成area_dict.sql和area_dict.json,方便我们入库进行后台管理。

3.2 地图geojson下载

我们使用上面生成的area_dict.json进行地图geojson下载

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import requests
import json
import os


def loadDistrict(filename):
data = []
with open(filename, 'r', encoding='utf-8') as fp:
data = json.load(fp)
return data


def saveGeoJson(areaCode, force=False):
saveName = 'geoV3/{0}.json'.format(areaCode)
if not force and os.path.isfile(areaCode):
return None

baseUrl = 'https://geo.datav.aliyun.com/areas_v3/bound/{0}_full.json'
baseUrl2 = 'https://geo.datav.aliyun.com/areas_v3/bound/{0}.json'

if areaCode[-2:] == '00':
url = baseUrl.format(areaCode)
else:
url = baseUrl2.format(areaCode)
print(url)

response = requests.get(url)
if response.ok and response.status_code == 200:
res_json = response.json()
with open(saveName, 'w', encoding='utf-8') as fp:
json.dump(res_json, fp, ensure_ascii=False)
else:
return areaCode


if __name__ == "__main__":
districts = loadDistrict('area_dict.json')

errorCodes = []
for district in districts:
code = saveGeoJson(district['adcode'])
if not code is None:
errorCodes.append(code)
print(errorCodes)

for district in districts:
code = saveGeoJson(district['adcode'],district['name'],district['level'])
if not code is None:
errorCodes.append(code)
print(errorCodes)

这个时候我们就已经把标准行政区的geojson全部下载到本地了,基于编码,还有文件,结合第二章,我们就已经可以实现中国区域地图的中国、省、市、县区的钻取。

其他下载可参考 《实战 抓取安徽省所有市、县、镇矢量坐标数据》

四、地图区域定制

4.1 geojson解读

首先我们看过地图的geojson文件,其大概如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"adcode": 630105,
"name": "城北区",
"center": [
101.761297,
36.648448
],
"centroid": [
101.699377,
36.680565
],
"childrenNum": 0,
"level": "district",
"acroutes": [
100000,
630000,
630100
],
"parent": {
"adcode": 630100
}
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
101.74077987670899,
36.6482446264917
],
[
101.73983573913573,
36.646617714744369
],
……
]
]
]
}
}
]
}

地图里面核心的几个参数
features[0].properties.adcode : 区域编码
features[0].properties.name : 区域名称
features[0].geometry.coordinates : 区域边沿的经纬度信息

4.1 地图加载

我们打开 https://geojson.io ,或者 https://www.strerr.com/geojson/geojson.html 点击open->file

选择我们需要编辑的地图,这里我们打开了青海地图。

在右边的table区域就会展示当前地图所包含的子模块,然后我们按照下面操作,勾选出我们需要制定的区域。

最后我们在table区域进行区域编辑,就可以制作好我们的地图文件,最后点击save->geojson可以保存我们地图json压缩文件,也可以拷贝右边的json进行保存。