Python地理数据处理 五:矢量文件处理

写在前面

  不同的矢量文件格式不是通用的,每种格式都有其特定的应用。文件格式还需要考虑到OGR处理数据的能力,因为每种驱动的能力不同。针对某数据集,我们要知道能做什么和不能做什么。

1. 矢量文件格式

1.1 基于文件的格式

  基于文件的格式是指用磁盘驱动器上一个或多个文件组成,并且可以很容易地从一个位置转移到另一个位置。某些格式是专有的并限制于少数软件,有些是开放的标准,所有人都可以编写软件来使用他们。开放格式如:GeoJSON、KML、GML、shapefile和Spatialite。
  空间数据可以存储在Excel电子表格、逗号或制表符分隔的文件或其他类似格式中,大多数空间数据都是使用在专门面向GIS数据格式中。这些格式中有几种是纯文本格式,说明可以在任何文本编辑器中将其打开并查看;其他格式则是二进制文件,需要能够解析的软件。
  纯文本文件的一个优点: 可以在文本编辑器中打开并检查内容,甚至可以编辑。
  显示一个GeoJSON文件,该文件包含两个以点表示的城市。

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": { "NAME": "Geneva", "PLACE": "city" },
      "geometry": {
        "type": "Point",
        "coordinates": [ 6.1465886, 46.2017589 ]
      }

    },
    {
      "type": "Feature",
      "properties": { "NAME": "Lausanne", "PLACE": "city" },
      "geometry": {
        "type": "Point",
        "coordinates": [ 6.6327025, 46.5218269 ]
      }
    },
  ]
}

  说明可以使用文本编辑器打开并且编辑文件,而不是使用GIS软件,可以容易调整city的名字和某点的坐标。
  纯文本格式(GeoJSON、KML、GML)优点:

  1. 适合传输少量数据,及web应用程序,但不利于数据分许。
  2. 允许不同的几何类型在同一数据集中,但GIS软件没有重视这一点,不能混合。如,一个shp文件只可以包含一种几何类型(点、线、面二者不能同时存在)
  3. GeoJSON文件可以包含3种几何体的组合,但必须存在于2个不同shp文件中。

  当涉及数据分析时,不能使用纯文本文件,因为它没有索引功能。索引用于快速搜索和访问数据,且对于大数据集十分重要,分为:

  1. 属性索引:搜索要素的
  2. 空间索引:储存数据集中要素的空间位置信息

  Spatialite数据库文件,基于文件的格式,包含多个不同几何形状的数据集:
在这里插入图片描述

1.2 多用户数据库格式

  主流客户服务器端空间数据库的解决方案:PostGIS空间拓展PostgreSQL、ArcSDE、SQL Server、Oracle Spatial和Graph。

2. 处理更多的数据格式

  不同数据源之间,打开数据源,读取数据的方法是一致的。

import os
from osgeo import ogr
import ospybook as pb
from ospybook.vectorplotter import VectorPlotter

data_dir = r'E:\Google chrome\Download\gis with python\osgeopy data'

# 编写一个函数,来输出数据源的图层
def print_layers(fn):
    ds = ogr.Open(fn, 0)
    if ds is None:
        raise OSError('Could not open {}'.format(fn))
    
    # 获取数据源包含的图层数,并获取每一个图层的索引
    for i in range(ds.GetLayerCount()): 
        lyr = ds.GetLayer(i)
        print('{0}: {1}'.format(i, lyr.GetName()))


# 输出函数
print_layers(os.path.join(data_dir, 'Washington', 'large_cities.geojson'))
0: large_cities

2.1Spatialite

  该数据源可以包含许多不同的图层,所有图层都具有唯一的名称。

>>> import ospybook as pb
>>> pb.print_layers(r'E:\Google chrome\Download\global\natural_earth_50m.sqlite')
0: countries (MultiPolygon)
1: populated_places (Point)

  获取图层时,最好使用图层名,而不是索引,因为当其他图层添加到数据源时,索引可能会变化。

import os
from osgeo import ogr
import ospybook as pb
from ospybook.vectorplotter import VectorPlotter

data_dir = r'E:\Google chrome\Download'

ds = ogr.Open(os.path.join(data_dir, 'global', 'natural_earth_50m.sqlite'))
lyr = ds.GetLayer('populated_places')

vp = VectorPlotter(True)
vp.plot(lyr, 'bo')
vp.draw()

  natural_earth_50m.sqlite中的populated_places图层:

在这里插入图片描述

ogrinfo:

  1.使用ogrinfo得到具体的数据源和数据层的信息:

C:\Users\Amazon\AppData\Local\Programs\Python\Python39\Lib\site-packages\osgeo>ogrinfo natural_earth_50m.sqlite
INFO: Open of `natural_earth_50m.sqlite'
      using driver `SQLite' successful.
1: countries (Multi Polygon)
2: populated_places (Point)

注:需要将natural_earth_50m.sqlite文件复制到osgeo目录下

  2.使用ogrinfo看到图层的元数据,及所有的属性数据。
获取natural earth SQLite数据库中countries图层的一个信息汇总(-so),包括元数据(范围、空间参考、属性字段列表、数据类型):

C:\Users\Amazon\AppData\Local\Programs\Python\Python39\Lib\site-packages\osgeo>ogrinfo -so natural_earth_50m.sqlite countries
INFO: Open of `natural_earth_50m.sqlite'
      using driver `SQLite' successful.

Layer name: countries
Geometry: Multi Polygon
Feature Count: 241
Extent: (-180.000000, -89.998940) - (180.000000, 83.599609)
Layer SRS WKT:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["longitude",east,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["latitude",north,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]
Data axis to CRS axis mapping: 1,2
FID Column = PK_UID
Geometry Column = Geometry
scalerank: Integer (0.0)
featurecla: String (0.0)
labelrank: Real (0.0)
sovereignt: String (0.0)
sov_a3: String (0.0)
adm0_dif: Real (0.0)
level: Real (0.0)
type: String (0.0)
admin: String (0.0)
adm0_a3: String (0.0)
<snip>

  3.显示图层第一个要素countries的所有属性值:
  -q:不要输出元数据
  -geom=NO:不要输出几何要素的文本表示

C:\Users\Amazon\AppData\Local\Programs\Python\Python39\Lib\site-packages\osgeo>ogrinfo -fid 1 -q -geom=NO natural_earth_50m.sqlite countries

Layer name: countries
OGRFeature(countries):1
  scalerank (Integer) = 3
  featurecla (String) = Admin-0 country
  labelrank (Real) = 5
  sovereignt (String) = Netherlands
  sov_a3 (String) = NL1
  adm0_dif (Real) = 1
  level (Real) = 2
  type (String) = Country
  admin (String) = Aruba
  adm0_a3 (String) = ABW
  geou_dif (Real) = 0
  geounit (String) = Aruba
  gu_a3 (String) = ABW
  su_dif (Real) = 0
  subunit (String) = Aruba
  su_a3 (String) = ABW
  brk_diff (Real) = 0
  name (String) = Aruba
  name_long (String) = Aruba
  brk_a3 (String) = ABW
  brk_name (String) = Aruba
<snip>

2.2 文件夹作为数据源(shapefile和CSV)

  某些情况下,OGR会将整个文件夹作为数据源。shapefile驱动和逗号分隔的文本文件驱动(.csv),都可用于单个文件或作为数据源的整个文件夹。

>>> pb.print_layers(os.path.join(data_dir, 'US'))
0: citiesx020 (Point)
1: cities_albers (Point)
2: countyp010 (Polygon)
3: states_48 (Polygon)
4: us_volcanos (Point)
5: us_volcanos_albers (Point)
6: wtrbdyp010 (Polygon)

  将shp或者csv作为数据源:

>>> pb.print_layers(os.path.join(data_dir, 'US', 'volcanoes.csv'))
0: volcanoes (None)
>>> pb.print_layers(os.path.join(data_dir, 'US', 'citiesx020.shp'))
0: citiesx020 (Point)

  在文件夹中获取csv图层:

pb.print_layers(os.path.join(data_dir, 'US', 'csv'))
# 结果无法获取

  使用CSV驱动打开文件夹获取

>>> driver = ogr.GetDriverByName('CSV')
>>> ds = driver.Open(os.path.join(data_dir, 'US'))
>>> print(ds)
None

:以上文件都是.shp文件,CSV驱动需要文件夹中所有文件都是csv格式。和shp文件打开一样,把csv文件本身当成拥有唯一图层的数据源。

2.3 Esri文件地理数据库

  Esri文件中只有要素类名称,和PostGIS完全不同。OGR中要素数据集的名字没有出现在图层名中:

在这里插入图片描述

import os
from osgeo import ogr
import ospybook as pb
# from ospybook.vectorplotter import VectorPlotter

data_dir = r'E:\Google chrome\Download'
fn = os.path.join(data_dir, 'global', 'natural_earth.gdb')
pb.print_layers(fn)

  结果:

0: countries_10m (MultiPolygon)
1: populated_places_10m (Point)
2: countries_110m (MultiPolygon)
3: populated_places_110m (Point)

  文件地理数据库有两个不同的驱动:

  1. 只读OpenFileGDB驱动被默认编译进OGR
  2. 读/写FileGDB驱动没有编译进OGR,因为需要Esri第三方库

  若不能访问FileGDB驱动(无第三方库),可以用OpenFileGDB打开地理数据库,再复制到可编辑文件格式中。

import os
from osgeo import ogr
import ospybook as pb
from ospybook.vectorplotter import VectorPlotter

data_dir = r'E:\Google chrome\Download'
fn = os.path.join(data_dir, 'global', 'natural_earth.gdb')
pb.print_layers(fn)

# 通过要素类名字,获取特征数据集内部的一个图层
ds = ogr.Open(fn)
lyr = ds.GetLayer('countries_10m')

# 输出一些属性,验证正常运行
pb.print_attributes(lyr, 5, ['NAME', 'POP_EST'])

# 从geodatabase导出一个特性类到一个shapefile文件
out_folder = os.path.join(data_dir, 'global')
gdb_ds = ogr.Open(fn)
gdb_lyr = gdb_ds.GetLayerByName('countries_110m')
shp_ds = ogr.Open(out_folder, 1)
shp_ds.CopyLayer(gdb_lyr, 'countries_110m')
del shp_ds, gdb_ds

  CopyLayer():允许复制整个图层的内容到新的数据源中的或相同数据源的不同图层中。

  当有FileGDB驱动时:

  1. 打开原始数据,检查文件地理数据库是否存在
  2. 若存在,打开要写入的地理数据库
  3. 若不存在,则创建
  4. 遍历原始数据源的all layer,copy到地理数据库中,并保持相同的文件名
import os
from osgeo import ogr


def layers_to_feature_dataset(ds_name, gdb_fn, dataset_name):
    """3个参数:原始数据源路径、文件数据库路径、目标要素集名字"""
    """复制图层到文件地理数据库中的特征数据集"""

    # 打开输入数据源
    in_ds = ogr.Open(ds_name)
    if in_ds is None:
        raise RuntimeError('Could not open datasource')

    # 打开geodatabase,如果它不存在,就创建它。
    gdb_driver = ogr.GetDriverByName('FileGDB')
    if os.path.exists(gdb_fn):
        gdb_ds = gdb_driver.Open(gdb_fn, 1)
    else:
        gdb_ds = gdb_driver.CreateDataSource(gdb_fn)
    if gdb_ds is None:
        raise RuntimeError('Could not open file geodatabase')

    # 创建一个选项列表
    # 以便将特性类保存在特性数据集中。
    options = ['FEATURE_DATASET=' + dataset_name]

    # 循环遍历输入数据源中的图层,并复制每个图层到地理数据库中
    for i in range(in_ds.GetLayerCount()):
        lyr = in_ds.GetLayer(i)
        lyr_name = lyr.GetName()
        print('Copying ' + lyr_name + '...')
        gdb_ds.CopyLayer(lyr, lyr_name, options)

  复制文件夹中的所有shp到一个地理数据库中:

import os
import listing4_2

data_dir = r'E:\Google chrome\Download'
shp_folder = os.path.join(data_dir, 'global')
gdb_fn = os.path.join(shp_folder, 'osgeopy-data.gdb')
listing4_2.layers_to_feature_dataset(shp_folder, gdb_fn, 'global')

2.4 网络要素服务(WFS)

  访问在线服务,网络要素服务(WFS):

# 需要翻墙才能实现,我还不能完成这一步
>>> url = 'WFS:http://gis.srh.noaa.gov/arcgis/services/watchWarn/MapServer/WFSServer'
>>> pb.print_layers(url)
# 从WFS获取第一个警告
# 使用GetNextFeature
ds = ogr.Open(url)
lyr = ds.GetLayer(1)
print(lyr.GetFeatureCount())
feat = lyr.GetNextFeature()
print(feat.GetField('prod_type'))
# 获取数据源的前几个要素
# 通过将返回的特性限制为1,从WFS获得第一个警告
ds = ogr.Open(url + '?MAXFEATURES=1')
lyr = ds.GetLayer(1)
print(lyr.GetFeatureCount())
# 绘制WatchesWarnings图层
vp = VectorPlotter(False)
ds = ogr.Open(os.path.join(data_dir, 'US', 'states_48.shp'))
lyr = ds.GetLayer(0)
vp.plot(lyr, fill=False)
ds = ogr.Open(url)
lyr = ds.GetLayer('watchWarn:WatchesWarnings')
vp.plot(lyr)
vp.draw()

  实时数据:
在这里插入图片描述
  从WFS中保存实时数据,并使用Folium构建简单的网络模块,所以这里需要安装Folium模块和Jinja2模块(支持Folium正常运行的模块)。
  首先,从WFS中取出流量测量数据,并保存为GeoJSON格式的函数;再创建显示流量测量数据的网络地图函数;获取几何对象;使用函数格式化WFS请求和地图数据。

# 从WFS数据制作webmap的脚本
import os
import urllib
from osgeo import ogr
import folium


def get_bbox(geom):
    """从几何要素中获取矩形边框"""
    return '{0},{2},{1},{3}'.format(*geom.GetEnvelope())

def get_center(geom):
    """返回几何图形的中心点"""
    centroid = geom.Centroid()
    return [centroid.GetY(), centroid.GetX()] # (y,x)是folium所需的地理坐标形式

def get_state_geom(state_name):
    """返回一个州的几何图形"""
    ds = ogr.Open(r'E:\Google chrome\Download\gis with python\osgeopy data\US\states.geojson')
    if ds is None:
        raise RuntimeError(
            '无法打开状态数据集,路径正确吗?')
    lyr = ds.GetLayer()
    lyr.SetAttributeFilter('state = "{0}"'.format(state_name))
    feat = next(lyr)
    return feat.geometry().Clone()

def save_state_gauges(out_fn, bbox=None):
    """保存流量测量数据到geojson文件"""
    url = 'http://gis.srh.noaa.gov/arcgis/services/ahps_gauges/' + \
          'MapServer/WFSServer'
    parms = {
    
    
        'version': '1.1.0',
        'typeNames': 'ahps_gauges:Observed_River_Stages',
        'srsName': 'urn:ogc:def:crs:EPSG:6.9:4326',
    }
    if bbox:
        parms['bbox'] = bbox
    try:
        request = 'WFS:{0}?{1}'.format(url, urllib.urlencode(parms))
    except:
        request = 'WFS:{0}?{1}'.format(url, urllib.parse.urlencode(parms))
    wfs_ds = ogr.Open(request)
    if wfs_ds is None:
        raise RuntimeError('无法打开WFS.')
    wfs_lyr = wfs_ds.GetLayer(0)

    driver = ogr.GetDriverByName('GeoJSON')
    if os.path.exists(out_fn):
        driver.DeleteDataSource(out_fn)
    json_ds = driver.CreateDataSource(out_fn)
    json_ds.CopyLayer(wfs_lyr, '')

def make_map(state_name, json_fn, html_fn, **kwargs):
    """制作一张folium地图"""
    geom = get_state_geom(state_name)
    save_state_gauges(json_fn, get_bbox(geom))
    fmap = folium.Map(location=get_center(geom), **kwargs)
    fmap.geo_json(geo_path=json_fn)
    fmap.create_map(path=html_fn)


if __name__ == "__main__":
    os.chdir(r'E:\Google chrome\Download\put_out')
    make_map('Oklahoma', 'ok.json', 'ok.html', zoom_start=7)

  GeoJSON文件制作的一个简单的Folium地图:
在这里插入图片描述

  folium 显示地图的类为 folium.Map,类的声明如:

class folium.folium.Map(location=None, width='100%', height='100%', left='0%', top='0%', position='relative', tiles='OpenStreetMap', attr=None, min_zoom=0, max_zoom=18, zoom_start=10, min_lat=-90, max_lat=90, min_lon=-180, max_lon=180, max_bounds=False, crs='EPSG3857', control_scale=False, prefer_canvas=False, no_touch=False, disable_3d=False, png_enabled=False, zoom_control=True, **kwargs)

参数:

  1. location :经纬度,list 或者 tuple 格式,顺序为 latitude, longitude
  2. zoom_start :缩放值,默认为 10,值越大比例尺越小,地图放大级别越大
  3. tiles :显示样式,默认OpenStreetMap,也就是开启街道显示
  4. crs :地理坐标参考系统,默认为"EPSG3857"

  Folium地图自定义标记:

import os
from osgeo import ogr
import folium
import listing4_3

# 基于洪水状态的标记颜色
colors = {
    
    
    'action': '#FFFF00',
    'low_threshold': '#734C00',
    'major': '#FF00C5',
    'minor': '#FFAA00',
    'moderate': '#FF0000',
    'no_flooding': '#55FF00',
    'not_defined': '#B2B2B2',
    'obs_not_current': '#B2B2B2',
    'out_of_service': '#4E4E4E'
}

# 为要素创建弹出文本
def get_popup(attributes):
    """Return popup text for a feature."""
    template = '''{location}, {waterbody}</br>
                  {observed} {units}</br>
                  {status}'''
    return template.format(**attributes)

#为地图添加标记
def add_markers(fmap, json_fn):
    ds = ogr.Open(json_fn)
    lyr = ds.GetLayer()
    for row in lyr:
        geom = row.geometry()
        color = colors[row.GetField('status')]
        fmap.circle_marker([geom.GetY(), geom.GetX()],
                           line_color=color,
                           fill_color=color,
                           radius=5000,
                           popup=get_popup(row.items()))


def make_map(state_name, json_fn, html_fn, **kwargs):
    """制作一张地图"""
    geom = listing4_3.get_state_geom(state_name)
    listing4_3.save_state_gauges(json_fn, listing4_3.get_bbox(geom))
    fmap = folium.Map(location=listing4_3.get_center(geom), **kwargs)
    add_markers(fmap, json_fn)
    fmap.create_map(path=html_fn)


os.chdir(r'D:\Dropbox\Public\webmaps')
make_map('Oklahoma', 'ok2.json', 'ok2.html',
         zoom_start=7, tiles='Stamen Toner')

  地图:
在这里插入图片描述

3.测试格式能力

  不是所有的操作对所有的数据格式和驱动都有效,用于测试能力的常量:

驱动功能 OGR常数
创建新的数据源 ODrCCreateDataSource
删除现有数据源 ODrCDeleteDataSource
数据源功能 OGR常数
建立新图层 ODsCCreateLayer
删除现有图层 ODsCDeleteLayer
图层功能 OGR常数
使用GetFeature读取随机功能 OLCRandomRead
更新现有功能 OLCRandomWrite
新增功能 OLCSequentialWrite

  检查一个给定的功能,要在驱动、数据源或图层上调用函数,并传递给表格中的一个常量作为参数。若操作允许,则返回真值,否则返回假值。

import os
from osgeo import ogr
import ospybook as pb
from ospybook.vectorplotter import VectorPlotter

# 为了只读而打开数据源
data_dir = r'E:\Google chrome\Download'
dirname = os.path.join(data_dir, 'global')
ds = ogr.Open(dirname)
print(ds.TestCapability(ogr.ODsCCreateLayer)) # 创建新的图层
# 为了写入而打开数据源
ds = ogr.Open(dirname, 1)
print(ds.TestCapability(ogr.ODsCCreateLayer))
import os
import sys
from osgeo import ogr
import ospybook as pb
from ospybook.vectorplotter import VectorPlotter

data_dir = r'E:\Google chrome\Download\gis with python\osgeopy data'

# 复制一个shapefile
original_fn = os.path.join(data_dir, 'Washington', 'large_cities2.shp')
new_fn = os.path.join(data_dir, 'Washington', 'large_cities3.shp')
pb.copy_datasource(original_fn, new_fn)

# 尝试打开只读数据源
ds = ogr.Open(new_fn, 0)
if ds is None:
    sys.exit('Could not open {0}.'.format(new_fn))
lyr = ds.GetLayer(0)

# 检查能添加的字段
# 若不允许添加字段,代码引发错误,且不能继续
if not lyr.TestCapability(ogr.OLCCreateField):
    raise RuntimeError('Cannot create fields.')
lyr.CreateField(ogr.FieldDefn('ID', ogr.OFTInteger))

# 尝试打开数据源进行编写
ds = ogr.Open(new_fn, 1)
if ds is None:
    sys.exit('Could not open {0}.'.format(new_fn))
lyr = ds.GetLayer(0)

if not lyr.TestCapability(ogr.OLCCreateField):
    raise RuntimeError('Cannot create fields.')
lyr.CreateField(ogr.FieldDefn('ID', ogr.OFTInteger))

# 使用ospybook模块调用print_capabilities函数
driver = ogr.GetDriverByName('ESRI Shapefile')
pb.print_capabilities(driver)

  结果:

*** Driver Capabilities ***
ODrCCreateDataSource: True
ODrCDeleteDataSource: True

猜你喜欢

转载自blog.csdn.net/amyniez/article/details/113402591