GDAL集成對KML文件的支援

  • 2019 年 10 月 24 日
  • 筆記

1. 正文

GDAL可以支援將KML作為矢量文件文件讀取,但是需要在編譯的時候添加第三方庫的支援,否則默認的編譯結果是還是會不識別這種格式。

查閱官方文檔發現有兩種驅動可以支援KML:一種驅動名稱是KML,需要Expat庫的支援,這是一個解析XML格式的庫;另一種驅動名稱是LIBKML,需要LibKML庫的支援,這是google自己的KML讀寫庫。第二種方式支援的功能更多,並且LibKML本身也需要Expat庫的支援。如果兩種驅動都存在,那麼在讀取的時候第二種會覆蓋第一種,也就是採用LIBKML的方式讀取KML。我這裡就是順手把兩種驅動都添加進去了。

閱讀這篇文章之前需要預先知道GDAL是如何編譯的,可參看《Win64下編譯集成GEOS和Proj4的GDAL》

1.1. 編譯LibKML

LibKML的源碼託管在GitHub(可點擊點擊進入)。下載解壓後可在其根目錄找到libkml.sln這個文件,通過這個文件可以在visual studio中打開,然後直接編譯就可以了。總結下在編譯過程中我遇到的問題:

1.1.1. 第三方庫支援

LibKML的源碼文件夾中已經自帶了其需要的第三方庫,如下圖所示:
image

LibKML這個靜態庫挺奇怪,只需要包含第三方對應的頭文件即可編譯了,所以如果編譯的時候提示找不到頭文件,可以自己把包含目錄重新設置一下,如下圖所示。這種問題一般都是包含目錄的相對路徑出錯或者缺失造成的。
image

1.1.2. 編譯錯誤

在編譯libkmlbase這個庫的file_win32.cc這個文件的時候,提示這段程式碼出錯:

// Internal to the win32 file class. We need a conversion from string to  // LPCWSTR.  static std::wstring Str2Wstr(const string& str) {    std::wstring wstr(str.length(), L'');    std::copy(str.begin(), str.end(), wstr.begin());    return wstr;  }    // Internal to the win32 file class. We need a conversion from std::wstring to  // string.  string Wstr2Str(const std::wstring& wstr) {    size_t s = wstr.size();    string str(static_cast<int>(s+1), 0);    WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), static_cast<int>(s), &str[0],                        static_cast<int>(s), NULL, NULL);    return str;  }

出錯的地方在std::wstring wstr(str.length(), L”)這一行,錯誤提示是「error C2137: 空字元常量」。其實就是C/C++沒有定義「空字元常量」,L”這種寫法不太標準,將其改成L’ ‘就可以了。同時這段程式碼還存在另一個問題:這段程式碼的意思是字元串wstring和字元串string互相轉換,但是很明顯這種寫法是不支援中文字元的。我這裡就將這段程式碼替換為:

//改動:  //str 與 wstr 的互轉  static std::wstring Str2Wstr(const string& str)  {      size_t i;      std::string curLocale = setlocale(LC_ALL, NULL);      setlocale(LC_ALL, "chs");      const char* _source = str.c_str();      size_t _dsize = str.size() + 1;      wchar_t* _dest = new wchar_t[_dsize];      wmemset(_dest, 0x0, _dsize);      mbstowcs_s(&i, _dest, _dsize, _source, _dsize);      std::wstring result = _dest;      delete[] _dest;      setlocale(LC_ALL, curLocale.c_str());      return result;  }  string Wstr2Str(const std::wstring& wstr)  {      size_t i;      std::string curLocale = setlocale(LC_ALL, NULL);      setlocale(LC_ALL, "chs");      const wchar_t* _source = wstr.c_str();      size_t _dsize = 2 * wstr.size() + 1;      char* _dest = new char[_dsize];      memset(_dest, 0x0, _dsize);      wcstombs_s(&i, _dest, _dsize, _source, _dsize);      std::string result = _dest;      delete[] _dest;      setlocale(LC_ALL, curLocale.c_str());      return result;  }

1.2. 配置GDAL

修改GDAL的編譯配置文件nmake.opt,找到LibKML部分,修改為:

# Uncomment out the following lines to enable LibKML support.  #LIBKML_DIR = C:/Dev/libkml  #LIBKML_INCLUDE = -I$(LIBKML_DIR)/src -I$(LIBKML_DIR)/third_party/boost_1_34_1  #LIBKML_LIBRARY = $(LIBKML_DIR)/msvc/Release  #LIBKML_LIBS =  $(LIBKML_LIBRARY)/libkmlbase.lib   #       $(LIBKML_LIBRARY)/libkmlconvenience.lib   #       $(LIBKML_LIBRARY)/libkmldom.lib   #       $(LIBKML_LIBRARY)/libkmlengine.lib   #       $(LIBKML_LIBRARY)/libkmlregionator.lib   #       $(LIBKML_LIBRARY)/libkmlxsd.lib   #       $(LIBKML_LIBRARY)/minizip_static.lib   #       $(LIBKML_DIR)/third_partyexpat.win32/libexpat.lib   #       $(LIBKML_DIR)/third_partyuriparser-0.7.5.win32/release/uriparser.lib   #       $(LIBKML_DIR)/third_partyzlib-1.2.3.win32/lib/minizip.lib   #       $(LIBKML_DIR)/third_partyzlib-1.2.3.win32/lib/zlib.lib  LIBKML_DIR = C:/Work/GDALBuild/libkml-master  LIBKML_INCLUDE = -I$(LIBKML_DIR)/src -I$(LIBKML_DIR)/third_party/boost_1_34_1  LIBKML_LIBRARY = $(LIBKML_DIR)/x64/Release  LIBKML_LIBS =   $(LIBKML_LIBRARY)/libkmlbase.lib           $(LIBKML_LIBRARY)/libkmlconvenience.lib           $(LIBKML_LIBRARY)/libkmldom.lib           $(LIBKML_LIBRARY)/libkmlengine.lib           $(LIBKML_LIBRARY)/libkmlregionator.lib           $(LIBKML_LIBRARY)/libkmlxsd.lib       $(LIBKML_DIR)/third_party/zlib-1.2.3/contrib/minizip/x64/Release/minizip_static.lib       $(EXPAT_DIR)/build/Release/libexpat.lib           $(LIBKML_DIR)/third_party/uriparser-0.7.5/win32/uriparser.lib           $(LIBKML_DIR)/third_partyzlib-1.2.3.win32/lib/minizip.lib           $(LIBKML_DIR)/third_partyzlib-1.2.3.win32/lib/zlib.lib

這裡的目錄設置可能每個人有點不太一樣,注意不要硬抄根據實際情況修改下。具體來說LIBKML_DIR定義的一個根目錄,通過這個目錄依次找到:LIBKML_INCLUDE包含的頭文件目錄;LIBKML_LIBRARY依賴的庫文件目錄;LIBKML_LIBS具體的每一個lib。

minizip_static.lib這個文件可能沒有直接提供,但是是有源程式碼的,可以自己編譯一下:
image

libexpat.lib文件也有點不同,宏(EXPAT_DIR)來自於Expat部分:

# Uncomment for Expat support (required for KML, GPX and GeoRSS read support).  #EXPAT_DIR = "C:Program FilesExpat 2.0.1"  #EXPAT_INCLUDE = -I$(EXPAT_DIR)/source/lib  #EXPAT_LIB = $(EXPAT_DIR)/bin/libexpat.lib  EXPAT_DIR = "C:WorkGDALBuildlibexpat-master"  EXPAT_INCLUDE = -I$(EXPAT_DIR)/expat/lib  EXPAT_LIB = $(EXPAT_DIR)/build/Release/libexpat.lib

這個Expat部分理論上是可以用third_party中已經編譯好的頭文件和lib的,但是我這裡並沒有詳細求證,因為我是先配置好Expat再配置LibKML的,Expat是自己編譯的。

1.3. 鏈接問題

在編譯鏈接GDAL的過程中,出現了形如「無法解析的外部符號「這種類型的錯誤,如下所示:
image

這是由於LibKML默認工程中包含的文件不全,GDAL在編譯鏈接的時候找不到實現造成的。只需要搜索無法解析的函數所在的文件,將其加入到LibKML的工程中,重新編譯LibKML和GDAL就可以了。我這裡缺失的文件有:

  1. libkmldom工程:xal.h、xal.cc、gx_timeprimitive.h、gx_timeprimitive.cc、gx_tour.h、gx_tour.cc
  2. libkmlbase工程: zip_file.h、zip_file.cc、uri_parser.cc、xml_namespaces.h、xml_namespaces.cc
  3. libkmlengine工程:id_mapper.h、id_mapper.cc、find_xml_namespaces.h、find_xml_namespaces.cc

2. 參考

[1] gdal集成kml庫的做法
[2] 解決gdal集成libkml的鏈接錯誤
[2] std::wstring