ラベル GDAL の投稿を表示しています。 すべての投稿を表示
ラベル GDAL の投稿を表示しています。 すべての投稿を表示

2019年10月11日金曜日

geotiff の変換あれこれ忘備録

ウィンドウズのバッチでまとめて見ました。
Windows 10 がコマンドプロンプトの utf-8 に対応したため、chcp 65001 をしてコマンドを実行した方が、トラブルが少ないです。

geotiff ファイルから png ファイルに変換する(goetiff to png)

if "%1"=="" goto err
chcp 65001
GDAL_TRANSLATE -OF PNG -B 1 -B 2 -B 3 %1.tif %1.png
goto end
:err
echo param1 [basefilename]
:end


geotiff を分割する(divide geotiff)

if "%1"=="" goto err
chcp 65001
echo divide geotiff 5000x5000
mkdir %1
call gdal_retile -ps 5000 5000 -targetDir %1 %1.tif
:err
echo param1 [basefilename]
:end


png の色を変換する

if "%1"=="" goto err
chcp 65001
convert %1.png -fill "#FEFEFE" -opaque "#FFFFFF" %1_.png
convert %1_.png -fill "#010101" -opaque "#000000" %1_dst.png
:err
echo param1 [basefilename]
:end


png を分割する(divide png)

if "%1"=="" goto err
chcp 65001
echo divide png 5000x5000
convert %1.png -crop 5000x5000 +repage +adjoin %1\%1_%%02d.png
:err
echo param1 [basefilename]
:end


World Fileを出力する(export world file(*.tfw) from geotiff)

if "%1"=="" goto err
chcp 65001
for %%s in (*.tif) do listgeo -tfw %%s
:err
echo param1 [basefilename]
:end

2015年6月29日月曜日

QGISの作るshapefileのdbf

しばしば ISO-8859-1 というコードページに解釈されるケースが多いので、そのようなファイルがどうなっているのか調べてました。

DBFファイルの先頭から 29バイト目 の0x0057 とかで入っていて、87=ISO-8859-1となってます。
日本語版のArcGISで、これを読むと、ちゃんとSJISとして解釈してくれる。もうたまりませんわ。
 ISO-8859-1が多数派なんで、自然な流れかと。

ちゃんとしてくれないツールが多くて閉口します。

2014年11月6日木曜日

GDAL-1.11.1 における ShapeDriver の日本語設定

前回「GDAL-1.10.1 の日本語環境ではまった」からの続きです。 うまく行ったり、行かなかったりは、文字コードが UTF-8 だったり SJIS だったりと、判別のしようが無い Shape が混在していたのが大きな原因でした。 それで、定説の 環境変数
set SHAPE_ENCODING=LDID/19 
ですが、そこを解釈するコードは、このようになっております。
CPLString OGRShapeLayer::ConvertCodePage( const char *pszCodePage )

{
    CPLString osEncoding;
  osEncoding.Clear();

    if( pszCodePage == NULL )
        return osEncoding;

    if( EQUALN(pszCodePage,"LDID/",5) )
    {
        int nCP = -1; // windows code page. 

        //http://www.autopark.ru/ASBProgrammerGuide/DBFSTRUC.HTM
        switch( atoi(pszCodePage+5) )
        {
          case 1: nCP = 437;      break;
          case 2: nCP = 850;      break;
          case 3: nCP = 1252;     break;
          case 4: nCP = 10000;    break;
          case 8: nCP = 865;      break;
          case 10: nCP = 850;     break;
          case 11: nCP = 437;     break;
          case 13: nCP = 437;     break;
          case 14: nCP = 850;     break;
          case 15: nCP = 437;     break;
          case 16: nCP = 850;     break;
          case 17: nCP = 437;     break;
          case 18: nCP = 850;     break;
          case 19: nCP = 932;     break;
          case 20: nCP = 850;     break;
          case 21: nCP = 437;     break;
          case 22: nCP = 850;     break;
          case 23: nCP = 865;     break;
          case 24: nCP = 437;     break;
          case 25: nCP = 437;     break;
          case 26: nCP = 850;     break;
          case 27: nCP = 437;     break;
          case 28: nCP = 863;     break;
          case 29: nCP = 850;     break;
          case 31: nCP = 852;     break;
          case 34: nCP = 852;     break;
          case 35: nCP = 852;     break;
          case 36: nCP = 860;     break;
          case 37: nCP = 850;     break;
          case 38: nCP = 866;     break;
          case 55: nCP = 850;     break;
          case 64: nCP = 852;     break;
          case 77: nCP = 936;     break;
          case 78: nCP = 949;     break;
          case 79: nCP = 950;     break;
          case 80: nCP = 874;     break;
          case 87: return CPL_ENC_ISO8859_1;
          case 88: nCP = 1252;     break;
          case 89: nCP = 1252;     break;
          case 100: nCP = 852;     break;
          case 101: nCP = 866;     break;
          case 102: nCP = 865;     break;
          case 103: nCP = 861;     break;
          case 104: nCP = 895;     break;
          case 105: nCP = 620;     break;
          case 106: nCP = 737;     break;
          case 107: nCP = 857;     break;
          case 108: nCP = 863;     break;
          case 120: nCP = 950;     break;
          case 121: nCP = 949;     break;
          case 122: nCP = 936;     break;
          case 123: nCP = 932;     break;
          case 124: nCP = 874;     break;
          case 134: nCP = 737;     break;
          case 135: nCP = 852;     break;
          case 136: nCP = 857;     break;
          case 150: nCP = 10007;   break;
          case 151: nCP = 10029;   break;
          case 200: nCP = 1250;    break;
          case 201: nCP = 1251;    break;
          case 202: nCP = 1254;    break;
          case 203: nCP = 1253;    break;
          case 204: nCP = 1257;    break;
          default: break;
        }

        if( nCP != -1 )
        {
            osEncoding.Printf( "CP%d", nCP );
            return osEncoding;
        }
    }

    // From the CPG file
    // http://resources.arcgis.com/fr/content/kbase?fa=articleShow&d=21106
    
    if( (atoi(pszCodePage) >= 437 && atoi(pszCodePage) <= 950)
        || (atoi(pszCodePage) >= 1250 && atoi(pszCodePage) <= 1258) )
    {
        osEncoding.Printf( "CP%d", atoi(pszCodePage) );
        return osEncoding;
    }
    if( EQUALN(pszCodePage,"8859",4) )
    {
        if( pszCodePage[4] == '-' )
            osEncoding.Printf( "ISO-8859-%s", pszCodePage + 5 );
        else
            osEncoding.Printf( "ISO-8859-%s", pszCodePage + 4 );
        return osEncoding;
    }
    if( EQUALN(pszCodePage,"UTF-8",5) )
        return CPL_ENC_UTF8;

    // try just using the CPG value directly.  Works for stuff like Big5.
    return pszCodePage;
}
これのディスカッションは、ESRI社のフォーラムで話題になっていました。
コード中の解説ページhttp://www.autopark.ru/ASBProgrammerGuide/DBFSTRUC.HTMが詳しいです。
CP932 に相当するケース文の値は、0x7B でなければなりませんから、10進数にしますと、123になります。上記コードと照らしあわせても整合がとれていますよね? という事で、LDID/19 でもOKなのですが、
set SHAPE_ENCODING=LDID/123 
を設定すると、CP932 に変換してくれる。
もしくは、最初から CP932 を指定していれば、LDID/の変換をパスして、上記関数を通った後の値が評価されるので
set SHAPE_ENCODING=CP932 
でもOKだったという事になるのでしょうか? 拡張子 DBF ファイルの 29バイト目に、case 文の値か、UTF-8なら -1 == 65535 == 0xFFFF を設定するように改善していけば良さそうですよね?
と、思ったら、-1 は CPACP 扱い? int は 32bit 以上で、65535 == 0xFFFF の解釈で考えればOKでしょうか? 同日追記:  実は SHAPE_ENCODING= を設定しない方が、うまく動く場合もあるようです。どうも、前回DBFファイルに設定があった場合は、SHAPE_ENCODINGは使用されないような事を書きましたが、その記述が間違いだったみたいです。そして、SHAPE_ENCODING=UTF-8 と設定してみましたが、挙動が違うような・・・。もうちょい、突っ込んで調べてみます・・・

2014年7月25日金曜日

GDAL-1.10.1 の日本語環境ではまった

 たまーに、お客さんから DBF ファイルにエンコーディング(文字コード)の設定をしていない Shape ファイルを貰うんですわ。 そしたら、ogr2ogr にかけると、文字化けするんです。
set SHAPE_ENCODING=LDID/19 
って、やっても文字化けは直らないんで、調べました。 DBF をバイナリエディタで見ると、もらった Shape ファイルは、DBFファイル中のヘッダーパートの29バイト目の値(エンコーディング)が 0 のままで、データに使用されている文字コードはCP932(Shift_JIS)でした。 GDALのコード中の処理順序は、こんな感じです。

  1.  DBF ファイルの codepage が設定されているかどうかで処理
  2. CPGファイルに codepage が設定されているかどうかで処理
  3. 環境変数 SHAPE_ENCODING の値が設定されているかどうかで処理
  4. プログラム・オプションに codepage が指定されているかどうかで処理
  5. 文字コードは変換しないで、そのまま利用する
これ、SHAPE_ENCODING を設定してても、DBFファイルにエンコーディングが設定されていれば、正しい変換を行ってくれるので、素晴らしいです。
調査していると、1.10.1 のバージョンでは
set SHAPE_ENCODING=CP932
としないと、ちゃんとSJISとして扱ってくれないようでした。いつから LDID/19 じゃなくなった? こんな調査に半日以上費やしました・・・(`;ω;´) 2014/09/04 追記: 環境によって、SHAPE_ENCODING=LDID/19 にしないと変換してくれない場合があったりして混乱中。  ソースを修正しすぎた?  なんと、UTF-8 コードで書かれた shape と、CP932 コードで書かれた shape が混在している事が判明しました。  エスパーじゃあるまいし、知るか!

2013年3月26日火曜日

本日のげんなり TIFF編

 今日は参りました。 TIFFGetField という関数には、前回もやられているんですが、今回も、こてんこてんにやっつけられました。 TIFFTAG_GDAL_NODATA というタグの値を取得したくて、GDALのGeoTIFFパートを参考にコードを組んだら SIGSEGVの嵐。は〜 SIGSEGV、どんどん、ほぇー SIGSEGV、どんどん、ハイヤー SIGSEGV バンバン。
   char* pszText = NULL;
    if( TIFFGetField( hTIFF, TIFFTAG_GDAL_NODATA, &pszText ) )
    {
        bNoDataSet = TRUE;
        dfNoDataValue = CPLAtofM( pszText );
    }
突っ込んで面倒を見ていくと libtiff の tif_dir.c の int _TIFFVGetField(TIFF* tif, uint32 tag, va_list ap) という関数でお亡くなりになっている事が判明しました。
  // なんと、field_passcount > 0 が成立しているので
  if (fip->field_passcount) {
    if (fip->field_readcount == TIFF_VARIABLE2) {
      // pszText にデータサイズが格納されて
      *va_arg(ap, uint32*) = (uint32)tv->count;
    } else  /* Assume TIFF_VARIABLE */ {
      *va_arg(ap, uint16*) = (uint16)tv->count;
    }
    // ありもしない第2の可変引数に値が格納されて
    // ここであぼ~ん 
    *va_arg(ap, void **) = tv->value;
    ret_val = 1;
  }
(゚∀゚)アヒャ(゚∀゚)アヒャ。関数の仕様というか、TAGの仕様が変わっとるがなwww 知らねぇよヽ(`Д´)ノウワァァァン!! と言いながらコードを修正
   uint32_t textLen = 0;
   char* pszText = NULL;
    if( TIFFGetField( hTIFF, TIFFTAG_GDAL_NODATA, &textLen, &pszText ) )
    {
        bNoDataSet = TRUE;
        dfNoDataValue = CPLAtofM( pszText );
    }
あれれ?まだまだ SIGSEGV おほー、どんどん SIGSEGV ひ~~。 更に突っ込んで面倒を見ていくと、同じく tif_dir.c にて
  // えっ?今度は、field_passcount == 0
  if (fip->field_passcount) {
    //...
  } else if (fip->field_tag == TIFFTAG_DOTRANGE
     && strcmp(fip->field_name,"DotRange") == 0) {
    //...
  } else {
    // そして、field_type == TIFF_ASCII なので
    if (fip->field_type == TIFF_ASCII
      || fip->field_readcount == TIFF_VARIABLE
      || fip->field_readcount == TIFF_VARIABLE2
      || fip->field_readcount == TIFF_SPP
      || tv->count > 1) {
          // 第1引数 uint32_t にポインタ値が代入され
          // 第2引数が放置プレーで、最終的にあぼ~ん
          *va_arg(ap, void **) = tv->value;
          ret_val = 1;
    } else {
      //...
 隊長!エスパーじゃないから、推定できましぇんヽ(`Д´)ノウワァァァン!!  ということで、とりあえず、2引数を取る形態で対処しました。
    if (fip->field_type == TIFF_ASCII
      || fip->field_readcount == TIFF_VARIABLE
      || fip->field_readcount == TIFF_VARIABLE2
      || fip->field_readcount == TIFF_SPP
      || tv->count > 1) {
          if( (tag == TIFFTAG_GDAL_METADATA) || (tag == TIFFTAG_GDAL_NODATA) ) {
             *va_arg(ap, uint32*) = (uint32)strlen(tv->value);
          }
          *va_arg(ap, void **) = tv->value;
          ret_val = 1;
    } else {
 もの凄く疲れた。

2012年11月12日月曜日

gdal patch 結末

 前回投下したパッチですが、-optfile というオプション自体をテキストファイルで受け渡すオプション(ややこしい)が追加されており、これを利用すると、UTF-8の文字コードでオプションを渡しても文字化けが起きないので、パッチは不要という事になりました。  この方法は確かにスマートで、自分も base64 でオプションを指定するオプションとか、似たような事を考えてました。UTF-8でオプションが使える事の利点は、QGISなどの既にマルチ言語で動作しているアプリケーションから、複数の言語をチャンポンにして扱える事が挙げられます。  チケットでのディスカッションは、こちら。なかなか話が通じなくて、困りました。多分、どちらも、そう思っていた事でしょう。  ま、自分の投下したパッチも、まるっきり無駄では無くて、コンソール画面にエラーを出力する場合には利用できると思います。ただ、クリティカルでは無いので、取り込まなくても支障は無いでしょう。とりあえず、何のためにあるねん chcp 65001 アホけ?という辺りで締めたいと思います。

2012年11月2日金曜日

GDAL patch 投下

 とりあえず、QGisのコミュニティのCavaliniさんに、パッチを渡してみましたが、QGis の問題というよりもGDALの問題なんで、GDAL-developer ML に投下した方が良いのかも?  ただ前回、短いpatchだからOKかも?と思ってGDAL-dev MLにパッチ投稿したら、MLへのpatch投下は、やっぱり遠慮して欲しいと言われたので、説明をした上で、どなたかにパッチを送るという手順になりそう。 アカウントは登録されているはずなんだけど、OSGeo の track にログインできないので、track に直接投下できなくて、困ってるのだ…。 パッチは、version 'gdal-2.0dev' https://svn.osgeo.org/gdal/trunk/gdal のもので、新しくコマンドライン引数を管理するクラスが生成されていたので、ちょこっと直すだけで、コマンドライン型アプリ全部に適用されそうです。
*** ogr/ogrutils.cpp Fri Nov  2 10:47:34 2012
--- ../gdal_new/ogr/ogrutils.cpp Fri Nov  2 12:28:33 2012
***************
*** 754,760 ****
  /* -------------------------------------------------------------------- */
          else
          {
!             papszReturn = CSLAddString( papszReturn, papszArgv[iArg] );
          }
      }
  
--- 754,762 ----
  /* -------------------------------------------------------------------- */
          else
          {
!             char* pszTmp = CPLRecodeCommandInput( papszArgv[iArg] );
!             papszReturn = CSLAddString( papszReturn, pszTmp );
!             CPLFree(pszTmp);
          }
      }
  
*** port/cpl_string.h Fri Nov  2 10:47:29 2012
--- ../gdal_new/port/cpl_string.h Fri Nov  2 12:29:27 2012
***************
*** 177,182 ****
--- 177,184 ----
                                     const char *pszDstEncoding ) CPL_WARN_UNUSED_RESULT;
  int CPL_DLL CPLIsUTF8(const char* pabyData, int nLen);
  char CPL_DLL *CPLForceToASCII(const char* pabyData, int nLen, char chReplacementChar) CPL_WARN_UNUSED_RESULT;
+ char CPL_DLL *CPLRecodeCommandInput( const char* pszSource ) CPL_WARN_UNUSED_RESULT;
+ char CPL_DLL *CPLRecodeCommandOutput( const char* pszSource ) CPL_WARN_UNUSED_RESULT;
  
  CPL_C_END
  
*** port/cpl_recode.cpp Fri Nov  2 10:47:30 2012
--- ../gdal_new/port/cpl_recode.cpp Fri Nov  2 12:16:08 2012
***************
*** 324,329 ****
--- 324,372 ----
          return -1;
  }
  
+ 
+ /************************************************************************/
+ /*                    CPLRecodeCommandInput()                           */
+ /************************************************************************/
+ /**
+  * Convert a string from a platform encoding to UTF-8, effects only windows platform.
+  *
+  * There is a I/O problem in windows command line character arguments.
+  * Unfortunately, Run batch file written in UTF-8 fails some case.
+  *
+  * @return a NULL terminated string which should be freed with CPLFree().
+  */
+ 
+ char CPL_DLL *CPLRecodeCommandInput( const char* pszSource )
+ 
+ {
+ #ifdef _WIN32
+   return CPLRecode( pszSource, "CP0", CPL_ENC_UTF8 );
+ #else
+   return CPLStrdup(pszSource);
+ #endif
+ }
+ 
+ /************************************************************************/
+ /*                    CPLRecodeCommandOutput()                          */
+ /************************************************************************/
+ /**
+  * Convert a string from UTF-8 to a platform encoding, effects only windows platform.
+  *
+  * There is a I/O problem in windows command line character arguments.
+  *
+  * @return a NULL terminated string which should be freed with CPLFree().
+  */
+ char CPL_DLL *CPLRecodeCommandOutput( const char* pszSource )
+ 
+ {
+ #ifdef _WIN32
+   return CPLRecode( pszSource, CPL_ENC_UTF8, "CP0" );
+ #else
+   return CPLStrdup(pszSource);
+ #endif
+ }
+ 
  /************************************************************************/
  /*                    CPLClearRecodeWarningFlags()                      */
  /************************************************************************/
*** port/cpl_recode_stub.cpp Fri Nov  2 10:47:29 2012
--- ../gdal_new/port/cpl_recode_stub.cpp Fri Nov  2 11:43:30 2012
***************
*** 166,172 ****
          && strcmp(pszDstEncoding,CPL_ENC_UTF8) == 0 )
      {
          int nCode = atoi( pszSrcEncoding + 2 );
!         if( nCode > 0 ) {
             return CPLWin32Recode( pszSource, nCode, CP_UTF8 );
          }
      }
--- 166,172 ----
          && strcmp(pszDstEncoding,CPL_ENC_UTF8) == 0 )
      {
          int nCode = atoi( pszSrcEncoding + 2 );
!         if( nCode >= 0 ) {
             return CPLWin32Recode( pszSource, nCode, CP_UTF8 );
          }
      }
***************
*** 178,184 ****
          && strncmp(pszDstEncoding,"CP",2) == 0 )
      {
           int nCode = atoi( pszDstEncoding + 2 );
!          if( nCode > 0 ) {
               return CPLWin32Recode( pszSource, CP_UTF8, nCode );
           }
      }
--- 178,184 ----
          && strncmp(pszDstEncoding,"CP",2) == 0 )
      {
           int nCode = atoi( pszDstEncoding + 2 );
!          if( nCode >= 0 ) {
               return CPLWin32Recode( pszSource, CP_UTF8, nCode );
           }
      }
 
2012/11/05 追記:やっぱ、GDAL-dev ML に投下しました。

Windows CMD args character conversion problem.

 ウィンドウズ環境におけるコマンドプロンプトの問題を説明するために、簡単なプログラムを書いてみました。ここが理解できないと、パッチが受け入れられないだろうとおもったので・・・
//
// 'Command Prompt Argument' check program
//
//  To compile, type below
//
//  cl /EHa /GR /MD chkarg.cpp
//

#include <iostream>
#include <iomanip>


void showHex(const char* str) {
  for( const unsigned char* p = reinterpret_cast<const unsigned char*>(str); *p != 0; ++p ) {
     std::cout << std::hex << "0x" << (int)*p << " ";
  }
  std::cout << std::endl;
}


int main(int argc, char* argv[]) {
  if( argc <= 1 ) return 1;

  showHex(argv[1]);

  return 0;
}
これを実行するバッチファイルは、こんなんです。
@echo off
echo Bad case UTF-8 arg command check
echo This text file was written in UTF-8 with Kanji.
echo result must be 
echo 0xe5 0x85 0x88
echo ===> run chkarg.
chkarg 先
 実行結果は
Bad case UTF-8 arg command check
This text file was written in UTF-8 with Kanji.
result must be
0xe5 0x85 0x88
0xe5 0x85 0x81 0x45
となります。chcp 65001 を行なっても状況に変わりはありません。

2012年8月30日木曜日

QGis Python Plugin と日本語の周辺3

 前回、UTF8で保存したバッチファイルを実行すればOKです。なんて書きましたが、問題がありました。 ウィンドウズのコマンドプロンプトが、コマンドラインの一部の文字コードの値を変更してしまうのです。そのため、使う文字コードによっては、まともに動作しません。  とりあえず、判明したのは「先」という漢字です。バイナリで表現すると、0xE5,0x85,0x88 の組みになります。もしかしたら、ANSI.SYS とかが組み込まれてて悪さしてるのかな?というのが頭を過ぎったのですが、Windows7 あたりでは、config.nt ファイルや、autoexec.nt ファイルなんて、ありませんでした。  コマンドプロンプトで chcp 65001(=UTF8) とか指定できますが、コンソールを通過すると文字化けするので、現時点では、まともに動作しません。よって、コマンドラインから入力される文字列は、MBCS (日本の場合 CP932=SJIS=ShiftJIS)に限定され、選択肢はありません。ウィンドウズだけのために、UNICODE文字セットを標準としてコンパイルするのは、問題があると思いますので、最小限の対処法は、入力値を MBCS から UTF8 に変換してやる事になります。  では、これを国際的に実装しようと思ったら、MultiByteToWideChar API を使って CPACP (= CP0) から、UNICODE に変換し、WideCharToMultiByte で UTF8 (=CP65001) にする以外に方法がありません。そうです、CPLWin32Recode 関数に "CP0" , "CP65001" という変換を促してやればいいのです。リポジトリに取り込まれたコードでは、CP0 は使わないだろうと思って、入力条件として コードページ > 0 としてしまったので、この条件を >= 0 とするよりありません。後、内部でUNICODEに相互変換する関数の実装があるのですが、変に複雑なんでウィンドウズの場合は MultiByteToWideChar と WideCharToMultiByte の関数に置き換えてあげた方が良いと考えてます。これらの変更を加えた上で、ogr2ogr 等の全てのコマンドプログラムに対して、コマンドの引数の文字コード変換を行うコードを挿入する必要があります。CPLArgumentConvert とかいう関数を設置してあげて、コーディング・ガイドラインを提示してあげた方が良いのかもしれません。という辺りを GDAL/OGR Develpment ML に投げてみようかと考えてます。 追記:GDAL_FILENAME_IS_UTF8 というのは、ウィンドウズの場合必須になります。ここを NO に設定すると、英語圏以外の文字コードは動作しません。  

2012年7月19日木曜日

QGis Python Plugin と日本語の周辺2

環境

  • Windows 7 x64
  • Quantum GIS Lisboa 1.8
  • GDAL/OGR 1.9.1

   いろいろと思考錯誤していて、GDAL/OGR がどのように多言語に対応しようとしているのか、わかってきました。
QGis Python Plugin と日本語の周辺1 に書いていたコードは、実はベストプラクティスでは無い事が判明いたしました。

  まずは、基本的なおさらいから…
Linux, mac 等の環境では、UTF8 で文字コードを取り扱うのが基本になります。それに対して Windows では MBCS がベースで、プログラム内部の文字コードは、MBCS か UNICODE(UCS2) を選択します。Visual C++ のコンパイルオプションで、どちらを選択するか可能なのですが、もう少し中身を説明すると、Windows API には、MBCS 系の文字コードを引数にとる HogeA という大文字 A をサフィックスにとるものと、UNICODEの文字コードを引数にとる HogeW という大文字 W をサフィックスにとるものが対になって存在します。C言語では、単に
#ifdef _UNICODE
#define Hoge HogeW
#else
#define Hoge HogeA
#endif
というように、マクロで切り替えているだけなのです。   ですので、MBCS系の文字コードでコンパイルをしていても、UNICODE系のAPIをコールする事は可能となっています。

  ここから、本題に入ります。QGIS では、GDAL_FILENAME_IS_UTF8 = YES を前提としています。それは、どのようなトリックかというと、ogr2ogr.exe 等の実行ファイルのパスは MBCS でないと実行できません。ところが、コマンドラインのオプション引数は MBCS である必要は、ありません。極端な話、UTF8 の文字コードをコマンドラインのオプション引数に渡しても問題ありません。   GDAL/OGR の shape ドライバでは、Windows 環境かつ、GDAL_FILENAME_IS_UTF8 = YES であれば、UTF8で引き渡された文字コードを内部で UNICODEに変換して、ファイル操作を行うように書かれていました。この手法でいけば、windows 付属の iconv.dll では、UTF8 と UNICODE 間の変換さえ、うまく行けば、IO(入出力回り)の文字化けに対応できる事になります。

  では、どうすれば日本語のファイルをコマンドラインから扱う事ができるか?
GDAL_FILENAME_IS_UTF8 = NO というような設定は、実は一切不要でして、サクラエディタなどのUTF8が扱えるエディタを使って、バッチファイルを作成し、ファイルを保存する時に、文字コードを「UTF8」にして保存してやればOKでした。QGISのインストール先を日本語の混じったフォルダ下にしなければ、これで、問題なく動くはずです。
UTF-8で書かれたバッチファイルを実行する事です。

問題は、Python で、どうやって UTF-8 な文字コードを subprocess.call に引き渡す事ができるのか。ここが、わかりませんorz...。decode encode('utf-8') してやっても、文字化けします。どうも、スクリプトは自由度が低くて相性の悪さを感じます(>_<)。 2012/07/24 追記:"日本語" の "語" が python の文字コード変換で文字化けするので、自前で DLL を作成して、その中で文字コード変換するようにした。 2012/08/30 追記:その3に続く

2012年7月18日水曜日

GDAL shape driver を深追いしてみた…

どうにも、GDAL(1.9.1)/OGR shape driver が日本語のファイル名を受け付けないので、コードを追ってみました。
VSIFilesystemHandler *VSIFileManager::GetHandler( const char *pszPath )

{
    VSIFileManager *poThis = Get();
    std::map<std::string,VSIFilesystemHandler*>::const_iterator iter;
    int nPathLen = strlen(pszPath);

    for( iter = poThis->oHandlers.begin();
         iter != poThis->oHandlers.end();
         ++iter )
    {
        const char* pszIterKey = iter->first.c_str();
        int nIterKeyLen = iter->first.size();
        if( strncmp(pszPath,pszIterKey,nIterKeyLen) == 0 )
            return iter->second;

        /* "/vsimem\foo" should be handled as "/vsimem/foo" */
        if (nIterKeyLen && nPathLen > nIterKeyLen &&
            pszIterKey[nIterKeyLen-1] == '/' &&
            pszPath[nIterKeyLen-1] == '\\' &&
            strncmp(pszPath,pszIterKey,nIterKeyLen-1) == 0 )
            return iter->second;

        /* /vsimem should be treated as a match for /vsimem/ */
        if( nPathLen == nIterKeyLen - 1
            && strncmp(pszPath,pszIterKey,nIterKeyLen-1) == 0 )
            return iter->second;
    }
    
    return poThis->poDefaultHandler;
}
  "\" 記号文字を "/" 記号文字で置き換えしようとしているのか?これ、SJIS だと典型的にハマるパターンですね。 "表" という SJIS コードは、16進数で表すと 0x955C になります。一方 "\" という ASCII コードは、16進数で表すと 0x5C になります。日本語SJISの取り扱いでは、それぞれの char コードの範囲が SJIS の第1バイトかどうかを調べて、第一バイトに該当した時には、第2バイトもSJISコードなので、ASCII コードとしては処理をしないという条件分岐が必要になります。  SJISが出来た当初は、ASCII と被らないで、なるべく2バイト内に納めて漢字の表現をしないといけなかった事情があるので、この設計は仕方ない事ではありますが、特殊すぎます。  とにかく、こういう罠があるので、内部では UNICODE(UCS2)か、UTF8にすべきなんですが、まじうざい。いっその事、標準で ufopen とか、UTF8 のパスを受け付ける関数とかが、めっちゃ欲しいです。 標準で ufopen とか、UTF8 のパスを受け付ける関数とかが、めっちゃ欲しいです。 標準で ufopen とか、UTF8 のパスを受け付ける関数とかが、めっちゃ欲しいです。 標準で ufopen とか、UTF8 のパスを受け付ける関数とかが、めっちゃ欲しいです。 2012/07/19 追記: ここのコードをトレースしてみたが、何をやりたいのか、ちょっとピンと来ない…。
pszIterKey:/vsigzip/
pszIterKey:/vsimem/
pszIterKey:/vsisparse/
pszIterKey:/vsistdin/
pszIterKey:/vsistdout/
pszIterKey:/vsistdout_redirect/
pszIterKey:/vsisubfile/
pszIterKey:/vsitar/
pszIterKey:/vsizip/
ソースファイル名を渡して、そいつが、これらのデバイスにマッチするかどうかを見ているようである。日本語だと、何故、こんなところで失敗するのか?もっと他の部分をあたってみるか… 同日 追記: VSIFilesystemHandler というクラスで、ファイルシステムの文字コードによる違いを吸収しているようだ。なんとも壮大で、頭がクラクラする。 iConv を使っている場合もあるようだが、 ifdef で iConv を使うか、WinAPIを使うか、切り替えてるので、バイナリ配布物の状況がよくわからない。QGis で配布されているやつは、iConv を使うコードでコンパイルされているのだろう。ところが、FOSS4GJP の情報によると Windows版 iConv のコンパイルは、いろいろと文字コードセットに対する対応が足りないらしく、正しく動作していないらしい。  Windows では、環境変数 GDAL_FILENAME_IS_UTF8 = YES が設定されていた場合には、ファイル名を UTF8 から UNICODE に変換して、UNICODE(UCS2)のファイル名を _wstat にかけている。  どうも、この _wstat に渡すパスあたりで、こけている感じだ。一生懸命、wchar_t にしているが、内部で利用されているパスは、生のMBCSなので、_wstat にかけたいがためだけのコードに留まってしまっている。複雑になった割には報われていないという印象だ。 同日 さらに追記:  stat のコードで失敗してるのかなぁと思ったが、そういう訳でもないらしい。いろんなバージョンで、試行錯誤してたので、DLL HELL に陥ってたのかも…。

GDAL windows 版は何を想定しているのか?

日本語対応の処方箋をogr2ogrに施してみたが、どうも、マシンによって挙動が異なるので、深追いしていました。そしたら、こんなコードにぶつかった。
int VSIStat( const char * pszFilename, VSIStatBuf * pStatBuf )

{
#if defined(WIN32) && !defined(WIN32CE)
    if( CSLTestBoolean(
            CPLGetConfigOption( "GDAL_FILENAME_IS_UTF8", "YES" ) ) )
    {
        int nResult;
        wchar_t *pwszFilename = 
            CPLRecodeToWChar( pszFilename, CPL_ENC_UTF8, CPL_ENC_UCS2 );

        nResult = _wstat( pwszFilename, (struct _stat *) pStatBuf );

        CPLFree( pwszFilename );

        return nResult;
    }
    else
#endif 
        return( stat( pszFilename, pStatBuf ) );
}

 これ、何を意図しているかというと、Windows 環境では、GDAL_FILENAME_IS_UTF8 = YES と環境変数に設定されている場合には、ファイル名等の文字コードを UNICODE(UCS2) wchar_t 型から、UTF8UTF8から、UNICODE(UCS2)wchar_t型 に変更してあげましょうというコードになります。  ところが、おかしいです。まず、細かい事ですが、defined(_WIN32) であって、defined(WIN32)では無い。次に、windows ce の場合は、UNICODEのアプリケーションを組む事になってたと思います。そろそろ windows ce はオワコンなんで、この部分は、もう関係無くなります。次に、そもそも現在は MBCS(Multi Byte Code Set)でコンパイルされているので、UNICODEであるはずがありません。前提条件が異なっている。仮に UNICODE でコンパイルしたとしたら、今度は、他のコードが大混乱に陥る事が容易に想像できます…。  うーん・・・ここで MBCS を UTF8 に変換してあげても、既存のIO関係のコードが MBCS 前提で動いてたりすると、もうお手上げに近いですね。CPLFopen とか、CPLFclose CPLFseek みたいな関数を用意してあげないと、ちょっと厳しいですね。UTF8をUNICODEに変換する事で、ウィンドウズの文字コード問題を乗り切るという方針で組まれているようです。