矢量数据管理与优化

作者

杰夫麦克纳

联系

jmckenna在gatewaygeomatics.com

原作者

主机地理信息系统

最后更新

2022-04-26

根据您的需要选择合适的矢量格式

  • 就显示速度而言,MapServer的最佳/最佳矢量数据源为 Shapefile

  • 最近为矢量性能设计的一种格式是,在MapServer中的地图绘制速度与shapefile相似 FlatGeobuf ,建议用于云性能。

  • 对于数据库,将显示MapServer PostGIS 层的速度非常快,在MapServer源代码中包含了一些专门用于PostGIS+MapServer绘制速度的自定义技巧。因此,推荐使用PostGIS。

  • SpatiaLite 建议那些需要可移植格式的人使用,并且与MapServer配合使用非常好。

  • GeoPackage (“GPKG”)在可移植性方面也很受欢迎,并且与MapServer配合得很好。

以下是一些基本的地图绘制速度结果,以及即将发布的MapServer8.0版本代码(在Windows上运行 MS4W ):

Shapefile  0.011s
FlatGeobuf 0.014s
GeoPackage 0.042s
SpatiaLite 0.045s
PostGIS    0.053s
GeoJSON    0.089s

花点时间查看与您选择的格式对应的GDAL驱动程序页面

这是关键的一步,因为MapServer依赖GDAL(实际上是项目的OGR部分,它处理向量)来访问矢量数据。每个驱动程序(OGR格式)都有自己的一组功能和开关。找到您的矢量格式并查看其选项 here

通过OGR/GDAL连接到您的数据

对于MapServer中的数据管理,这应该始终是您的首要步骤之一。有时桌面GIS程序会以某种方式(如大写)显示一种格式或其属性,但您的数据可能不会显示在MapServer中;检查OGR/GDAL如何读取您的数据文件或数据库表,将帮助您管理数据。这个 通过MapServer的OGR矢量层 文档中有很好的例子(对于 OgrInfo 和其他命令)来连接到您的数据。以下是连接到PostGIS数据库并返回空间表列表的示例:

ogrinfo -ro PG:"host=127.0.0.1 user=postgres password=postgres port=5432 dbname=mydb"

      INFO: Open of `PG:host=127.0.0.1 user=postgres password=postgres port=5432 dbname=mydb'
            using driver `PostgreSQL' successful.
      1: popplace (Point)
      2: province (Multi Polygon)

然后通过ogrinfo:获取‘Popplace’表的摘要

ogrinfo -ro PG:"host=127.0.0.1 user=postgres password=postgres port=5433 dbname=mydb" popplace -summary


    INFO: Open of `PG:host=127.0.0.1 user=postgres password=postgres port=5433 dbname=mydb'
          using driver `PostgreSQL' successful.

        Layer name: popplace
        Geometry: Point
        Feature Count: 497
        Extent: (-2303861.750000, -681502.875000) - (2961766.250000, 3798856.750000)
        Layer SRS WKT:
        PROJCS["NAD83 / Canada Atlas Lambert",
            GEOGCS["NAD83",
                DATUM["North_American_Datum_1983",
                    SPHEROID["GRS 1980",6378137,298.257222101,
                        AUTHORITY["EPSG","7019"]],
                    TOWGS84[0,0,0,0,0,0,0],
                    AUTHORITY["EPSG","6269"]],
                PRIMEM["Greenwich",0,
                    AUTHORITY["EPSG","8901"]],
                UNIT["degree",0.0174532925199433,
                    AUTHORITY["EPSG","9122"]],
                AUTHORITY["EPSG","4269"]],
            PROJECTION["Lambert_Conformal_Conic_2SP"],
            PARAMETER["standard_parallel_1",49],
            PARAMETER["standard_parallel_2",77],
            PARAMETER["latitude_of_origin",49],
            PARAMETER["central_meridian",-95],
            PARAMETER["false_easting",0],
            PARAMETER["false_northing",0],
            UNIT["metre",1,
                AUTHORITY["EPSG","9001"]],
            AXIS["Easting",EAST],
            AXIS["Northing",NORTH],
            AUTHORITY["EPSG","3978"]]
        FID Column = gid
        Geometry Column = geom
        area: Real (0.0)
        perimeter: Real (0.0)
        popplace_: Real (0.0)
        popplace_i: Real (0.0)
        unique_key: String (5.0)
        name: String (25.0)
        name_e: String (20.0)
        name_f: String (20.0)
        reg_code: Real (0.0)
        nts50: String (7.0)
        lat: String (7.0)
        long: String (7.0)
        sgc_code: Real (0.0)
        capital: Real (0.0)
        pop_range: Real (0.0)

备注

可以使用从ogrinfo返回的范围值粘贴到 Mapfile 的范围参数中。您还可以在该摘要中注意到 PROJCS/AUTHORITY 行,该行声明此数据当前位于 EPSG:3978 投影。

学习和复习各种OGR实用程序来管理您的矢量

OGR命令行实用程序非常有用,在MapServer中显示之前,可以很容易地操作源向量文件或数据库。例如,您可能想要将空间文件导入到现有的PostGIS数据库中,这可以通过轻松完成 ogr2ogr 如下所示(获取一个shapefile,将其导入到现有的PostGIS数据库中,并将新表重命名为‘Roadways’):

ogr2ogr PG:"host=127.0.0.1 user=postgres password=postgres port=5432 dbname=mydb" road.shp -nln roadways

备注

其中,ogr2ogr语法实际上是: Ogr2ogr目标源

您也可以导入空间文件,然后将其重新投影到另一个EPSG投影,如下所示(获取shapefile,将其重新投影到Web墨卡托 EPSG:3857 ,将其导入现有的PostGIS数据库,并将新表重命名为‘Road way s3857’):

ogr2ogr -t_srs EPSG:3857 -s_srs EPSG:3978 PG:"host=127.0.0.1 user=postgres password=postgres port=5432 dbname=mydb" road.shp -nln roadways3857

备注

其中,ogr2ogr语法实际上是: ogr2ogr -t_srs (ouput projection) -s_srs (source projection) destination source

查看所有可用的OGR向量实用程序 here

为您的数据编制索引

当然,这对于任何矢量层来说都是重要的一步,以便在MapServer中快速显示。您可以启用几种类型的索引来加快使用MapServer显示矢量数据的速度:

平铺索引

看见 瓦片索引 有关使用MapServer动态马赛克的更多详细信息。

向数据添加空间索引

确保您的几何体具有空间索引。

  • Shapefile:请参见 希普特里

  • 数据库(和GPKG)见下文

向数据添加属性索引

如果要按数据中的特定列进行查询或过滤,则应在矢量数据上设置属性索引。

Shapefile:

ogrinfo -sql  "CREATE INDEX ON province USING NAME_E" province.shp

应创建2个扩展名为: .ind和.idm

GeoPackage:

ogrinfo -sql "CREATE INDEX IDXnewindexname ON yourtable (yourcolumn)" file.gpkg

拆分数据

如果您发现自己制作了多个层,所有层都使用相同的数据集,但是过滤只使用一些记录,那么您可能会做得更好。如果条件是静态的,一种方法是预拆分数据。

这个 ogr2ogr 实用程序可以从数据源中选择某些功能,并将其保存到新的数据源。因此,可以将数据集拆分为几个较小的数据集,这些数据集已经被有效筛选,并删除filter语句。

如果您使用的是shapefile,则 shp2tile 实用程序是实现这一点的一个很好的命令行工具。

备注

对于Windows用户, MS4W 包括shp2tier实用程序和这里提到的所有实用程序。

在 Mapfile 中处理矢量图层

请查看文档中的注释 Mapfile 调整和管理 。您还应该检查您的格式是否有任何特定的MapServer注释 矢量数据 文档。

Shapefile注释

使用 希普特里 生成空间索引的步骤 shapefile 。这是快速而简单的(“shptree foo.shp”),并生成一个.qix文件。MapServer将自动检测索引并使用它。

备注

切片索引也可以使用 希普特里

MapServer还附带了 SORTHHP 实用工具。这将重新组织形状文件,并根据其中一列中的值对其进行排序。如果您通常是按条件筛选的,并且几乎总是按特定列筛选,那么这可以使流程稍微高效一些。

虽然shapefiles是一种非常快速的数据格式, PostGIS 速度也相当快,特别是如果您很好地使用索引,并且有内存用于缓存。

邮政地理信息系统注释

使用PostGIS进行索引

对性能的最大提升就是索引。确保在“几何”列上有一个gist索引,并且每个记录也应该有一个索引主键。如果使用shp2pgsql,那么这些语句应该创建必要的索引:

ALTER TABLE table ADD PRIMARY KEY (gid);
CREATE INDEX table_the_geom ON table (the_geom) USING GIST;

PostgreSQL还支持重新组织表中的数据,以便按索引进行物理排序。这使得PostgreSQL能够更高效地读取索引数据。使用 CLUSTER command ,例如

CLUSTER the_geom ON table;

除了地理空间组件之外,还可以在数据库服务器本身上执行许多优化。最简单的就是增加 shared_bufferspostgresql.conf 文件,这允许PostgreSQL使用更多的内存进行缓存。值得花时间研究一下 Resource Consumption 部分的PostgreSQL文档。

指定唯一ID列

要处理查询,MapServer需要唯一的ID列,作为您的PostgreSQL表的一部分。MapServer将尝试猜测唯一ID列,但这样做的代价很高,因为必须进行多个数据库查询;相反,您应该始终在图层的数据语句中使用 使用唯一 语法,例如:

DATA "geom FROM mydata USING UNIQUE myid USING SRID=3857"

如果您的表没有唯一ID列,则可以添加一个列,如下所示:

ALTER TABLE mytable ADD COLUMN unique_id SERIAL PRIMARY KEY;

警告

在较早的PostgreSQL版本中,一个(脏)技巧是使用现有的 OID 然而,作为唯一ID,从PostgreSQL 12.0版本开始,从PostgreSQL中删除了OID。因此,请始终在层的DATA语句中指定映射文件的实际唯一ID列。

PostGIS的调试速度问题

您可能会遇到一种情况,即您的PostGIS表在MapServer中绘制得很慢。以下步骤将帮助您检查问题(使用WFS案例):

  • 始终从获取您的层的绘制时间开始,使用 Sp2IMG 命令:

    shp2img -m postgis-wfs.map -o ttt.png -map_debug 3
    
      msDrawMap(): Layer 0 (provinces), 1.587s
      msDrawMap(): Drawing Label Cache, 0.000s
      msDrawMap() total time: 1.589s
      msSaveImage(ttt.png) total time: 0.005s
      freeLayer(): freeing layer at 010E0EC0.
      msPostGISLayerIsOpen called.
      msConnPoolClose(host=127.0.0.1 user=postgres password=postgres port=5432 dbname=gmap,01197840)
    
  • 要诊断问题是出在您的PostGIS表配置上,还是出在MapServer上,请通过执行以下步骤,在psql.exe命令行中执行由MapServer发送的请求:

    • 添加到 Mapfile 的地图级别:

      CONFIG "CPL_DEBUG" "ON"
      CONFIG "MS_ERRORFILE" "/ms4w/tmp/ms_error.txt"
      DEBUG 5
      
    • 添加到 Mapfile 的PostGIS图层中:

      DEBUG 5
      
    • 现在使用WFS客户端(如QGIS)并添加您的WFS PostGIS图层

    • 在记事本++中打开“/ms4w/tmp/ms_error.txt”

    • 搜索“msPostGISLayerWhichShape Query:”

    • 该行应该列出从MapServer发送到PostreSQL实例的长查询,可能如下所示:

      [Mon Apr 12 11:27:34 2021].207000 msPostGISLayerWhichShapes query:
       SELECT "gid"::text,"area"::text,"perimeter"::text,"province_"::text,"province_i"::text,"status"::text,"name"::text,"name_e"::text,"name_f"::text,"reg_code"::text,"poly_featu"::text,"island"::text,"island_e"::text,"island_f"::text,ST_AsBinary(("geom"),'NDR')
       as geom,"gid"::text FROM province WHERE "geom" &&
       ST_GeomFromText('POLYGON((-5814679.36987815
       -1504714.04276694,-5814679.36987815 4439806.52253364,5943763.33635122
       4439806.52253364,5943763.33635122 -1504714.04276694,-5814679.36987815
       -1504714.04276694))',3978) LIMIT 2 OFFSET 0
      

      备注

      对于一个QGIS操作,可能会有多个查询发送到MapServer,因此还要在该错误日志中查找其他“msPostGISLayerWhichShape Query:”实例。

    • 现在通过psql.exe连接到该数据库

      psql -U postgres -p 5432 -d mydb
      
    • 使用该错误文件行,从“SELECT”中获取所有内容,并在数据库提示符中,使用“EXPLAIN ANALYE”启动命令,然后粘贴完整的查询,例如:

      mydb=# EXPLAIN ANALYZE SELECT
      "gid"::text,"area"::text,"perimeter"::text,"province_"::text,"province_i"::text,"status"::text,"name"::text,"name_e"::text,"name_f"::text,"reg_code"::text,"poly_featu"::text,"island"::text,"island_e"::text,"island_f"::text,ST_AsBinary(("geom"),'NDR')
      as geom,"gid"::text FROM province WHERE "geom" &&
      ST_GeomFromText('POLYGON((-5814679.36987815
      -1504714.04276694,-5814679.36987815 4439806.52253364,5943763.33635122
      4439806.52253364,5943763.33635122 -1504714.04276694,-5814679.36987815
      -1504714.04276694))',3978) LIMIT 2 OFFSET 0;
      
    • 响应将告诉您查询花费了多长时间,例如:

      Execution time: 0.293 ms
      
    • 如果在psql命令行执行查询需要很长时间,那么您应该将精力集中在改进PostGIS表的索引/设置上。

通用数据库(PostGIS、Oracle、SpatiaLite、GeoPackage、Microsoft SQL Server、MySQL)

启用连接池

默认情况下,MapServer为映射文件中的每个数据库驱动的图层打开和关闭新的数据库连接。如果有多个层从同一个数据库中读取数据,这就没有多大意义了。对于某些数据库(如Oracle),建立连接需要足够长的时间,因此可能会变得非常重要。

尝试将此行添加到数据库层:

PROCESSING "CLOSE_CONNECTION=DEFER"

这会导致MapServer在处理完映射文件之前无法关闭每个层的数据库连接,这可能会减少几秒钟的映射生成时间。

在 Mapfile 的图层级别设置范围

此外,出于性能考虑,每个具有数据库连接的图层都应在图层级别设置范围(以及 ows_extent 元数据(如果您通过OGC服务提供),例如:

/* my database layer */
LAYER
  NAME "provinces"
  METADATA
    "wms_title" "Land"
    "wms_extent" "-2340603.75 -719746.0625 3009430.5 3836605.25" #this helps for performance
  END #metadata
  TYPE POLYGON
  STATUS ON
  CONNECTIONTYPE postgis
  CONNECTION "host=127.0.0.1 user=postgres password=postgres port=5432 dbname=gmap"
  DATA "geom FROM province USING unique gid using srid=3978"
  EXTENT -2340603.75 -719746.0625 3009430.5 3836605.25 #this helps for performance
  PROJECTION
    "init=epsg:3978"
  END # projection
  ...
END # layer

小技巧

PostGIS用户可以使用 ST_Extent() 用于获取表的边框的空间函数,例如::

SELECT ST_Extent(geom) as table_extent FROM province;


              table_extent
----------------------------------------------------
    BOX(-2340603.75 -719746.0625,3009430.5 3836605.25)
(1 row)

验证表是否具有空间索引

PostgreSQL/PostGIS示例

通过psql命令行连接后,描述该表并查找“索引”部分,其中提到几何图形,例如:

Indexes:
    "province_pkey" PRIMARY KEY, btree (ogc_fid)
    "province_wkb_geometry_geom_idx" gist (wkb_geometry)

地理包示例

您可以使用 OgrInfo 要验证您的GPKG表的几何是否具有空间索引,这对性能非常重要(请查看 HasSpatialIndex (Integer) = 1 确认空间索引存在):

ogrinfo -sql "SELECT HasSpatialIndex('countries', 'GEOMETRY')" countries.gpkg

  Layer name: SELECT
  Geometry: Unknown (any)
  Feature Count: 1
  Layer SRS WKT:
  (unknown)
  HasSpatialIndex: Integer (0.0)
  OGRFeature(SELECT):0
    HasSpatialIndex (Integer) = 1

然后使用ogrinfo添加空间索引:

ogrinfo -sql "SELECT CreateSpatialIndex('parcelle_graphique', 'geom')" PARCELLES_GRAPHIQUES.gpkg

对于WFS服务,禁止使用默认/全图范围

对于您的数据库连接,MapServer可能(默认情况下)在执行查询时使用数据的全部范围(这可能导致响应缓慢超过30秒)。您可能应该设置以下图层元数据以禁用此功能:

METADATA
  "wfs_use_default_extent_for_getfeature" "false"
END

以下是一个完全增强的层示例:

LAYER
  NAME "CLUS"
  METADATA
    "ows_title"                   "CLUS"
    "ows_srs"                     "EPSG:3857 EPSG:4326 EPSG:2154"
    "wfs_getfeature_formatlist"   "geojson"
    "wfs_geomtype"                "MultiPolygon"
    "gml_featureid"               "fid"
    "gml_include_items"           "all"
    "wfs_extent"                  "115134 6049690 1242200 7108930" #set to improve performance for all DB layers
    "wfs_use_default_extent_for_getfeature" "false" #set to improve performance for all DB layers
    "ows_enable_request"          "*"
  END #metadata
  TYPE POLYGON
  CONNECTIONTYPE OGR
  CONNECTION "C:/ms4w/apps/RPG_2-0_GPKG_LAMB93_FR-2019/PARCELLES_GRAPHIQUES.gpkg"
  DATA "parcelle_graphique"
  EXTENT 115134 6049690 1242200 7108930 #set to improve performance for all DB layers
  PROCESSING "CLOSE_CONNECTION=DEFER" #set to improve performance for all DB layers
  STATUS ON
  PROJECTION
    "init=epsg:2154"
  END #proj
  COMPOSITE
    OPACITY 100
  END #composite
  CLASS
    NAME "CLUS"
    STYLE
      OUTLINECOLOR 255 0 255
    END #style
  END #class
END #layer