MS RFC 126:端口到PROJ 6 API

日期

2019-10-01

作者

甚至鲁奥

联系

even.rouault@spatialys.com

状态

采用

最后更新

2019-10-10

版本

MapServer 8.0版

概述

自2019年3月1日起, PROJ 6.0 已经释放。此版本不支持proj_API.h中提供的“传统”API,而支持proj.h中提供的新API。目前,通过定义ACCEPT_USE_OF_DEPRECATED_proj_API_h宏,传统API仍然可用,但从proj 7.0开始,proj_API.h将不再可用。

因此,这个RFC是关于MapServer中使用proj.h新API所需的更改。当MapServer针对proj 4.x或proj 5.x构建时,对传统的proj_api.h的支持目前仍然保留。

建议的解决方案

与PROJ的大部分接口都包含在mapproject.c中,因此大部分更改都在那里完成,projectionObj对象是在代码库中一致使用的抽象,用于对坐标参考系建模。只有一些对pj_is_latlong()的直接调用被一个新的msprojisgographiccrs()函数抽象出来。

然而,在最初实现的测试中,很快就发现需要进行其他更改来保持MapServer的性能。PROJ 6计算坐标操作的新机制非常强大,使用“后期绑定”方法试图找到源坐标系和目标坐标系之间最直接的转换,而不是使用众所周知的+to WGS84=或+nadgrids=PROJ关键字系统地通过WGS84。穿过WGS84枢轴可能会导致不良结果,例如当在两个固定板CRS(如GDA94和GDA2020)之间转换时,两者相差1.8米,但其中每个CRS与WGS84之间的转换是零移位转换。

然而,这种新功能有一个计算代价。而对于PROJ 4.x,pj_transform()可以毫无问题地用于转换单个坐标元组,天真地使用等效的调用PROJ_create_crs_to_crs()+PROJ_trans()将是一个主要的性能杀手,因为PROJ_create_crs_to_crs()在最复杂的情况下可能需要100毫秒的时间。因此,需要缓存由proj_create_crs_to_crs()产生的PJ对象,并在相同对象(源crs,目标crs)之间转换时重用它。

因此,解决方案是通过projectonContext不透明结构拥有一个全局投影上下文池,在PROJ 6的情况下,该结构包含PJ_上下文以及多个(源_crs,目标_crs)对之间PJ*转换对象的缓存。将mapObj结构扩展为存储projectionContext,并从loadMap()中的全局池中获取一个。它将其分配给mapObj.projection成员,并且在MapServer中创建projectionObj的所有调用站点都将被修改为继承投影上下文。当mapObj被释放时,它的投影上下文被返回到全局池,因此它可以被另一个请求重用(通常是FastCGI或MapScript循环)。

避免研究现有的PJ * object for a (source_crs, target_crs) in the cache of the current projection context, which has a price by itself, a variant API for a number of functions like msProjectPoint(), msProjectShape() or msProjectLine() have has been created to directly take a reprojectionObj* 指针而不是(projectionObj 在,projectionObj 出)元组。那次谴责 基本上是PJ的持有人 坐标变换对象。这对于PROJ 4.x也有帮助,因为msProjectPoint()必须对每个坐标转换的CRS的性质进行多次检查,而reprojectonobj可以缓存它们。

综上所述,新的功能是:

projectionContext* msProjectionContextGetFromPool(void);
void msProjectionContextReleaseToPool(projectionContext* ctx);
void msProjectionContextPoolCleanup(void);

reprojectionObj* msProjectCreateReprojector(projectionObj* in, projectionObj* out);
void msProjectDestroyReprojector(reprojectionObj* reprojector);

int msProjectPointEx(reprojectionObj* reprojector, pointObj *point);
int msProjectShapeEx(reprojectionObj* reprojector, shapeObj *shape);
int msProjectLineEx(reprojectionObj* reprojector, lineObj *line);

void msProjectionInheritContextFrom(projectionObj *pDst, projectionObj* pSrc);
void msProjectionSetContext(projectionObj *p, projectionContext* ctx);

持续集成更改

PHP 7.3配置被修改为使用PROJ 6 API,而其他目标仍然使用PROJ 4.xapi。为了简化测试套件的维护,所有Travis配置都被修改为使用PROJ 6.1.1。对于不使用PROJ 6 API的配置,只需删除PROJ.h文件,因此MapServer将使用PROJ_API.h。将同一PROJ版本上的所有配置对齐的原因是,与所使用的API无关,PROJ中的更改使坐标重投影的结果略有不同,这影响了许多预期结果(这主要是由于PROJ 6默认使用增强的横轴墨卡托法,而不是以前使用的传统算法)。这样,当使用proj_api.h或proj.h时,预期的结果是相同的(除了在WCS GeoTIFF geokeys中存在微米级差异的单个情况,因为proj_api.h和proj.h使用的地理到地心转换算法略有不同)

Vagrant也被修改为使用PROJ 6.1.1,它创建了两个构建:针对PROJ 4.x API构建Vagrant,针对PROJ 6 API构建Vagrant。

实施细节

将修改以下文件:

  • .travis.yml:更改PHP7.3配置的名称

  • CMakeLists.txt:考虑删除mapprojhack.c

  • c:创建一个reprojectionObj对象,并将其与msprojectshapex一起使用

  • mapchart.c:同上

  • mapcluster.c:同上

  • mapcopy.c:msCopyProjectionExtended()中的共享上下文

  • mapdraw.c:使用reprojectonobj对象和msprojectshapex()。使用msprojisgographiccrs()

  • mapfile.c:将msInitProjection()、msFreeProjection()和msProcessAutoProjection()移动到mapproject.c。在initMap()和initLayer()中共享上下文

  • mapgml.c:使用reprojectionObj对象和msprojectshapex()

  • mapgradicule.c:使用reprojectonobj对象和msProjectShapeEx()。使用msprojisgographiccrs()

  • mapkmlrenderer.cpp:上下文共享。使用msprojisgographiccrs()

  • c:使用reprojectionObj对象和msProjectShapeEx()

  • mapmvt.c:同上

  • mapobject.c:将投影上下文释放到msFreeMap()中的投影池

  • mapogcfilter.c:上下文共享

  • mapogcfilter.h:修改fltdoaxiswappingifnecessage()的原型,以接受用于上下文共享的mapObj*

  • mapogcfiltercommon.c:上下文共享

  • mapogcsos.c:使用reprojectionObj对象和msprojectshapex()

  • mapogr.cpp:上下文共享,当CRS是EPSG代码时,避免通过msOGRSpatialRef2ProjectionObj()中的PROJ字符串执行有损步骤

  • mapogroutput.c:使用reprojectionObj对象和msprojectshapex()

  • mapows.c:上下文共享。使用msprojisgographiccrs()

  • mapproject.c:很多变化!

  • mapproject.h:新功能和结构

  • mapquery.c:使用reprojectionObj对象和msprojectshapex()

  • mapresample.c:一些PROJ 6特定的代码路径,因为有对PROJ API的直接调用

  • mapserver.h:添加到mapObj的projContext成员,以及存储在layerObj中的2个reprojector对象

  • mapservutil.c:使用msprojisgographiccrs()

  • c:使用reprojectionObj对象和msProjectShapeEx()

  • mapshape.h:在mstiled shplayerinfo中存储一个重新投影程序对象

  • maptemplate.c:使用reprojectionObj对象和msprojectshapex()

  • mapunion.c:同上

  • maputil.c:在msCleanup()中调用msprojectonContextPoolCleanup()

  • mapwcs.c:上下文共享。使用msprojisgographiccrs()

  • mapwcs11.c:上下文共享。

  • mapwcs20.c:上下文共享。使用msprojisgographiccrs()

  • mapwfs.c:上下文共享

  • mapwms.c:上下文共享

  • Vagrantfile:调用proj6.sh脚本

  • ci/travis/before_install.sh:安装卷边

  • ci/travis/install.sh:下载并构建项目6。修改PHP 7.3目标以使用它

  • cmake/FindProj.cmake:检测项目6 API

  • msautotest/pymod/testlib.py:测试替代预期结果的逻辑

  • msautotest/wxs/expected/:由于PROJ 6增强的横轴墨卡托而更改了许多文件

  • scripts/vagrant/mapserver.sh:PROJ 6相关的构建更改

  • scripts/vagrant/packages.sh:安装curl和sqlite3

将添加以下文件:

  • scripts/vagrant/proj6.sh:下载并构建proj6

将删除以下文件:

  • mapprojhack.c:合并到mapproject.c中

向后兼容性问题

映射文件语法上没有。由于项目的变化,坐标重投影可能会有轻微的变化。

安全影响

没有。

性能影响

使用PROJ 6 API比较慢,特别是对于MapServer CGI二进制文件的单次调用。这显示在msautotest的运行时,它包含许多这样的单次调用。Travis CI show上显示的典型运行时,PROJ 4api构建大约9分钟,PROJ 6api构建大约15分钟30秒。对此我们无能为力。

对于涉及使用MapScript的FastCGI或WSGI的用例,其中流程的生命周期较长,投影上下文池策略有助于大大降低成本。

我们使用了以下简单的MapScript python代码和mapfile来测试性能。

def test():
    map = mapscript.mapObj('bench.map')
    mapscript.msIO_installStdoutToBuffer()
    request = mapscript.OWSRequest()
    request.loadParamsFromURL('service=WMS&version=1.1.1&request=GetMap&layers=grey&srs=EPSG:4269&bbox=-180,-90,180,90&format=image/png&width=80&height=40')
    status = map.OWSDispatch(request)
    return mapscript.msIO_getStdoutBufferBytes()
MAP
    NAME TEST
    STATUS ON
    SIZE 80 40
    EXTENT -180 -90 180 90

    PROJECTION
        "init=epsg:4326"
    END

    OUTPUTFORMAT
        NAME png24_t
        DRIVER "GDAL/PNG"
        IMAGEMODE RGBA
    END

    WEB
    METADATA
        "ows_enable_request" "*"
        "wms_srs" "EPSG:4326 EPSG:4269"
        "ows_http_max_age" "86400"
    END
    END

    LAYER
        NAME grey
        TYPE raster
        STATUS default
        DATA ../gdal/data/grey.tif
    END
END

这涉及到从WGS84到NAD83的重投影,它使用相同的GDAL 2.4和Proj主分支(7.0dev)构建,在test()上循环500次使用Proj4API导致每次迭代的运行时间为0.9ms,而使用Proj6API为1.1ms。

如果在热运行时使用PROJ 4 API进行单次迭代,则运行时为19ms,使用proj6api时为30ms。

这种情况有点极端,因为渲染速度非常快,因此坐标操作设置是不可忽略的。对于请求时间较长的用例,预计时间差会成比例地降低。

MapScript含义

没有。

文件需求

理论上没有,因为没有语法变化。但是,将在https://mapserver.org/mapfile/projection.html中添加一些单词,建议使用init=epsg:XXXX语法,而不是PROJ 4 CRS字符串,以便从更精确的后期绑定坐标转换中获益。

票证ID和参考号

投票历史

+来自PSC的成员包括鲁奥、米克尔·史密斯、塞思·吉尔文、丹尼尔·莫里塞特和朱卡·拉科宁。

信用

感谢法国国防部的资助。