构建电子地图网站——01 软件安装及数据处理
2022-11-09 14:57:23

最近在搜索关于GIS的项目时,在知乎看到了一个构建电子地图网站的教程,里面运用到的QGIS、python语言是之前未接触到的,觉得比较新颖,接下来准备用几天的时间按照教程来实现网站功能,希望能有所收获,这也算是一次小小的实践经验!

参考链接:从零开始,构建电子地图网站—-索引目录 - 知乎 (zhihu.com)

1. 需要用到的软件

后端开发软件:IntelliJ IDEA,语言JAVA。

数据处理软件:pyCharm,语言python。(试试用vscode替代)

前端开发软件:sublime,语言JavaScript。(试试用vscode替代)

数据库:PostgreSQL+PostGIS,语言SQL。

数据查看软件:QGIS。

浏览器:谷歌chrome

新安装了QGIS、PostgreSQL、PostGIS、Navicat Premium

1.1 安装QGIS(易)

这个软件开源且安装简单,相比ArcGIS的各种破解,这个软件是真滴良心!直接从官网下载好电脑对应的版本,之后就是傻瓜的“下一步”式安装了!安装好了之后在“XYZ Tiles”下可以添加在线地图,百度搜索“瓦片地图”对应地图的url添加进去就ok 了!

1.2 安装PostgreSQL+PostGIS+Navicat Premium

特别注意:PostGIS的安装必须在PostgreSQL安装好之后再进行安装!!!两个的版本要对应,推荐安装稍微低一点的版本,高版本易出错,推荐PostgreSQL12+postgis3.0.x+Navicat Premium16

Navicat Premium的版本和

  • 安装PostgreSQL(较易)

    • 下载电脑对应版本的软件

      注:PostGIS的版本要与PostgreSQL版本一致,所以,不推荐下载高版本的。最开始我下载的PostgreSQL15,安装了对应的PostgreSQL版本之后,PostgreSQL中是有根据PostGIS新建的数据库,但是PostgreGIS一直打不开,报错:找不到xxxxx.dll文件

      推荐下载PostgreSQL12

    • 安装过程要设置密码和端口号,端口号默认就行

  • 安装PostgreGIS(较易)

    • 下载与PostgreSQL对应的版本

      可以去官网或者一些网站下载,也可以直接在PostgreSQL安装路径下的“Application Stack Builder”中下载(更推荐后者的下载方式,可以直接选择下载对应的版本,但是这根据网上的说法好像要网不卡)

    • 安装路径要和PostgreSQL的安装路径一样

    • 输入在PostgreSQL安装时设置的username和password,还有端口号

    • 弹出来的弹窗都选择“是”,这是自动设置了环境变量

  • 安装Navicat Premium(要破解)

    参考链接:Navicat 16 最新版Windows安装与永久激活教程 - 知乎 (zhihu.com)

    Navicat的版本可以下载高版本的,当版本较低,而PostSQL版本较高时会连接报错!Navicat的版本应比PostSQL的版本高!

1.3 安装PyCharm

我是直接通过联想的电脑管家直接进行傻瓜式安装

2 数据处理——PostGIS

2.1 数据来源

China Historical GIS(CHGIS (harvard.edu)

里面的数据包含基于wgs84的utf和gbk两种不同的编码,包含:历史行政区划点,到县一级;历史行政区划点,到州一级;历史政区面

2.2 数据入库

  • navicat连接pg库

  • 空间扩展

    • navicat打开数据库postgres,这就是初始化的数据库

    • 新建查询,执行CREATE EXTENSION postgis,为数据库添加空间扩展,会发现数据库中多了一表spatial_ref_sys

2.3 PostGIS工具上传shp

把utf8编码格式、wgs84坐标系的文件上传数据库,因为数据库一般都是utf-8编码的

  • 打开PostGIS Shapefile Import/Export Manager
  • View connection details连接PostSQL
  • Add File上传shp文件再Import一下

3 查看数据

3.1 QGIS查看pg库中的数据

  • QGIS添加一个地图瓦片

    Browser——XYZ Tiles——右键——New Connection,打开 XYZ Connection——把url粘贴进去

  • QGIS连接PostSQL并把shp数据加载进去

    打开Data Source Manger,选择Vector,选择Database,下拉选择PostgreSQL,选择new,输入Connection Information,输入User name和Password,勾选store,Test Connection,显示连接成功,ok,ok,add

3.2 用Sql语法查看

  • 打开Navicat

  • 新建查询

    1
    2
    3
    SELECT st_astext(geom) FROM v6_time_cnty_pts_utf_wgs84 LIMIT 1;
    SELECT st_astext(geom) FROM v6_time_pref_pgn_utf_wgs84 LIMIT 1;
    SELECT st_astext(geom) FROM v6_time_pref_pts_utf_wgs84 LIMIT 1

    查询结果一:POINT(111.079483 39.012409)

    查询结果二:MULTIPOLYGON(((113.549411712145 36.7545434898289,113.543285553768 36.7521524539279,113.560044728209 36.7134634435762,113.55651873219 36.7287158304727,113.552914744172 36.7456842597138,113.549411712145 36.7545434898289)))

    查询结果三:POINT(111.076347 39.017826)

4 数据处理——Python

用PostGIS可以很简单的就上传shp文件,但是由于导入shp文件没有字段备注和表说明,且如果字段名过长,还会截短字段名。所以为了规范化,用py脚本把数据读入PostgreSQL数据库中。

4.1 环境搭建

Python需要3个库包:gdal、shapely、psycopg2

这3个库包可以进入cmd利用命令行进行安装

1
2
3
4
5
pip install gdal

pip install shapely

pip install psycopg2

4.2 Python读取shp文件

读shp文件,并返回一个list方法,再把list写入一个文本txt中

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
# coding=gbk
try:
from osgeo import gdal
from osgeo import ogr
except ImportError:
import gdal
import ogr
# pathStr,shp文件的全路径
def ReadVectorFile(pathStr):

# 返回结果是一个list
result=[]
# 支持中文路径
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "NO")
# 属性表字段支持中文
gdal.SetConfigOption("SHAPE_ENCODING", "")
strVectorFile = pathStr
# 注册所有的驱动
ogr.RegisterAll()
# 打开数据
ds = ogr.Open(strVectorFile, 0)
# 获取该数据源中的图层个数,一般shp数据图层只有一个,如果是mdb、dxf等图层就会有多个
iLayerCount = ds.GetLayerCount()
# 获取第一个图层
oLayer = ds.GetLayerByIndex(0)
# 对图层进行初始化
oLayer.ResetReading()
# 获取图层中的属性表表头并输出,可以定义建表语句
print("属性表结构信息:")
oDefn = oLayer.GetLayerDefn()
iFieldCount = oDefn.GetFieldCount()
for iAttr in range(iFieldCount):
oField = oDefn.GetFieldDefn(iAttr)
print("%s: %s(%d.%d)" % ( \
\
oField.GetNameRef(), \
\
oField.GetFieldTypeName(oField.GetType()), \
\
oField.GetWidth(), \
\
oField.GetPrecision()))
# 输出图层中的要素个数
print("要素个数 = ", oLayer.GetFeatureCount(0))
oFeature = oLayer.GetNextFeature()
# 下面开始遍历图层中的要素,将对象都作为string输出
while oFeature is not None:
# 获取要素中的属性表内容
lineStr=[]
for iField in range(iFieldCount):
lineStr.append(oFeature.GetFieldAsString(iField))
# 获取要素中的几何体
oGeometry = oFeature.GetGeometryRef()
lineStr.append(str(oGeometry))
# print(lineStr)
result.append(lineStr)
# 循环
oFeature = oLayer.GetNextFeature()
print("数据集关闭!")
return result

if __name__ == '__main__':
result=ReadVectorFile( r'E:\develop_gis\map website\data\V6 Time Series County Points\v6_time_cnty_pts_utf_wgs84\v6_time_cnty_pts_utf_wgs84.shp')
f_new = open(
r'E:\develop_gis\map website\data\V6 Time Series County Points\v6_time_cnty_pts_utf_wgs84\v6_time_cnty_pts_utf_wgs84.txt',
'a', encoding='utf-8')
for r in result:
for p in r:
f_new.write(p + '\t')
f_new.write('\n')
f_new.close()

4.3 PostSQL数据库中建表

直接将之前shp生成的txt文件作为源,写入数据库!进行两个步骤的操作:① 建表;② 插入

  • 建表

    建表的时候最好加上字段说明,顺便可以再加上索引

    • public. v6_time_cnty_pts_utf_wgs84表
    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
    --建表
    CREATE TABLE public. v6_time_cnty_pts_utf_wgs84(
    gid SERIAL8 PRIMARY KEY NOT NULL,
    name_py varchar(40),
    name_ch varchar(45),
    name_ft varchar(45),
    x_coor float8,
    y_coor float8,
    pres_loc varchar(60),
    type_py varchar(15),
    type_ch varchar(15),
    lev_rank varchar(1),
    beg_yr int8,
    beg_rule varchar(1),
    end_yr int8,
    end_rule varchar(1),
    note_id int8,
    obj_type varchar(7),
    sys_id int8,
    geo_src varchar(10),
    compiler varchar(12),
    gecomplr varchar(10),
    checker varchar(10),
    ent_date varchar(10),
    beg_chg_ty varchar(21),
    end_chg_ty varchar(30),
    geom geometry
    );
    --建立索引
    CREATE INDEX v6_time_cnty_pts_utf_wgs84_index ON v6_time_cnty_pts_utf_wgs84 USING btree(gid);
    --表说明
    COMMENT ON TABLE public.v6_time_cnty_pts_utf_wgs84 IS '第6版中国历史地理时间序列点数据';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.gid IS '主键ID';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.name_py IS '拼音名称';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.name_ch IS '简体中文名称';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.name_ft IS '繁体中文名称';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.x_coor IS '经度';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.y_coor IS '纬度';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.pres_loc IS '现所在地';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.type_py IS '建制类型拼音';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.type_ch IS '建制类型简体中文';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.lev_rank IS '建制等级';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.beg_yr IS '建制开始时间';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.beg_rule IS '开始时间精度';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.end_yr IS '建制结束时间';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.end_rule IS '结束时间精度';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.note_id IS '系统id';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.obj_type IS 'geometry对象类型';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.sys_id IS '系统id';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.geo_src IS 'geometry数据来源';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.compiler IS '编辑人员';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.gecomplr IS '绘制人员';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.checker IS '审核人员';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.ent_date IS '结束时间';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.beg_chg_ty IS '建制开始原因';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.end_chg_ty IS '建制结束原因';
    COMMENT ON COLUMN public.v6_time_cnty_pts_utf_wgs84.geom IS 'geometry对象';
  • 插入

    插入一条数据进去

    1
    INSERT INTO v6_time_cnty_pts_utf_wgs84(name_py,name_ch,name_ft,x_coor,y_coor,pres_loc,type_py,type_ch,lev_rank,beg_yr,beg_rule,end_yr,end_rule,note_id,obj_type,sys_id,geo_src,compiler,gecomplr,checker,ent_date,beg_chg_ty,end_chg_ty,geom) VALUES('Luowubu','罗婺部','羅婺部',102.40378,25.88668,'云南省禄劝彝族苗族自治县西北七十二里云龙','Bu','部','6',960,null,1253,null,80317,'	POINT',80317,'FROM_FD',null,null,null,null,'新建','撤销',st_geomfromtext('POINT(102.4037799950270653 25.88667999033716072)',4326))

4.4 Python把生成的txt文件写入PostSQL数据库

  • 测试一下能不能连接成功

    1
    2
    3
    4
    5
    6
    # coding=gbk
    import psycopg2

    conn = psycopg2.connect(database="mapdata", user="postgres", password="123456", host="127.0.0.1", port="5432")

    print('连接成功')
  • 连接成功之后,就可以把在4.2生成的txt文件写入PostSQL数据库了

    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
    # 建立游标
    cur = conn.cursor()
    f=open(r'E:\develop_gis\map website\data\V6 Time Series County Points\v6_time_cnty_pts_utf_wgs84\v6_time_cnty_pts_utf_wgs84.txt','r',encoding='utf-8')
    # 按行读入txt
    flines=f.readlines()
    for line in flines:
    # 去掉干扰,切词
    abbrlist=line.replace("'"," ").split('\t')
    # name_py,name_ch,name_ft,x_coor,y_coor,pres_loc,type_py,type_ch,lev_rank,beg_yr,beg_rule,end_yr,end_rule,note_id,obj_type,sys_id,geo_src,compiler,gecomplr,checker,ent_date,beg_chg_ty,end_chg_ty,geom
    name_py='null'
    if (abbrlist[0]!=''):
    name_py="'"+abbrlist[0]+"'"
    name_ch='null'
    if (abbrlist[1]!=''):
    name_ch="'"+abbrlist[1]+"'"
    name_ft='null'
    if (abbrlist[2]!=''):
    name_ft="'"+abbrlist[2]+"'"
    x_coor='null'
    if (abbrlist[3]!=''):
    x_coor=abbrlist[3]
    y_coor='null'
    if (abbrlist[4]!=''):
    y_coor=abbrlist[4]
    pres_loc='null'
    if (abbrlist[5]!=''):
    pres_loc="'"+abbrlist[5]+"'"
    type_py='null'
    if (abbrlist[6]!=''):
    type_py="'"+abbrlist[6]+"'"
    type_ch='null'
    if (abbrlist[7]!=''):
    type_ch="'"+abbrlist[7]+"'"
    lev_rank='null'
    if (abbrlist[8]!=''):
    lev_rank="'"+abbrlist[8]+"'"
    beg_yr='null'
    if (abbrlist[9]!=''):
    beg_yr=abbrlist[9]
    beg_rule='null'
    if (abbrlist[10]!=''):
    beg_rule="'"+abbrlist[10]+"'"
    end_yr='null'
    if (abbrlist[11]!=''):
    end_yr=abbrlist[11]
    end_rule='null'
    if (abbrlist[12]!=''):
    end_rule="'"+abbrlist[12]+"'"
    note_id='null'
    if (abbrlist[13]!=''):
    note_id=abbrlist[13]
    obj_type='null'
    if (abbrlist[14]!=''):
    obj_type="'"+abbrlist[14]+"'"
    sys_id='null'
    if (abbrlist[15]!=''):
    sys_id=abbrlist[15]
    geo_src='null'
    if (abbrlist[16]!=''):
    geo_src="'"+abbrlist[16]+"'"
    compiler='null'
    if (abbrlist[17]!=''):
    compiler="'"+abbrlist[17]+"'"
    gecomplr='null'
    if (abbrlist[18]!=''):
    gecomplr="'"+abbrlist[18]+"'"
    checker='null'
    if (abbrlist[19]!=''):
    checker="'"+abbrlist[19]+"'"
    ent_date='null'
    if (abbrlist[20]!=''):
    ent_date="'"+abbrlist[20]+"'"
    beg_chg_ty='null'
    if (abbrlist[21]!=''):
    beg_chg_ty="'"+abbrlist[21]+"'"
    end_chg_ty='null'
    if (abbrlist[22]!=''):
    end_chg_ty="'"+abbrlist[22]+"'"
    geom='null'
    if (abbrlist[23]!=''):
    geom="st_geomfromtext('"+abbrlist[23]+"',4326)"
    # 拼接sql语句
    sqltxt="INSERT INTO v6_time_cnty_pts_utf_wgs84(" \
    "name_py,name_ch,name_ft,x_coor,y_coor,pres_loc,type_py,type_ch,lev_rank,beg_yr,beg_rule," \
    "end_yr,end_rule,note_id,obj_type,sys_id,geo_src,compiler,gecomplr,checker,ent_date," \
    "beg_chg_ty,end_chg_ty,geom) VALUES("+name_py+","+name_ch+","+name_ft+","+x_coor+","+y_coor+","\
    +pres_loc+","+type_py+","+type_ch+","+lev_rank+","+beg_yr+","+beg_rule+","+end_yr+","+end_rule+","\
    +note_id+","+obj_type+","+sys_id+","+geo_src+","+compiler+","+gecomplr+","+checker+","+ent_date+","\
    +beg_chg_ty+","+end_chg_ty+","+geom+")"
    print(sqltxt)
    # 执行sql
    cur.execute(sqltxt)
    # 关闭连接
    conn.commit()
    conn.close()
    print('插入完成')

4.5 总结流程

用python处理数据的流程:先把python的环境搭建好,然后读取shp文件,与此同时会返回一个txt的文件;接着,在PostSQL中建表,并插入一条数据测试;然后,用python连接数据库,把生成的txt文件写入数据库中!

Python读shp文件==》返回txt文件==》PostSQL数据库中建表==》插入一条数据==》Python连接数据库==》txt文件写入数据库

简单总结就两步:

  • shp文件导出txt
  • txt文件导入pg数据库

5 上传代码到GitHub

  • 在GitHub上面新建一个仓库

  • 选择本地需要上传的文件夹作为本地仓库,右键git bash here

  • 敲命令行上传代码

    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
    -----------------------------
    -----------------------------
    # 上传本地的文件到GitHub仓库

    # 初始化
    git init

    # 把GitHub上面的仓库克隆下来
    git clone https://github.com/xxxxx/historymap.git

    # 把本地文件添加到暂存区
    git add .

    # 将暂存区的文件添加到仓库中
    git commit -m "first commit"

    # 将仓库中的文件提交提交到远程仓库
    git push origin main

    ------------------------------
    ------------------------------
    # 删除GitHub仓库中的文件

    # 查看有哪些文件
    dir

    # 删除一个文件
    git rm -r --cached xxx
    git rm -r --cached xxxx
    git rm -r --cached xxxxx

    # 提交删除信息,添加操作说明
    git commit -m "delete"

    # push到GitHub仓库
    git push

  • git push origin main/git push -u origin main和git push -u origin master有什么区别

    为什么后者代码数据传不上去,参考链接:git push -u origin master 与git push –set-upstream origin master_yzpyzp的博客-CSDN博客

    • 新建的仓库的默认分支从master变成了main,至于origin,就是远程仓库的别名,github把远程仓库的名称默认称为origin,自己也可以改为其他名称,就像GitHub之前仓库的默认分支叫做master,而现在改为main

    • git push -ugit push --set-upstream的缩写版本;(upstream 是指本地分支与远程仓库中的分支之间的流通道,建立流通道就是建立本地分支与远程分支的关联,建立之后,后续就可以直接使用git push指令把本地分支的commit推到远程分支中)

    • git push -u origin main的作用:

      • 先把本地的当前分支推送到远程仓库origin的main分支

      • 再把本地的当前分支关联到远程仓库origin的main分支

        相当于git push origin main加上git branch --set-upstream-to=origin/main main的作用,即先把本地分支push到远程仓库中,然后再建立本地分支与远程分支的关联。

    • 不带任何参数的git push,默认只推送当前分支

    • 推送代码时,经常会报以下两个错误:

      • ```
        fatal: unable to access ‘https://github.com/xxxxx/historymap.git/': Failed to connect to github.com port 443 after 22072 ms: Timed out
        1
        2
        3
        4

        - ```
        error: src refspec master does not match any
        error: failed to push some refs to 'https://github.com/xxxxx/historymap.git'

      这是网络的原因,GitHub网站经常会出现打不开的问题