GDAL在java中乱码问题解决方案

时间:2014-05-01 17:53:24   收藏:0   阅读:1741
[前序]
    首先关于GDAL源码方面,GDAL开源项目源码是使用C++语言所写,通过源码的编译可以生成支持一系列语言如c++/java/php/csharp/perl/python/ruby开发所依赖的第三方包或头文件。GDAL的同时支持不同平台下的编译生成,在Win平台下使用起来出现的问题较多,比如编译时选择不同的编译选项32位或x64位产生出不同的依赖库,还有中文路径、中文读写问题等等。
    在使用GDAL在java环境下进行读写数据文件时,遇到java语言读tab数据源出现乱码问题,具体的情况描述:gdal通过在WIN64位平台编译,生成的支持C++开发的头文件和dll文件,用于C++开发读写tab数据源不存在乱码;生成的支持java语言的JAR库及dll文件用于JAVA开发产生读写同一tab数据源出现乱码,数据内容乱码及中文路径乱码。

[编译支持JAVA环境]
download下的GDAL包,home目录下文件nmake.opt给出了一些选项,其中涉及到SWIG、JAVA_HOME 、ANT_HOME,首先要安装这几项,具体参照http://trac.osgeo.org/gdal/wiki/GdalOgrInJavaBuildInstructions#TestSampleApplication,
Install SWIG http://www.swig.org/download.html

Install an appropriate version of SWIG for windows. GDAL/OGR‘s Java bindings correct operation are fairly sensitive to the version of SWIG used. Version 1.3.39 is appropriate for GDAL/OGR 1.8.0. A Windows version with a pre-built executable is available, so compiling isn‘t necessary.

OSGeo4W has a "swig" package which will install a suitable version of swig in C:\OSGeo4W\apps\swigwin.

Install the latest version of the Java SE Development Kit
Make sure you are getting the "JDK", not just the runtime environment. The "SE" (standard edition?) is fine, no need for "EE" (enterprise edition).

 http://www.oracle.com/technetwork/java/javase/downloads/index.html

Install Apache Ant http://ant.apache.org/bindownload.cgi

Binary distributions are available, so compiling isn‘t necessary. 

本地配置可以修改为:
# Set the location of your SWIG installation
!IFNDEF SWIG
SWIG = D:\interShip\Sogou-inter\swigwin-2.0.11\swig.exe
!ENDIF
# SWIG Java settings
!IFNDEF JAVA_HOME
JAVA_HOME = D:\Java\jdk1.7.0_45
!ENDIF
!IFNDEF ANT_HOME
ANT_HOME=D:\interShip\Sogou-inter\apache-ant-1.9.3
!ENDIF
JAVADOC=$(JAVA_HOME)\bin\javadoc
JAVAC=$(JAVA_HOME)\bin\javac
JAVA=$(JAVA_HOME)\bin\java
JAR=$(JAVA_HOME)\bin\jar
JAVA_INCLUDE=-I$(JAVA_HOME)\include -I$(JAVA_HOME)\include\win32

在Win64平台下还要修改
# Uncomment the following if you are building for 64-bit windows
# (x64). You‘ll need to have PATH, INCLUDE and LIB set up for 64-bit
# compiles.
WIN64=YES

编译GDAL,使用VS打开GDAL源码,把相关的编译生成项目属性修改为如下
mamicode.com,码迷


完整的生成命令行:cd $(ProjectDir) && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 install 

然后使用命令行:cd $(ProjectDir) && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 install && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 devinstall

窗口目录中会生成相应的文件.lib、.dll、.pdb文件:
mamicode.com,码迷

然后编译支持JAVA的绑定,进入SWIG子目录下,
cd swig
nmake /f makefile.vc java
即编译选项命令为
mamicode.com,码迷
cd $(ProjectDir)/swig && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 && nmake -f makefile.vc MSVC_VER=1500 DEBUG=1 java 

这样在~/swig/java子目录下会生成几个文件:gdal.jar, gdalconstjni.dll, gdaljni.dll, ogrjni.dll and osrjni.dll.
mamicode.com,码迷

[java开发gdal环境部署]
把上面生成的gdal110.dll及四个dll文件全部一起放入JDK的开发环境目录,即bin子目录下;同时把jar包或者生成的源码放入eclipse开发项目环境之下进行开发。

上面所有呈现的前期工作进行java语言操作gdal数据源时会出现中文乱码问题

[重要解决之道]
出现中文乱码问题,于是我们开始寻求问题的解决策略,首先分析GDAL的源码问题,是不是GDAL操作方面源码有些BUG呢,find相关的字符编码函数,
    发现mitab.h内有virtual int SetCharset(const char* charset);及变量char *m_pszCharset;
    mitab_imapinfofile.cpp内有int IMapInfoFile::SetCharset(const char* pszCharset)方法,
进一步发现,mitab_miffile.cpp文件内设有写的模式编码 即
int MIFFile::Open(const char *pszFname, const char *pszAccess,
                  GBool bTestOpenNoError /*=FALSE*/ )  内包含
     /*-----------------------------------------------------------------
     * In write access, set some defaults
     *----------------------------------------------------------------*/
    if (m_eAccessMode == TABWrite)
    {
        m_nVersion = 300;
        m_pszCharset = CPLStrdup("Neutral");
    }
可知源码处理上没有具体设置为哪些编码,默认几乎就是中性的(本人认为哟,亲们)。

经过探索,生成的依赖项用于C++会没有乱码现象,但是用于JAVA会存在乱码,还有java自己读自己预先同一写的数据源,没有乱码现象,但是用于事先其它程序生成的有乱码问题,可知乱码一定会是因为某些方面的技术需要等解决的。

接下来,首先呈现出解决策略吧,需要修改一个重要的文件,并且在编码开始之前应该要运用修改后的文件,此文件是jni.h头文件,位于自己的jdk环境下的include目录之下:
把文件函数jstring NewStringUTF(const char *utf) 实现修改为:
jstring NewStringUTF(const char *utf) {
  jstring rtn = 0;
  int slen = strlen(utf);
  unsigned short * buffer = 0;
  if( slen == 0 )
  rtn = functions->NewStringUTF(this,utf );
  else
  {
   int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)utf, slen, NULL, 0 );
   buffer = (unsigned short *)malloc( length*2 + 1 );
   if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)utf, slen, (LPWSTR)buffer, length ) >0 )
   rtn = functions->NewString(this,(jchar*)buffer, length );
  }
  if( buffer )
   free( buffer );
  return rtn;
        //return functions->NewStringUTF(this,utf);
}
文件中函数const char* GetStringUTFChars(jstring str, jboolean *isCopy)实现修改为:
const char* GetStringUTFChars(jstring str, jboolean *isCopy) {
  int length = functions->GetStringLength(this,str );
  const jchar* jcstr = functions->GetStringChars(this,str, isCopy );
  char* rtn = (char*)malloc( length*2+1 );
  int size = 0;
  size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL );
  if( size <= 0 )
   return NULL;
  functions->ReleaseStringChars(this,str, jcstr );
  rtn[size] = 0;
  return rtn;
        //return functions->GetStringUTFChars(this,str,isCopy);
}

同时在行#include "jni_md.h"后面紧接着加上一行:
#include <windows.h>

整个文件为mamicode.com,码迷 ,链接为http://blog.csdn.net/makamus/article/details/21288283

用于java开发环境中,给出一段编写代码如:
static void ReadVectorFile()
 {
    String strVectorFile = "D:/interShip/Sogou-inter/无缝中国_地市_M.tab";
 
    // 注册所有的驱动
    ogr.RegisterAll();
 
    // 为了支持中文路径,请添加下面这句代码
    gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8","NO");
    // 为了使属性表字段支持中文,请添加下面这句
    gdal.SetConfigOption("SHAPE_ENCODING","");
 
   
    //打开数据
    DataSource ds = ogr.Open(strVectorFile,0);
    if (ds == null)
    {
       System.out.println("打开文件【"+ strVectorFile + "】失败!" );
       return;
    }
   
    System.out.println("打开文件【"+ strVectorFile + "】成功!" );
 
    // 获取该数据源中的图层个数,一般shp数据图层只有一个,如果是mdb、dxf等图层就会有多个
    int iLayerCount = ds.GetLayerCount();
         
          // 获取第一个图层
    Layer oLayer = ds.GetLayerByIndex(0);
    if (oLayer == null)
    {
         System.out.println("获取第0个图层失败!\n");
         return;
    }
 
          // 对图层进行初始化,如果对图层进行了过滤操作,执行这句后,之前的过滤全部清空
    oLayer.ResetReading();
 
          // 通过属性表的SQL语句对图层中的要素进行筛选,这部分详细参考SQL查询章节内容
          //oLayer.SetAttributeFilter("\"NAME99\"LIKE \"北京市市辖区\"");
 
          // 通过指定的几何对象对图层中的要素进行筛选
          //oLayer.SetSpatialFilter();
 
          // 通过指定的四至范围对图层中的要素进行筛选
          //oLayer.SetSpatialFilterRect();
 
          // 获取图层中的属性表表头并输出
    System.out.println("属性`结构信息:");
    FeatureDefn oDefn =oLayer.GetLayerDefn();
    int iFieldCount =oDefn.GetFieldCount();
    for (int iAttr = 0; iAttr <iFieldCount; iAttr++)
    {
        FieldDefn oField =oDefn.GetFieldDefn(iAttr);
 
        try {
    System.out.println(new String(oField.GetNameRef())+ ": " +
                           oField.GetFieldTypeName(oField.GetFieldType())+ "(" +
                  oField.GetWidth()+"."+ oField.GetPrecision() + ")");
     } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
     }
 
          // 输出图层中的要素个数
          System.out.println("要素个数 = " + oLayer.GetFeatureCount(0));
 
          Feature oFeature = null;
          // 下面开始遍历图层中的要素
          while ((oFeature =oLayer.GetNextFeature()) != null)
          {
                    System.out.println("当前处理第" + oFeature.GetFID() + "个:\n属性值:");
                    // 获取要素中的属性表内容
                    for (int iField = 0; iField< iFieldCount; iField++)
                    {
                             FieldDefn oFieldDefn= oDefn.GetFieldDefn(iField);
                             int type =oFieldDefn.GetFieldType();
 
                             switch (type)
                             {
                                case ogr.OFTString:
         try {
          System.out.print(new String(oFeature.GetFieldAsString(iField))+ "\t");
         } catch (Exception e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
         }
                                    break;
                                case ogr.OFTReal:
                                                System.out.print(oFeature.GetFieldAsDouble(iField)+ "\t");
                                                break;
                               case ogr.OFTInteger:
                                                System.out.print(oFeature.GetFieldAsInteger(iField)+ "\t");
                                                break;
                               default:
                                                System.out.print(oFeature.GetFieldAsString(iField)+ "\t");
                                                break;
                             }
                    }
                    System.out.println();
                    // 获取要素中的几何体
                    Geometry oGeometry =oFeature.GetGeometryRef();
 
                    //
                   
          }
 
          System.out.println("数据集关闭!");
 }

注意上面红色标记的一行代码,此行表明对中文路径的支持,这涉及到gdal里面的一些开发选项问题,具体可以参照文章:http://trac.osgeo.org/gdal/wiki/ConfigOptions内的一些选项说明,

GDAL_FILENAME_IS_UTF8

This option only has an effect on Windows systems (using cpl_vsil_win32.cpp). If set to "NO" (default is YES) then filenames passed to functions like VSIFOpenL() will be passed on directly toCreateFile?() instead of being converted from UTF-8 to wchar_t and passed to CreateFileW(). This effectively restores the pre-GDAL1.8 behavior for handling filenames on Windows and might be appropriate for applications that treat filenames as being in the local encoding.

一切都能顺利的解决,下面需要谈到一些此方面的总结和知识点。

[问题总结与知识梳理]
jdk内的jni.h头文件是什么?此问题用以下一段话来回答
从Java1.1开始,JNI标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

首先,需要明确几个关于编码的基本概念:

◆  java内部是使用的16bit的unicode编码(utf-16)来表示字符串的,无论英文还是中文都是2字节;

◆ jni内部是使用utf-8编码来表示字符串的,utf-8是变长编码的unicode,一般ascii字符是1字节,中文是3字节;

◆ c/c++使用的是原始数据,ascii就是一个字节,中文一般是GB2312编码,用2个字节表示一个汉字。

jni的中文字符串处理

先从字符流的方向分别对java-->C++和C++-->java进行分析

◆ java-->C++

这种情况下,java调用的时候使用的是utf-16编码的字符串,jvm把这个参数传递给jni,C++得到的输入是jstring,此时,可以利用jni提供的两种函数,一个是GetStringUTFChars,这个函数将得到一个UTF-8编码的字符串;另一个是GetStringChars这个将得到UTF-16编码的字符串。无论那个函数,得到的字符串如果含有中文,都需要进一步转化成GB2312的编码。

mamicode.com,码迷

◆  c/c++ –> java

jni返回给java的字符串,c/c++首先应该负责把这个字符串变成UTF-8或者UTF-16格式,然后通过NewStringUTF或者NewString来把它封装成jstring,返回给java就可以了。

mamicode.com,码迷

如果字符串中不含中文字符,只是标准的ascii码,那么用GetStringUTFChars/NewStringUTF就可以搞定了,因为这种情况下,UTF-8编码和ascii编码是一致的,不需要转换。

但是如果字符串中有中文字符,那么在c/c++部分进行编码转换就是一个必须了。我们需要两个转换函数,一个是把UTF8/16的编码转成GB2312;一个是把GB2312转成UTF8/16。

这里要说明一下:linux和win32都支持wchar,这个事实上就是宽度为16bit的unicode编码UTF16,所以,如果我们的 c/c++程序中完全使用wchar类型,那么理论上是不需要这种转换的。但是实际上,我们不可能完全用wchar来取代char的,所以就目前大多数应用而言,转换仍然是必须的。

具体的转换函数,linux和win32都有一定的支持,比如glibc的mbstowcs就可以用来把 GB2312编码转成UTF16,但是这种支持一般是平台相关的(因为c/c++的标准中并没有包括这部分),不全面的(比如glibc就没有提供转成 UTF8的编码),不独立的(linux下mbstowcs的行为要受到locale设置的影响)。所以我推荐使用iconv库来完成转换。

iconv库是一个免费的独立的编码转换库,支持很多平台,多种编码(事实上,它几乎可以处理我们所使用的所有字符编码),而且它的行为不受任何外部环境的影响。iconv在*nix平台上,基本上是缺省安装的。在win32平台上需要额外安装。


1、从jni的接口看,jni提供了UTF16和UTF8两个系列的字符串处理函数,但是由于jni的文档中说,jni的内部实现中是用UTF8作为字符串编码格式的,所以使用UTF8系列比较合适(NewStringUTF/GetStringUTFChars /ReleaseStringUTFChars)

2、使用iconv库的话,运行环境的设置对于编码转换是没有影响的,但是外层java程序对于字符串的解析依赖于运行环境的locale,所以设置正确的locale对于jni意义不大,但是对整个系统还是必要的。

以上是主要是说明使用第三方库去解决编码问题,针对仅在windows平台下,是可以使用windows提供的相关方法进行编码转换的。

使用一下方法可以将jstring转换为char*,主要用于在C++中接收java传递过来的参数时包含中文字符时使用。在转换过程中已经对编码进行了转换,可以正常返回出中文字符。

char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)
{
    jsize len = pJNIEnv->GetStringLength(jstr);
    const jchar * jcstr = pJNIEnv->GetStringChars(jstr, NULL);
    int size = 0;
    char * str = (char *)malloc(len * 2 + 1);
    if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)
        return NULL;
    pJNIEnv->ReleaseStringChars(jstr, jcstr);
    str[size] = 0;
    return str;

注意:以上方法中返回的char*在使用过后需要delete释放,因为在编码转换过程中使用了malloc分配了内存,不释放会发生内存泄漏。

如果需要在C++中返回中文信息给java,则需要以下方法将char*转换成jstring

jstring WindowsTojstring( JNIEnv* env, char* str )
{
    jstring rtn = 0;
    int slen = strlen(str);
    unsigned short * buffer = 0;
    if( slen == 0 )
        rtn = (env)->NewStringUTF(str );
    else
    {
        int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
        buffer = (unsigned short *)malloc( length*2 + 1 );
        if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
            rtn = (env)->NewString( (jchar*)buffer, length );
    }
    if( buffer )
        free( buffer );
    return rtn;
}  

根据前面的字符流的分析,和后面提供的转换方法,基本上可以解决jni中中文参数乱码的问题。

GDAL在java中乱码问题解决方案,码迷,mamicode.com

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!