地理空间数据–矢量#

地理空间数据是描述位于地球表面或附近位置的对象、事件或其他要素的信息。地理空间数据通常将位置信息(例如坐标)和属性信息(相关物体、事件或现象的特征)与时间信息(位置和属性存在的时间或寿命)相结合。

地理空间数据通常涉及从许多不同来源以不同格式收集到的大量空间数据,并且可以包括人口普查数据,卫星图像和天气数据等信息。

用于收集和分析地理空间数据的技术我们称之为地理空间技术。涉及地理空间技术的软件叫GIS软件,GIS(Geographic Information System),简单理解就是帮助我们处理这些地理空间数据的。常见的GIS工具有ArcGIS、QGIS等,未来我们都会接触到的。

地理空间数据的两个主要类型为栅格数据和矢量数据。

1 矢量数据#

矢量数据就是使用点、线和面表示地理要素的位置和形状的数据。

位置: 线和面都由点组成,这些点都是由空间坐标的

形状: 使用点表示的水文站点、由线表示的河流和由面表示流域

  • 点 : 每个点都由单个X、Y坐标定义。点是0维,既没有长度也没有面积。

  • 线 : 线由许多(至少2个)连接的点组成。线是1维,只能用于测量长度。

  • 面 : 面由3个或更多个连接且闭合的点组成。面数据是2维的,可测量面积和周长。

2 存储矢量数据的格式#

矢量数据有很多类型,如下图红框所示。

我们常见的有Shapefile格式、GeoJSON格式、GML格式、KML格式等。

  • Shapefile 格式是一种非常流行的地理空间矢量数据格式,用于地理信息系统 (GIS) 软件,用于存储地理特征的位置、形状和属性。Shapefile 实际上由多个文件组成,除了一个包含实际几何数据的.shp文件外,还需要其他文件共同构成 Shapefile 文件。

  1. .shp——图形格式,保存元素的几何实体,点线面多点多线多面。

  2. .shx——图形索引格式。记录每一个几何体在shp文件之中的位置,能够加快向前或向后搜索一个几何体的效率,就是快速展示出前后的几何体。

  3. .dbf——属性数据格式,以特定格式(dBase IV的数据表格式)存储每个几何形状的属性数据。

以上是Shapefile必备文件,以下是可选文件

  1. .prj——投影文件,用于保存地理坐标系统与投影信息(投影:将三维空间几何体投影成一个二维平面,.shp展示的是一个二维图形)

  2. .sbx——几何体的空间索引

  3. .fbx——只读的Shapefiles的几何体的空间索引

  4. .mxs——可读写Shapefile文件的地理编码索引(ODB格式)

  5. .ixs——可读写Shapefile文件的地理编码索引

  6. .cpg— 用于描述.dbf文件的代码页,指明其使用的字符编码。

  7. .sbn——空间索引文件,用于优化空间查询。此文件类型与.sbx 文件一起保存。这两个文件组成一个形状索引来加速空间查询。

  8. .sbx——类似于.sbn 文件,它们加快了加载时间。它与.sbn 文件一起使用以优化空间查询。

  • GeoJSON是一种轻量级JSON((JavaScript object notation,是一种有条理易于访问的存储信息的方法),是各种地理数据结构进行编码的格式。GeoJSON对象可以表示几何、特征或者特征集合。GeoJSON支持下面几何类型:点、线、面、多点、多线、多面和几何集合。GeoJSON里的特征包含一个几何对象和其他属性,特征集合表示一系列特征。

  • GML是一个基于XML的地理信息编码标准,GML以文本的形式表示地理信息。XML是一个独立于软件和硬件的工具,用于存储和传输数据。

  • KML是基于XML的,主要用于谷歌地球。KML是一种XML符号,用于表达二维地图和三维浏览器中的地理注释和可视化。KML规定了一套有趣的特征,如地点标记、图像、多边形、文本描述和许多在许多地理空间软件中显示的特征。KML的扩展名是.kmz。

其中我们最常见的还是Geojson和shapefile。虽然两个文件格式是平行关系,不过是可以将Geojson格式转换为shapefile格式的,因为空间地理数据最基本的元素就是坐标,他们采取编码的形式不同而已。

以上的动图是我们提供的geojson文件在QGIS中打开的样式,可以看出是个流域。

现在我们演示下在Python中对geojson与shapefile进行读写操作。

3 读写Geojson#

Python自带json模块,因此我们可以直接导入json工具包,使用json.load()函数即可读取Geojson。

import json 
# Opening JSON file
with open('../data/stationbasins.geojson', 'r') as openfile: 
    # Reading from json file
    json_object = json.load(openfile) 
print(json_object)
print(type(json_object))
{'displayFieldName': 'river', 'fieldAliases': {'grdc_no': 'grdc_no', 'river': 'river', 'station': 'station', 'altitude': 'altitude', 'dist_km': 'dist_km', 'area_hys': 'area_hys'}, 'geometryType': 'esriGeometryPolygon', 'spatialReference': {'wkid': 4326, 'latestWkid': 4326}, 'fields': [{'name': 'grdc_no', 'type': 'esriFieldTypeDouble', 'alias': 'grdc_no'}, {'name': 'river', 'type': 'esriFieldTypeString', 'alias': 'river', 'length': 80}, {'name': 'station', 'type': 'esriFieldTypeString', 'alias': 'station', 'length': 80}, {'name': 'altitude', 'type': 'esriFieldTypeDouble', 'alias': 'altitude'}, {'name': 'dist_km', 'type': 'esriFieldTypeDouble', 'alias': 'dist_km'}, {'name': 'area_hys', 'type': 'esriFieldTypeDouble', 'alias': 'area_hys'}], 'features': [{'attributes': {'grdc_no': 2181200.0, 'river': 'ZAGUNAO HE', 'station': 'ZAGUNAO', 'altitude': -999.0, 'dist_km': 11.7, 'area_hys': 2400.0}, 'geometry': {'rings': [[[102.8958, 31.1958], [102.8813, 31.198], [102.8294, 31.2052], [102.8105, 31.2144], [102.8103, 31.2228], [102.8265, 31.2441], [102.8098, 31.2615], [102.7813, 31.2688], [102.748, 31.2734], [102.752, 31.298], [102.7592, 31.3118], [102.7482, 31.3224], [102.7396, 31.3355], [102.7262, 31.3443], [102.7146, 31.3563], [102.6939, 31.3717], [102.6896, 31.4021], [102.685, 31.4157], [102.6855, 31.4396], [102.6605, 31.4477], [102.6604, 31.4564], [102.6768, 31.4722], [102.6848, 31.4956], [102.6688, 31.5266], [102.6686, 31.5354], [102.669, 31.552], [102.6564, 31.5641], [102.6508, 31.5812], [102.6414, 31.5758], [102.6286, 31.5563], [102.6108, 31.5636], [102.601, 31.6021], [102.6063, 31.6229], [102.6104, 31.6521], [102.6269, 31.6601], [102.6354, 31.6767], [102.6395, 31.7105], [102.6439, 31.7274], [102.6435, 31.7437], [102.6521, 31.7604], [102.6396, 31.7847], [102.6437, 31.798], [102.6539, 31.813], [102.6604, 31.8396], [102.673, 31.8562], [102.6854, 31.8772], [102.7064, 31.8768], [102.7187, 31.8896], [102.7482, 31.8937], [102.7789, 31.9145], [102.798, 31.9105], [102.8288, 31.898], [102.8359, 31.9023], [102.8451, 31.9119], [102.8562, 31.9062], [102.8747, 31.9017], [102.8854, 31.8854], [102.9064, 31.8562], [102.906, 31.8361], [102.9114, 31.8201], [102.8996, 31.8027], [102.9078, 31.7866], [102.8688, 31.7323], [102.8729, 31.727], [102.8792, 31.7272], [102.8875, 31.727], [102.8975, 31.7272], [102.9108, 31.7208], [102.9103, 31.6979], [102.923, 31.6938], [102.9437, 31.6854], [102.961, 31.6798], [102.9688, 31.6646], [102.9768, 31.648], [103.002, 31.6294], [103.0021, 31.6229], [103.0019, 31.6105], [103.0063, 31.6063], [103.0104, 31.5979], [103.0188, 31.5855], [103.0237, 31.5703], [103.0355, 31.5646], [103.0395, 31.5604], [103.0473, 31.5529], [103.0575, 31.5331], [103.0438, 31.5145], [103.0357, 31.5068], [103.0351, 31.4775], [103.0591, 31.4526], [103.0771, 31.4438], [103.0937, 31.4312], [103.1104, 31.4315], [103.1271, 31.4312], [103.1313, 31.4313], [103.1355, 31.4312], [103.16, 31.4479], [103.1649, 31.4436], [103.1645, 31.4231], [103.1726, 31.4147], [103.1893, 31.4066], [103.198, 31.3938], [103.202, 31.3854], [103.2104, 31.3692], [103.2021, 31.3604], [103.1979, 31.3563], [103.1934, 31.3335], [103.1687, 31.3188], [103.161, 31.303], [103.1404, 31.2815], [103.1186, 31.2981], [103.1105, 31.2895], [103.0931, 31.2767], [103.0815, 31.2646], [103.0604, 31.2729], [103.048, 31.2813], [103.0275, 31.2912], [103.0222, 31.3186], [103.0025, 31.319], [102.9855, 31.2859], [102.9813, 31.2645], [102.9624, 31.2584], [102.9563, 31.252], [102.9438, 31.2523], [102.9271, 31.2437], [102.9113, 31.236], [102.9053, 31.205], [102.8979, 31.198], [102.8958, 31.1958]]]}}]}
<class 'dict'>

我们将json对象(仔细看,我们用的名称还是json_object)全部读取出来了,可以看出里面有很多信息,例如属性、坐标等等。可以看出json对象使用花括号{}定义的,中间使用的是键值。

同时我们还打印出json的类型,输出是’dict’,是’dictionary’的简称,这是Python中数据的一种类型,我们也遇见过,就在上一节。字典的特征也是使用大括号{},中间也是键与值,例如{'键1':'值1','键2':'值2'},键值之间用英文冒号:隔开,键值与键值之间用逗号隔开,,当然也是英文逗号!其中键是一个字符串,它的值可以是任何基本类型(例如 int、string、null)或复杂数据类型(例如数组)。而且我们也可以看出,读取出来不光有{}还有[]标记符,以下示例中101没用引号'',在Python的数据章节这些我们都会具体介绍。

我们来写一个简单的json对象:

{
 'id':101,
 'company' : 'GeeksForGeeks'
}

复杂json对象是那些在一个对象中嵌套一个对象,再来写一个复杂的json对象:

{
 'id':101,
 'company' : 'GeeksForGeeks',
 'Topics' : { 'Data Structure',
              'Algorithm',
              'Gate Topics' }
}

我们用Python读取json,将json对象转换为Python对象,我们将其称为反序列化。

那么序列化就是将python对象转化为json对象,即用python语言将数据写入json对象中。

以下是python对象类型对应的json对象类型,以后在我们了解python数据类型后对这些就很清楚了,这里我们先暂时了解以下。

python object

json object

dict

object

list,tuple

array

str

string

int,long,float

numbers

True

ture

False

false

None

null

json工具包中的.dump()或者.dumps()函数将python对象转化为各自json对象,有助于将python对象(字典)转化为json对象,不过他需要两个参数(我们经常见的函数:y = ax1 + bx2,其中x1和x2就是函数的参数):

  • dictionary:转为json对象的字典名称

  • indent:定义缩进的单位数

以下我们创建一个json文件(当然也可以是geojson文件,geojson文件是一个轻量级的json文件)。

当我们对json文件进行写入时,发现没有这个文件,那么python就会自动创建文件,以下我们就创建一个新的json文件,不对我们下载的json文件进行写入更改了。

import json 
#撰写数据(字典)
dictionary = {
    'river': 'ZAGUNAO HE',
    'station': 'ZAGUNAO', 
    'altitude': -999.0,
    'dist_km': 11.7,
    'area_hys': 2400.0,
    'geometry': {'rings': [[[102.8958, 31.1958], [102.8813, 31.198], [102.8294, 31.2052]]]}
} 
# Serializing json
json_object = json.dumps(dictionary, indent=4) 
# Writing to sample.json
with open("../data/stationbasins_modified.geojson", "w") as outfile:
    outfile.write(json_object)

读取我们创建的json文件,读取的内容和我们创建dicttionary的是一样的。

import json 
# Opening JSON file
with open('../data/stationbasins_modified.geojson', 'r') as openfile: 
    # Reading from json file
    json_object = json.load(openfile) 
print(json_object)
{'river': 'ZAGUNAO HE', 'station': 'ZAGUNAO', 'altitude': -999.0, 'dist_km': 11.7, 'area_hys': 2400.0, 'geometry': {'rings': [[[102.8958, 31.1958], [102.8813, 31.198], [102.8294, 31.2052]]]}}

以上是我们对geojson文件的读写,但是这种方式对后续的GIS分析并不友好,接下来看看使用GIS相关的计算包读写这类数据。

以下我们将以shapefile这类更常见的格式为例介绍。

4 读写.shp#

geopandas可以使用geopandas.read_file()函数读取几乎任何矢量的空间数据格式,包括shapefile、geojson。

我们先试试把geojson转为shpafile,即先看看写一个shp文件。

import geopandas
import matplotlib.pyplot as plt

def saveShapefile(file_path, save_path):
    try:
        data = geopandas.read_file('../data/stationbasins.geojson')
        ax = data.plot()
        plt.show()  # 可视化,即显示生成的图片
        data.to_file(save_path, driver='ESRI Shapefile', encoding='utf-8')
        print("--保存成功,文件存放位置:"+save_path)
    except Exception as ex:
        print("--------JSON文件不存在,请检查后重试!----")
        pass
saveShapefile('../data/stationbasins.geojson', '../data/basin')
../_images/3c32582730fc815614afb0fe28309d2e59cee3404223c4ecfa4abfe0ff5e4162.png
--保存成功,文件存放位置:../data/basin

接下来我们再试试读取shpfile

import geopandas
df_shp = geopandas.read_file('../data/basin/basin.shp')
df_shp
grdc_no river station altitude dist_km area_hys geometry
0 2181200.0 ZAGUNAO HE ZAGUNAO -999.0 11.7 2400.0 POLYGON ((102.89580 31.19580, 102.88130 31.198...

我们再用geopandas读取刚刚的geojson文件,可以看到和读取shpfile是一样的

import geopandas
df_json = geopandas.read_file('../data/stationbasins.geojson')
df_json
grdc_no river station altitude dist_km area_hys geometry
0 2181200.0 ZAGUNAO HE ZAGUNAO -999.0 11.7 2400.0 POLYGON ((102.89580 31.19580, 102.88130 31.198...