2012年2月27日星期一

在Android上利用NDK编译并使用Spatialite库(Windows环境)

  Sqlite数据库因其体积便携(基于单个文件,跨平台),功能完整(类似RDBMS)受到非常广泛的欢迎,在iOS,Android平台上都提供原生的支持。Spatialite是Sqlite的一个空间扩展,根据官方介绍,Spatialite之于Sqlite,相当于PostGIS之于PostgreSQL。
  Spatialite扩展提供了一系列用于空间操作的SQL函数,例如基础的空间查询,缓冲区生成等,也有高级的类似ArcObjects中ITopologicalOperator,IRelationalOperator等接口的功能。
  在Android平台上使用Spatialite资料比较少,主要是google上的Spatialite for Android和github上的android-spatialite两个工程。前者带有完整的编译步骤,我尝试了这个,其中遇到了些问题,记录下来,方便需要用到的朋友。后者工程似乎带有编译好的libjsqlite.so库,可直接使用。
  我机器的环境是Windows 7 64bit,已经搭建好了Android Eclipse开发环境。由于Spatialite及Sqlite是C/C++的native语言编写,而Android的默认开发语言是Java,所以在Android上编译Spatialite必须用到google提供的NDK(Native Development Kit)环境。NDK环境的搭建比较简单,按照google的文档进行即可:

  1. 下载NDK压缩包(android-ndk-r7b-windows.zip)。我下载的是最新的android-ndk-r7b版本。将其解压到任意目录,下文以<ndk>指代。注意:由于后面cygwin中配置环境变量时不支持带空格的路径,所以建议将其解压到某个盘符的根目录下,比如d:\android-ndk-r7b\;
  2. 设置Windows的环境变量。主要是将NDK的路径添加到PATH中去,方便在任何位置调用ndk-build命令。详细操作不再赘述。此步骤只是为cmd提供了便利;
  3. 由于NDK需求中说明,必须用到GNU Make命令,所以在Windows环境上还需要提前安装Cygwin。这是在Windows上模拟Linux环境的工具。安装步骤参考此贴:Windows环境下Android NDK环境搭建。注意在cygwin的linux环境下,配置$NDK环境变量,方便在任何地方调用ndk-build编译命令。我在安装Cygwin时,只额外勾选了Devel库。

  完成这个步骤后,可尝试编译运行NDK中自带的hello-jni例子,验证NDK编译环境已经搭好。具体步骤参见google的文档。可以发现,native代码需要放在与src同级的jni目录下,该目录下必须至少有Android.mk文件,向NDK系统描述源文件;编译时需要在工程的根目录下运<ndk>/ndk-build命令;利用NDK编译后,Android工程文件夹中会多出libs目录,里面会生成lib<something>.so共享库,这就是我们要用到类库,该文件也会一并打包到apk文件中去。更多的ndk知识请参考<ndk>/documentation.html文件,非常详尽。注:JNI相当于.net平台的p/invoke。
  接下来就要按照Spatialite for Android上的提示,在cygwin环境中编译spatialite库了。

  1. 在windows下利用svn工具对整个project checkout(这次作者用的是Spatialite 3.0.1,PROJ 4.7.0,geos 3.2.2几个库)。会发现有两个工程spatialite-android和spatialite-android-test。我们主要对spatialite-android工程进行编译;
  2. 由于spatialite用到了geos和proj两个库,所以首先需要为该工程下载,配置两个库。具体步骤如下,在cygwin环境中执行:
       1: $ cd jni
       2: $ wget http://download.osgeo.org/geos/geos-3.2.2.tar.bz2
       3: $ tar xvjf geos-3.2.2.tar.bz2
       4: $ cd geos-3.2.2
       5: $ ./configure --build=x86_64-pc-linux-gnu --host=arm-linux-eabi
       6: $ cd ..
       7: $ wget ftp://ftp.remotesensing.org/proj/proj-4.7.0.tar.gz
       8: $ tar xvzf proj-4.7.0.tar.gz
       9: $ cd proj-4.7.0
      10: $ ./configure --build=x86_64-pc-linux-gnu --host=arm-linux-eabi
      11: $ touch src/empty.cpp
      12: $ cd ..

  3. 按照作者的提示,下来就可以直接对工程进行ndk-build了(注:此时仍然在spatialite-android/jni目录下,而非spatialite-android目录)。如果你这么做了,会出现一个错误:


       1: ...
       2: Compile thumb  : proj <= rtodms.c
       3: Compile thumb  : proj <= vector1.c
       4: StaticLibrary  : libproj.a
       5: Compile++ thumb  : geos <= geos_c.cpp
       6: In file included from D:/spatialite-android/jni/geos-3.2.2/source/headers/geos/geom/Coordinate.h:20,
       7:                  from D:/spatialite-android/jni/geos-3.2.2/source/headers/geos/g                                                                                                                eom/Envelope.h:26,
       8:                  from D:/spatialite-android/jni/geos-3.2.2/source/headers/geos/i                                                                                                                ndex/strtree/STRtree.h:27,
       9:                  from D:/spatialite-android/jni/geos-3.2.2/capi/geos_c.cpp:19:
      10: D:/spatialite-android/jni/geos-3.2.2/source/headers/geos/platform.h:29:20: error                                                                                                                : ieeefp.h: No such file or directory
      11: /cygdrive/d/android-ndk-r7b/build/core/build-binary.mk:243: recipe for target `/                                                                                                                cygdrive/d/spatialite-android/obj/local/armeabi/objs-debug/geos/geos-3.2.2/capi/                                                                                                                geos_c.o' failed
      12: make: *** [/cygdrive/d/spatialite-android/obj/local/armeabi/objs-debug/geos/geos                                                                                                                -3.2.2/capi/geos_c.o] Error 1
    image
    提示ieeefp.h: No such file or directory。要修正这个错误,你需要在spatialite-android/jni/geos-3.2.2/source/headers/geos/platform.h文件中大约15行的位置,找到“#define HAVE_IEEEFP_H 1”这句话,并在其下面添加一行“#undef HAVE_IEEEFP_H”,取消对HAVE_IEEEFP_H的定义。保存该文件;

  4. 再次ndk-build,这回发现上面的错误消失了。但又产生了新的错误:


       1: ...
       2: Compile++ thumb  : geos <= InteriorPointLine.cpp
       3: Compile++ thumb  : geos <= InteriorPointPoint.cpp
       4: Compile++ thumb  : geos <= LineIntersector.cpp
       5: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp: In st                                                                                                                atic member function 'static double geos::algorithm::LineIntersector::interpolat                                                                                                                eZ(const geos::geom::Coordinate&, const geos::geom::Coordinate&, const geos::geo                                                                                                                m::Coordinate&)':
       6: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp:224: e                                                                                                                rror: expected unqualified-id before '(' token
       7: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp:232: e                                                                                                                rror: expected unqualified-id before '(' token
       8: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp: In me                                                                                                                mber function 'void geos::algorithm::LineIntersector::computeIntersection(const                                                                                                                 geos::geom::Coordinate&, const geos::geom::Coordinate&, const geos::geom::Coordi                                                                                                                nate&)':
       9: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp:304: e                                                                                                                rror: expected unqualified-id before '(' token
      10: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp:306: e                                                                                                                rror: expected unqualified-id before '(' token
      11: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp: In me                                                                                                                mber function 'int geos::algorithm::LineIntersector::computeIntersect(const geos                                                                                                                ::geom::Coordinate&, const geos::geom::Coordinate&, const geos::geom::Coordinate                                                                                                                &, const geos::geom::Coordinate&)':
      12: D:/spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp:426: e                                                                                                                rror: expected unqualified-id before '(' token
    image在很多位置都提示 error: expected unqualified-id before '(' token,google一下这个错误,发现大概是由于在头文件中对某个函数重复声明冲突引起的。于是打开spatialite-android/jni/geos-3.2.2/source/algorithm/LineIntersector.cpp文件查看,发现基本都与一个名为“ISNAN”的函数有关。在Windows下搜索包含此字符串的文件,又来到了刚才修改过的platform.h文件中,发现这个文件中有与此函数相关的语句。为了解决此问题,尝试对此文件进行再次修改。在大约25行的位置,可以找到“/* #undef HAVE_ISNAN */”这句话,在这句话下面添加一句“#define HAVE_ISNAN 1”。这样做的目的是改变该文件后续的一些编译处理。

  5. 最后再次ndk-build,成功输出libjsqlite.so库。
    image

  现在我们在eclipse中运行spatialite-android这个工程(需要将test-2.3.sqlite数据库adb push到模拟器的sd卡上),就可以看到预期的结果了。
image  关于如何利用java语言调用spatialite的功能,可以详细参考这个工程中jsqlite包下的封装代码。至此我们就完成了在Android平台上使用spatialite库的准备工作。
  此次编译中遇到的问题可参考:http://code.google.com/p/spatialite-android/issues/detail?id=3

没有评论:

发表评论