近期接触到一个项目需要通过WFS和GeoServer服务器交互,实现服务器和本地数据的同步,经过调研客户端计划采用GDAL来操作WFS读写,但是在测试过程中遇到一些问题,此处做一个记录,并分享给有需要的人。
问题:
由于我们服务器涉及到多个平台多个客户端数据同步所以服务器并没有设置fid为主键自增,导致WFS的Transaction接口的更新操作和插入和删除操作不可用。
WFS到目前为止共计有1.0.0、1.1.0、2.0.0三个版本,但是GDAL从老版本到最新版本的Transaction接口部分未按WFS2.0.0规范进行实现。导致按WFS2.0.0请求时向服务器更新和插入数据失效。
GDAL发送的Transaction接口过滤器部分采用layer.fid的形式,GeoServer无法匹配到fid导致操作失败【此问题可能由于问题1导致,未验证】。
GDAL读写WFS示例代码【需要设置主键自增】:
#include "gdal.h"
#include "gdal_priv.h"
#include "ogrsf_frmts.h"
#include "ogr_feature.h"
#include "ogr_core.h"
#include <iostream>
int main()
{
CPLSetConfigOption("GDAL_DATA", "E:/gdal_dev/apps/gdal/share/gdal");//添加自己的路径
CPLSetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES");//交换坐标轴主要解决报gml坐标超限问题[超出-90,90主要是坐标顺序不对]
CPLSetConfigOption("CPL_CURL_GZIP", "YES");//可选采用GZip格式返回
//CPLSetConfigOption("FORMAT", "GML3");//返回格式似乎没用,从请求url处也可配置【outputFromat】
//CPLSetConfigOption("GML_PARSER", "XERCES");//解析gml
CPLSetConfigOption("GML_PARSER", "EXPAT");//解析gml
CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
//CPLSetConfigOption("OGR_WFS_USE_STREAMING", "NO");
//CPLSetConfigOption("GML_FIELDTYPES", "ALWAYS_STRING");
OGRRegisterAll();
std::string strUrl = "WFS:http://localhost:8080/geoserver/wfs?typeName=w3:layer2&version=1.0.0&outputFormat=GML";
GDALDataset* pWFSDs = (GDALDataset*)GDALOpenEx(strUrl.data(), GDAL_OF_UPDATE, NULL, NULL, NULL);//只需要使用GDAL_OF_UPDATE模式即可,pWFSDs读写模式值可能为只读,但是不影响后续操作
if (NULL == pWFSDs)
{
const char* s_error = CPLGetLastErrorMsg();
printf(s_error);
}
int n_layer = pWFSDs->GetLayerCount();
OGRLayer* pLayer = NULL;
OGRFeatureDefn* pDef = NULL;
OGRFeature* pFeature = NULL;
for (int i = 0; i < n_layer; i++)
{
pLayer = pWFSDs->GetLayer(i);
if (pLayer == NULL)
continue;
pDef = pLayer->GetLayerDefn();
if (pDef == NULL)
continue;
pLayer->ResetReading();
while ((pFeature = pLayer->GetNextFeature()) != NULL)
{
OGRGeometry* pTempGeo = pFeature->GetGeometryRef();
OGRFeature* pTempFeature = new OGRFeature(pDef);//利用原有的要素信息新建一个要素
pTempFeature->SetGeometry(pTempGeo);
//pTempFeature->SetField("gml_id", nId);//修改源码后修改和新增要素需要加上这句
pTempFeature->SetField("type", 33);//自己定义的字段修改一下以做区分
pTempFeature->SetField("doubleValue", 33.0124);//自己定义的字段修改一下以做区分
CPLString str = "点5";
pTempFeature->SetField("stringValue", str);
pFeature->SetField("type", 777);
OGRErr err = pLayer->SetFeature(pFeature);//修改要素
err = pLayer->CreateFeature(pTempFeature);//新建要素
__int64 nfId = pFeature->GetFID();
pLayer->DeleteFeature(nfId);//删除要素
//OGRFeature::DestroyFeature(pFeature);
}
}
GDALClose(pWFSDs);
}
解决方法:修改GDAL源码主要是ogrwfslayer.cpp部分代码如下。
/************************************************************************/
/* GetPostHeader() */
/************************************************************************/
CPLString OGRWFSLayer::GetPostHeader()
{
CPLString osPost;
if (atoi(poDS->GetVersion()) >= 2)
{
osPost += "<?xml version=\"1.0\"?>\n";
osPost += "<wfs:Transaction xmlns:wfs=\"http://www.opengis.net/wfs/2.0\"\n";
osPost += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
osPost += " service=\"WFS\" version=\""; osPost += poDS->GetVersion(); osPost += "\"\n";
osPost += " xmlns:gml=\"http://www.opengis.net/gml/3.2\"\n";
osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
osPost += " xsi:schemaLocation=\"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/";
osPost += poDS->GetVersion();
osPost += "/wfs.xsd ";
osPost += osTargetNamespace;
osPost += " ";
}
else
{
osPost += "<?xml version=\"1.0\"?>\n";
osPost += "<wfs:Transaction xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
osPost += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
osPost += " service=\"WFS\" version=\""; osPost += poDS->GetVersion(); osPost += "\"\n";
osPost += " xmlns:gml=\"http://www.opengis.net/gml\"\n";
osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
osPost += " xsi:schemaLocation=\"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/";
osPost += poDS->GetVersion();
osPost += "/wfs.xsd ";
osPost += osTargetNamespace;
osPost += " ";
}
char* pszXMLEncoded = CPLEscapeString(
GetDescribeFeatureTypeURL(FALSE), -1, CPLES_XML);
osPost += pszXMLEncoded;
CPLFree(pszXMLEncoded);
osPost += "\">\n";
return osPost;
}
/************************************************************************/
/* ICreateFeature() */
/************************************************************************/
OGRErr OGRWFSLayer::ICreateFeature( OGRFeature *poFeature )
{
if (!TestCapability(OLCSequentialWrite))
{
if (!poDS->SupportTransactions())
CPLError(CE_Failure, CPLE_AppDefined,
"CreateFeature() not supported: no WMS-T features advertized by server");
else if (!poDS->UpdateMode())
CPLError(CE_Failure, CPLE_AppDefined,
"CreateFeature() not supported: datasource opened as read-only");
return OGRERR_FAILURE;
}
if (poGMLFeatureClass == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot insert feature because we didn't manage to parse the .XSD schema");
return OGRERR_FAILURE;
}
if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find gml_id field");
return OGRERR_FAILURE;
}
// if (poFeature->IsFieldSetAndNotNull(0))
// {
// CPLError(CE_Failure, CPLE_AppDefined,
// "Cannot insert a feature when gml_id field is already set");
// return OGRERR_FAILURE;
// }
CPLString osPost;
const char* pszShortName = GetShortName();
if( !bInTransaction )
{
osPost += GetPostHeader();
osPost += " <wfs:Insert>\n";
}
osPost += " <feature:"; osPost += pszShortName; osPost += " xmlns:feature=\"";
osPost += osTargetNamespace; osPost += "\" fid=\""; osPost += poFeature->GetFieldAsString(0); osPost += "\">\n";
for( int i = 1; i <= poFeature->GetFieldCount(); i++ )
{
if (poGMLFeatureClass->GetGeometryPropertyCount() == 1 &&
poGMLFeatureClass->GetGeometryProperty(0)->GetAttributeIndex() == i - 1)
{
OGRGeometry* poGeom = poFeature->GetGeometryRef();
if (poGeom != NULL && !osGeometryColumnName.empty())
{
if (poGeom->getSpatialReference() == NULL)
poGeom->assignSpatialReference(poSRS);
char* pszGML = NULL;
if (strcmp(poDS->GetVersion(), "1.1.0") == 0)
{
char** papszOptions = CSLAddString(NULL, "FORMAT=GML3");
pszGML = OGR_G_ExportToGMLEx((OGRGeometryH)poGeom, papszOptions);
CSLDestroy(papszOptions);
}
else if (atoi(poDS->GetVersion()) >= 2)
{
char** papszOptions = CSLAddString(NULL, "FORMAT=GML3");
pszGML = OGR_G_ExportToGMLEx((OGRGeometryH)poGeom, papszOptions);
CSLDestroy(papszOptions);
}
else
pszGML = OGR_G_ExportToGML((OGRGeometryH)poGeom);
osPost += " <feature:"; osPost += osGeometryColumnName; osPost += ">";
osPost += pszGML;
osPost += "</feature:"; osPost += osGeometryColumnName; osPost += ">\n";
CPLFree(pszGML);
}
}
if (i == poFeature->GetFieldCount())
break;
#ifdef notdef
if( poFeature->IsFieldNull(i) )
{
OGRFieldDefn* poFDefn = poFeature->GetFieldDefnRef(i);
osPost += " <feature:";
osPost += poFDefn->GetNameRef();
osPost += " xsi:nil=\"true\" />\n";
}
else
#endif
if (poFeature->IsFieldSet(i) && !poFeature->IsFieldNull(i) )
{
OGRFieldDefn* poFDefn = poFeature->GetFieldDefnRef(i);
osPost += " <feature:";
osPost += poFDefn->GetNameRef();
osPost += ">";
if (poFDefn->GetType() == OFTInteger)
osPost += CPLSPrintf("%d", poFeature->GetFieldAsInteger(i));
else if (poFDefn->GetType() == OFTInteger64)
osPost += CPLSPrintf(CPL_FRMT_GIB, poFeature->GetFieldAsInteger64(i));
else if (poFDefn->GetType() == OFTReal)
osPost += CPLSPrintf("%.16g", poFeature->GetFieldAsDouble(i));
else
{
char* pszXMLEncoded = CPLEscapeString(poFeature->GetFieldAsString(i),
-1, CPLES_XML);
osPost += pszXMLEncoded;
CPLFree(pszXMLEncoded);
}
osPost += "</feature:";
osPost += poFDefn->GetNameRef();
osPost += ">\n";
}
}
osPost += " </feature:"; osPost += pszShortName; osPost += ">\n";
if( !bInTransaction )
{
osPost += " </wfs:Insert>\n";
osPost += "</wfs:Transaction>\n";
}
else
{
osGlobalInsert += osPost;
nExpectedInserts ++;
return OGRERR_NONE;
}
CPLDebug("WFS", "Post : %s", osPost.c_str());
char** papszOptions = NULL;
papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
papszOptions = CSLAddNameValue(papszOptions, "HEADERS",
"Content-Type: application/xml; charset=UTF-8");
CPLHTTPResult* psResult = poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
CSLDestroy(papszOptions);
if (psResult == NULL)
{
return OGRERR_FAILURE;
}
if (strstr((const char*)psResult->pabyData,
"<ServiceExceptionReport") != NULL ||
strstr((const char*)psResult->pabyData,
"<ows:ExceptionReport") != NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
psResult->pabyData);
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
CPLDebug("WFS", "Response: %s", psResult->pabyData);
CPLXMLNode* psXML = CPLParseXMLString( (const char*) psResult->pabyData );
if (psXML == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
psResult->pabyData);
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
CPLStripXMLNamespace( psXML, NULL, TRUE );
bool bUse100Schema = false;
CPLXMLNode* psRoot = CPLGetXMLNode( psXML, "=TransactionResponse" );
if (psRoot == NULL)
{
psRoot = CPLGetXMLNode( psXML, "=WFS_TransactionResponse" );
if (psRoot)
bUse100Schema = true;
}
if (psRoot == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find <TransactionResponse>");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
CPLXMLNode* psFeatureID = NULL;
if( bUse100Schema )
{
if (CPLGetXMLNode( psRoot, "TransactionResult.Status.FAILED" ))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Insert failed : %s",
psResult->pabyData);
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
psFeatureID =
CPLGetXMLNode( psRoot, "InsertResult.FeatureId");
if (psFeatureID == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find InsertResult.FeatureId");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
}
else
{
if (atoi(poDS->GetVersion()) >= 2)
{
psFeatureID =
CPLGetXMLNode( psRoot, "InsertResults.Feature.ResourceId");
if (psFeatureID == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find InsertResults.Feature.ResourceId");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
}
else
{
psFeatureID =
CPLGetXMLNode( psRoot, "InsertResults.Feature.FeatureId");
if (psFeatureID == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find InsertResults.Feature.FeatureId");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
}
}
if (atoi(poDS->GetVersion()) >= 2)
{
const char* pszFID = CPLGetXMLValue(psFeatureID, "rid", NULL);
if (pszFID == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find rid");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
poFeature->SetField("gml_id", pszFID);
/* If the returned fid is of the form layer_name.num, then use */
/* num as the OGR FID */
if (strncmp(pszFID, pszShortName, strlen(pszShortName)) == 0 &&
pszFID[strlen(pszShortName)] == '.')
{
GIntBig nFID = CPLAtoGIntBig(pszFID + strlen(pszShortName) + 1);
poFeature->SetFID(nFID);
}
}
else
{
const char* pszFID = CPLGetXMLValue(psFeatureID, "fid", NULL);
if (pszFID == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find fid");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
poFeature->SetField("gml_id", pszFID);
/* If the returned fid is of the form layer_name.num, then use */
/* num as the OGR FID */
if (strncmp(pszFID, pszShortName, strlen(pszShortName)) == 0 &&
pszFID[strlen(pszShortName)] == '.')
{
GIntBig nFID = CPLAtoGIntBig(pszFID + strlen(pszShortName) + 1);
poFeature->SetFID(nFID);
}
}
CPLDebug("WFS", "Got FID = " CPL_FRMT_GIB, poFeature->GetFID());
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
/* Invalidate layer */
bReloadNeeded = true;
nFeatures = -1;
bHasExtents = false;
return OGRERR_NONE;
}
/************************************************************************/
/* ISetFeature() */
/************************************************************************/
OGRErr OGRWFSLayer::ISetFeature( OGRFeature *poFeature )
{
if (!TestCapability(OLCRandomWrite))
{
if (!poDS->SupportTransactions())
CPLError(CE_Failure, CPLE_AppDefined,
"SetFeature() not supported: no WMS-T features advertized by server");
else if (!poDS->UpdateMode())
CPLError(CE_Failure, CPLE_AppDefined,
"SetFeature() not supported: datasource opened as read-only");
return OGRERR_FAILURE;
}
if (poFeatureDefn->GetFieldIndex("gml_id") != 0)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find gml_id field");
return OGRERR_FAILURE;
}
if (poFeature->IsFieldSetAndNotNull(0) == FALSE)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot update a feature when gml_id field is not set");
return OGRERR_FAILURE;
}
if( bInTransaction )
{
CPLError(CE_Warning, CPLE_AppDefined,
"SetFeature() not yet dealt in transaction. Issued immediately");
}
const char* pszShortName = GetShortName();
CPLString osPost;
osPost += GetPostHeader();
osPost += " <wfs:Update typeName=\"feature:"; osPost += pszShortName; osPost += "\" xmlns:feature=\"";
osPost += osTargetNamespace; osPost += "\">\n";
OGRGeometry* poGeom = poFeature->GetGeometryRef();
if ( !osGeometryColumnName.empty() )
{
osPost += " <wfs:Property>\n";
if (atoi(poDS->GetVersion()) >= 2)
{
osPost += " <wfs:ValueReference>"; osPost += osGeometryColumnName; osPost += "</wfs:ValueReference>\n";
}
else
{
osPost += " <wfs:Name>"; osPost += osGeometryColumnName; osPost += "</wfs:Name>\n";
}
if (poGeom != NULL)
{
if (poGeom->getSpatialReference() == NULL)
poGeom->assignSpatialReference(poSRS);
char* pszGML = NULL;
if (strcmp(poDS->GetVersion(), "1.1.0") == 0)
{
char** papszOptions = CSLAddString(NULL, "FORMAT=GML3");
pszGML = OGR_G_ExportToGMLEx((OGRGeometryH)poGeom, papszOptions);
CSLDestroy(papszOptions);
}
else if (atoi(poDS->GetVersion()) >= 2)
{
char** papszOptions = CSLAddString(NULL, "FORMAT=GML3");
pszGML = OGR_G_ExportToGMLEx((OGRGeometryH)poGeom, papszOptions);
CSLDestroy(papszOptions);
}
else
pszGML = OGR_G_ExportToGML((OGRGeometryH)poGeom);
osPost += " <wfs:Value>";
osPost += pszGML;
osPost += "</wfs:Value>\n";
CPLFree(pszGML);
}
osPost += " </wfs:Property>\n";
}
for( int i = 1; i < poFeature->GetFieldCount(); i++ )
{
OGRFieldDefn* poFDefn = poFeature->GetFieldDefnRef(i);
osPost += " <wfs:Property>\n";
if (atoi(poDS->GetVersion()) >= 2)
{
osPost += " <wfs:ValueReference>"; osPost += poFDefn->GetNameRef(); osPost += "</wfs:ValueReference>\n";
}
else
{
osPost += " <wfs:Name>"; osPost += poFDefn->GetNameRef(); osPost += "</wfs:Name>\n";
}
if (poFeature->IsFieldSetAndNotNull(i))
{
osPost += " <wfs:Value>";
if (poFDefn->GetType() == OFTInteger)
osPost += CPLSPrintf("%d", poFeature->GetFieldAsInteger(i));
else if (poFDefn->GetType() == OFTInteger64)
osPost += CPLSPrintf(CPL_FRMT_GIB, poFeature->GetFieldAsInteger64(i));
else if (poFDefn->GetType() == OFTReal)
osPost += CPLSPrintf("%.16g", poFeature->GetFieldAsDouble(i));
else
{
char* pszXMLEncoded = CPLEscapeString(poFeature->GetFieldAsString(i),
-1, CPLES_XML);
osPost += pszXMLEncoded;
CPLFree(pszXMLEncoded);
}
osPost += "</wfs:Value>\n";
}
osPost += " </wfs:Property>\n";
}
osPost += " <ogc:Filter>\n";
if( poDS->UseFeatureId() || bUseFeatureIdAtLayerLevel )
osPost += " <ogc:FeatureId fid=\"";
else if (atoi(poDS->GetVersion()) >= 2)
osPost += " <ogc:ResourceId rid=\"";
else
osPost += " <ogc:GmlObjectId gml:id=\"";
osPost += poFeature->GetFieldAsString(0); osPost += "\"/>\n";
osPost += " </ogc:Filter>\n";
osPost += " </wfs:Update>\n";
osPost += "</wfs:Transaction>\n";
CPLDebug("WFS", "Post : %s", osPost.c_str());
char** papszOptions = NULL;
papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
papszOptions = CSLAddNameValue(papszOptions, "HEADERS",
"Content-Type: application/xml; charset=UTF-8");
CPLHTTPResult* psResult = poDS->HTTPFetch(poDS->GetPostTransactionURL(), papszOptions);
CSLDestroy(papszOptions);
if (psResult == NULL)
{
return OGRERR_FAILURE;
}
if (strstr((const char*)psResult->pabyData,
"<ServiceExceptionReport") != NULL ||
strstr((const char*)psResult->pabyData,
"<ows:ExceptionReport") != NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
psResult->pabyData);
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
CPLDebug("WFS", "Response: %s", psResult->pabyData);
CPLXMLNode* psXML = CPLParseXMLString( (const char*) psResult->pabyData );
if (psXML == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
psResult->pabyData);
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
CPLStripXMLNamespace( psXML, NULL, TRUE );
int bUse100Schema = false;
CPLXMLNode* psRoot = CPLGetXMLNode( psXML, "=TransactionResponse" );
if (psRoot == NULL)
{
psRoot = CPLGetXMLNode( psXML, "=WFS_TransactionResponse" );
if (psRoot)
bUse100Schema = true;
}
if (psRoot == NULL)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot find <TransactionResponse>");
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
if( bUse100Schema )
{
if (CPLGetXMLNode( psRoot, "TransactionResult.Status.FAILED" ))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Update failed : %s",
psResult->pabyData);
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
return OGRERR_FAILURE;
}
}
CPLDestroyXMLNode( psXML );
CPLHTTPDestroyResult(psResult);
/* Invalidate layer */
bReloadNeeded = true;
nFeatures = -1;
bHasExtents = false;
return OGRERR_NONE;
}
注意:由于删除操作不由客户端控制所以源码未修改删除操作部分,如需要修改可参考上面部分代码,另外以上部分代码是针对服务器fid未设为自增的情况修改,主键自增可能不适用【猜测,大概率适用】,需要自行对照源码改回源码即可,该部分代码主要用途是使GDAL完全支持WFS2.0.0。
最后:源码修改完后需要重新编译GDAL,我编译的版本为GDAL2.2.3 win32位,需要的话可以私信我发你编译好的文件【读写WFS至少需要libcurl、geos、proj等库,高版本gdal,proj库强依赖sqlite】。