jpegファイルのexifからGPSデータの緯度、経度を抽出する

2024.05.13
2024.04.07


スマホで撮影した写真のexifから緯度、経度を抽出するプログラムを作成しようと調べた ときのメモです。

サンプル写真 2024.03.09 撮影 道路にいたカモシカ  DSC_0073.JPG

kamoshika
(ここは、カモシカ、サルをよく見かけます。ツキノワグマの目撃情報も年に数回あるところなので、
熊よ けスプレーを携帯して歩きました。なぜ道路でカモシカと遭遇するかというとガードレール側は、
深い湖、左側は高い崖でお互い逃げ場がない からです。クマでなくてよかった)

1 目的

 写真のファイルのexifという領域に撮影した状況が記録されています。exifを表示したり、変更したりするツールは既に
たくさんあるので、まったく必要ありません。c言語でバイナリファイルを操作したり、構造体を使ったプログラム作成の
練習のために調べました。


2 Exif(Exchangeable image file format)情報

以下は、Exif情報を順番に追ってGPSの緯度、経度の情報にたどり着く手順。
詳しくは https://www.cipa.jp/j/std/std-sec.html#listTop 「デジタルスチルカメラ用画像ファイルフォーマット規格 Exif3.0 」 参照

(1)hexdump

 jpegファイルの先頭から1kバイトぐらいのところまでにexif情報が記述されているようです。
 ダンプツールはhexdumpを使います。

$  hexdump -C  DSC_0073.JPG  > 0073.log
$  less  0073.log

00000000  ff d8 ff e1 21 3b 45 78  69 66 00 00 4d 4d 00 2a  |....!;Exif..MM.*|
00000010  00 00 00 08 00 0d 01 00  00 03 00 00 00 01 0c c0  |................|
00000020  00 00 01 01 00 03 00 00  00 01 09 90 00 00 01 0e  |................|
00000030  00 02 00 00 00 09 00 00  00 aa 01 0f 00 02 00 00  |................|
00000040  00 06 00 00 00 b4 01 10  00 02 00 00 00 07 00 00  |................|
00000050  00 ba 01 12 00 03 00 00  00 01 00 01 00 00 01 1a  |................|
00000060  00 05 00 00 00 01 00 00  00 c2 01 1b 00 05 00 00  |................|
00000070  00 01 00 00 00 ca 01 28  00 03 00 00 00 01 00 02  |.......(........|
00000080  00 00 01 32 00 02 00 00  00 14 00 00 00 d2 02 13  |...2............|
00000090  00 03 00 00 00 01 00 01  00 00 87 69 00 04 00 00  |...........i....|
000000a0  00 01 00 00 00 e6 88 25  00 04 00 00 00 01 00 00  |.......%........|
000000b0  03 3a 00 00 04 3c 44 53  43 5f 30 30 37 33 00 00  |.:...<DSC_0073..|
000000c0  53 48 41 52 50 00 53 48  2d 4d 31 39 00 00 00 00  |SHARP.SH-M19....|
000000d0  00 48 00 00 00 01 00 00  00 48 00 00 00 01 32 30  |.H.......H....20|
000000e0  32 34 3a 30 33 3a 30 39  20 31 30 3a 35 31 3a 34  |24:03:09 10:51:4|
000000f0  37 00 00 22 82 9a 00 05  00 00 00 01 00 00 02 84  |7.."............|
これ以降の作業
 Tiffヘッダーから0th IFDの先頭アドレスを調べる
  0th IFDからエントリ数を調べ、エントリのタグからGPSのタグを見つける
 GPSタグのエントリ情報を調べてGPS情報の先頭アドレスを算出する
 GPS情報の先頭アドレスのデータからGPS情報の数を調べ、緯度のタグ、経度のタグ
を調べてそれぞれ算出する


(2) Tiffヘッダーの確認

 スマホで撮影したjpeg形式の写真の先頭の方の領域にtiffヘッダーというのがある。(Tiff:Tag Image File Format)
 GPSのデータはこのTiffヘッダーを調べることから始まる。
  

 1)まず先頭2バイトがff d8で始まることを確認。
 2)そのあとExifという文字列を探す。必ずしもサンプルと同じ位置にあるとはかぎらない。。  
  3)Exifのあと2バイト後ろに、コードでいうと4d 4d ('MM')または49 49 ('II')という文字列があるところが
 tiffヘッダーの始まり。

00000000  ff d8 ff e1 21 3b 45 78  69 66 00 00 4d 4d 00 2a  |....!;Exif..MM.*|
00000010  00 00 00 08 00 0d 01 00  00 03 00 00 00 01 0c c0  |................|

表1 Tiffヘッダーの構造
サイズ(byte)
名称 概要
2
エンディアン
'MM'はビッグエン ディアン
'II'はリトルエンディアン
2
マジックNO
ビッグエンディアンのと き下位から順に00 2aが入る
リトルエンディアンのとき下位から順に2a 00が入る
4
0th IFDへのオフセット
0番目の IFD(Image File Directory)のオフセットアドレス

リトルエンディアンは、複数バイトのデータを下位から順に記録していく方式
2バイトの数値 (ABCD)Hは、下位から順に CD ABと 記録される。

ビッグエンディアンは、複数バイトのデータを上位から順に記録していく方式
2バイトの数値 (ABCD)Hは、下位から順に AB CDと 記録される。

Tiffヘッダーの先頭アドレスは上の例だと4dが入っている0CH 番地である。
これ以降、オフセットアドレスに、Tiffヘッダの先頭アドレスである0cH を足し こんだアドレスが、ターゲットの位置となる。


(3)0th IFD先頭アドレスの産出

この例だと0thIFDのオフセットアドレスは、00 00 00 08なので,08H+0cH=14Hからが0th IFDのアドレスとなる。
つまりtiff ヘッダの直後から0thIFDとなる。

(4)IFDの構造

 
00000000  ff d8 ff e1 21 3b 45 78  69 66 00 00 4d 4d 00 2a  |....!;Exif..MM.*|
00000010  00 00 00 08 00 0d 01 00  00 03 00 00 00 01 0c c0  |................|

 上の例では最初の2バイト00 0dがエントリ数、つまり13個のエントリがあることを示している。
そのあと下表のように12バイトづつエントリ内容が記述される。

表2 ifdの構造
サイズ(byte) 概 要
2
エントリ数
12
エントリ1
12
エントリ2
 :
     :
12
エントリn
4
NEXT IFD

その各12バイトのエントリの構造は下表のようになる。
表3 エントリの構造

サイズ (byte)
概 要
2
タグ フィールドを識別するための固有の番号
2
タイプ
4
カウント 
値の個数 バイト数ではない
4
値 ただし、値が4バイトを超えるときはtiff ヘッダからのオフセットアドレス

表4 タイプ一覧
タイプ(2byte)
概要
00 01 BYTE   8ビット符号無し整数
00 02 ASCII文字コード
00 03
SHORT    16ビット(2バイト)符号無し整数
00 04 LONG     32ビット(4 バイト)符号無し整数
00 05 RATIONAL  LONG2個。最初のLONG は分子、2個目のLONGは分母を表す。カウントは分子と分母ペアで1
00 07 UNDEFINED フィールドの定義により、どんな値をとってもよい 8 ビットバイト
00 09 SLONG     32ビット(4 バイト)符号付き整数(2の補数表現)
00 0a SRATIONAL カウント(Count) SLONG2 個。最初の SLONG は分子、2 個目の SLONG は分母を表す


(5)GPSタグのあるエントリを探す

00000000  ff d8 ff e1 21 3b 45 78  69 66 00 00 4d 4d 00 2a  |....!;Exif..MM.*|
00000010  00 00 00 08 00 0d 01 00  00 03 00 00 00 01 0c c0  |................|
00000020  00 00 01 01 00 03 00 00  00 01 09 90 00 00 01 0e  |................|
00000030  00 02 00 00 00 09 00 00  00 aa 01 0f 00 02 00 00  |................|
00000040  00 06 00 00 00 b4 01 10  00 02 00 00 00 07 00 00  |................|
00000050  00 ba 01 12 00 03 00 00  00 01 00 01 00 00 01 1a  |................|
00000060  00 05 00 00 00 01 00 00  00 c2 01 1b 00 05 00 00  |................|
00000070  00 01 00 00 00 ca 01 28  00 03 00 00 00 01 00 02  |.......(........|
00000080  00 00 01 32 00 02 00 00  00 14 00 00 00 d2 02 13  |...2............|
00000090  00 03 00 00 00 01 00 01  00 00 87 69 00 04 00 00  |...........i....|
000000a0  00 01 00 00 00 e6 88 25  00 04 00 00 00 01 00 00  |.......%........|
000000b0  03 3a 00 00 04 3c 44 53  43 5f 30 30 37 33 00 00  |.:...<DSC_0073..|
000000c0  53 48 41 52 50 00 53 48  2d 4d 31 39 00 00 00 00  |SHARP.SH-M19....|
000000d0  00 48 00 00 00 01 00 00  00 48 00 00 00 01 32 30  |.H.......H....20|
000000e0  32 34 3a 30 33 3a 30 39  20 31 30 3a 35 31 3a 34  |24:03:09 10:51:4|
000000f0  37 00 00 22 82 9a 00 05  00 00 00 01 00 00 02 84  |7.."............|
 
14H番地の エントリ数 00 0d のあと各エントリのタグが12byte毎に13回出現する。
GPSのタグは88 25  なので、これを探すと最後の13番目のエントリa6H番地にあることがわかる。
表3のエントリの構造に従ってGPSタグを読み解く。
88 25        GPSのタグ
00 04        タイプ  32bit符号無し整数(表4参照)
00 00 00 01  カウント
00 00 03 3a  オフセットアドレス

 オフセットアドレスにtiff headerの先頭アドレス0chを足した0346Hが実アドレスになる。
 03 3a + 0c = (0346)H


(6)GPS IFDからデータを取得

00000340  30 30 00 00 00 00 00 0d  00 00 00 01 00 00 00 04  |00..............|
00000350  02 02 00 00 00 01 00 02  00 00 00 02 4e 00 00 00  |............N...|
00000360  00 02 00 05 00 00 00 03  00 00 03 dc 00 03 00 02  |................|
00000370  00 00 00 02 45 00 00 00  00 04 00 05 00 00 00 03  |....E...........|
00000380  00 00 03 f4 00 05 00 01  00 00 00 01 00 00 00 00  |................|
00000390  00 06 00 05 00 00 00 01  00 00 04 0c 00 0c 00 02  |................|
000003a0  00 00 00 02 4b 00 00 00  00 0d 00 05 00 00 00 01  |....K...........|
000003b0  00 00 04 14 00 0e 00 02  00 00 00 02 54 00 00 00  |............T...|
000003c0  00 0f 00 05 00 00 00 01  00 00 04 1c 00 12 00 02  |................|
000003d0  00 00 00 07 00 00 04 24  00 1b 00 07 00 00 00 0f  |.......$........|
000003e0  00 00 04 2c 00 00 00 00  00 00 00 24 00 00 00 01  |...,.......$....|
000003f0  00 00 00 29 00 00 00 01  00 00 00 35 00 00 00 01  |...).......5....|
00000400  00 00 00 8a 00 00 00 01  00 00 00 2f 00 00 00 01  |.........../....|
00000410  00 00 00 04 00 00 00 01  00 00 03 34 00 00 00 01  |...........4....|
00000420  00 00 00 00 00 00 00 01  00 00 00 a7 00 00 00 01  |................|
00000430  57 47 53 2d 38 34 00 00  41 53 43 49 49 00 00 00  |WGS-84..ASCII...|
00000440  47 50 53 2d 46 49 58 00  00 09 01 03 00 03 00 00  |GPS-FIX.........|
00000450  00 01 00 06 00 00 01 12  00 03 00 00 00 01 00 01  |................|
00000460  00 00 01 1a 00 05 00 00  00 01 00 00 04 ae 01 1b  |................|
 
GPS IFDの構造も表3に従って解析していく
なおGPSのタグは デジタルスチルカメラ用画像ファイルフォーマット規格 Exif 2.3
GPSに関する付属情報 を参照した
以下、前述の計算したアドレスより順に

0346番地〜
00 0d  エントリ数 GPSに関する情報、バージョン、緯度、経度、標高などが13個のエントリーがある

0348番地〜
00 00       GPSバージョンのタグ
00 01        タイプ   BYTE 8ビット符号なし整数
00 00 00 04 カウント   4個ある つまりタイプbyteなので4byte ということで続く4byteはデータ
02 02 00 00  値     バージョン 2.2.0.0

0354番地〜
00 01        GPSの北緯または南緯のタグ
00 02        タイプ   ASCII 文字コード
00 00 00 02  カウント   
4e 00 00 00  値     4e='N'  北緯ということ 
 なぜ、カウンタが02なのか、たぶんヌル文字も含めて 'N\0' 1個分、後半はヌルヌルで1個 合計2
 ということか?

360番地〜
00 02        GPSの緯度のタグ
00 05    タイプ   RATIONAL 分数 最初の4byteが分子、次の4byteが分母
00 00 00 03  カウント    3個  度、分、秒の3個が分数で記述されている
00 00 03 dc オフセットアドレス   03dc+0c=03e8に ある

036c番地〜
00 03        GPSの東経または西経のタグ 
00 02        タイプ   ASCII 文字コード
00 00 00 02 カウント
45 00 00 00  45='E'  東経ということ

0378番地〜
00 04        GPSの経度のタグ
00 05    タイプ   RATIONAL 分数 最初の4byteが分子、次の4byteが分母
00 00 00 03  カウント    3個  度、分、秒の3個が分数で記述されている
00 00 03 f4 オフセットアドレス   03f4+0c=0400にある

以下略

(7)緯度、経度の抽出

000003e0  00 00 04 2c 00 00 00 00  00 00 00 24 00 00 00 01  |...,.......$....|
000003f0  00 00 00 29 00 00 00 01  00 00 00 35 00 00 00 01  |...).......5....|
00000400  00 00 00 8a 00 00 00 01  00 00 00 2f 00 00 00 01  |.........../....|
00000410  00 00 00 04 00 00 00 01  00 00 03 34 00 00 00 01  |...........4....|
00000420  00 00 00 00 00 00 00 01  00 00 00 a7 00 00 00 01  |................|
00000430  57 47 53 2d 38 34 00 00  41 53 43 49 49 00 00 00  |WGS-84..ASCII...|
00000440  47 50 53 2d 46 49 58 00  00 09 01 03 00 03 00 00  |GPS-FIX.........|
 
緯度は
度の分子(00 00 00 24)H,   度の分母は(00 00 00 01)H
24H / 01H = 24H = 36°

分の分子(00 00 00 29)H,   分の分母は(00 00 00 01)H
29H / 01H = 29H = 41'

秒の分子(00 00 00 35)H,   秒の分子は(00 00 00 01)H
35H / 01H =35H = 53"

よって 36°41'53" N  が判明


経度は
度の分子(00 00 00 8a)H,   度の分母は(00 00 00 01)H
8aH / 01H = 24H = 138°

分の分子(00 00 00 2f)H,   分の分母は(00 00 00 01)H
2fH / 01H = 2fH = 47'

秒の分子(00 00 00 04)H,   秒の分子は(00 00 00 01)H
04H / 01H =04H = 4"

よって 138°47'04" E  が判明


※cの構造体を使えば、比較的簡単にタグやデータを抜き出すことができると思うが、
ビッグエンディアン、リトルエンディアンをしっかり確認してデータの扱い方を変え
るプログラムを作成しなければならない



3 プログラム
下のプログラムは前節のExif情報からGPSの緯度、経度情報の数値にたどり着くまでの手順のプログラム。

exif_read.c
/*********************************************
 * jpegファイルからGPSデータの緯度、経度を抽出する
 *        2024.05.13
 * ******************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
fpos_t Getfilesize(FILE *); //ファイルサイズを調べる
int Jpgchk(FILE *); //jpegファイルかどうかを確認
int Thdchk(FILE*); //Tiff header chk
unsigned int bxl4(unsigned int);
            //4byte unsigned int型ビッグエンディアンを
        //リトルエンディアンにする
unsigned short int bxl2(unsigned short int);
           //2byte unsigned shortint型ビッグエンディアンを
       //リトルエンディアンにする

//tiff header 構造体
struct tifhead{
    unsigned short int endian; //2byte endian
    unsigned short int magic;  //2byte magic number
    unsigned int   zerothifd; //4byte 0th IFD addr
};

//IFD 12byte 構造体
typedef struct{    
    unsigned short entry_tag;
    unsigned short entry_type;
    unsigned int   entry_cnt;
    unsigned int   entry_value;
}Ifd;

//緯度経度構造体
typedef struct{
    unsigned int deg; //度
    unsigned int d_deno; //度の分母
    unsigned int min; //分
    unsigned int m_deno; //分の分母
    unsigned int sec; //秒
    unsigned int s_deno; //秒の分母
}Gpoint;

/**************************
 *   main
 *************************/
int main(int argc,char *argv[]){
    FILE *fp;
    fpos_t fsize;
    unsigned int tifa; //tiff header 先頭アドレス
    unsigned int zthadr; //0thIFDaddr
    unsigned int gpsifd; //gps ifd addr
    unsigned int subadr; //gps ifd addr
    unsigned int  wrk,i,j;
    unsigned short int  wrk2; //2byte wrk
    unsigned short int entry;
    unsigned char buff[2024];
    unsigned char charw;
    float degree,minute,second,deno; //度,分,秒,分母
    unsigned char endian_f; //endian flag 'B' or 'L'
   
    if(argc<2){
        printf(" exif_r  ファイル名 \n");
        exit(1);
    }
        if((fp=fopen(argv[1],"rb"))==NULL){
        printf("%sをオープンできません。\n",argv[1]);
        exit(1);
       }
    //ファイルサイズの取得と表示
    fsize=Getfilesize(fp);
    printf("ファイルサイズは %dです。\n",fsize);
    if(Jpgchk(fp)==0){
        printf("Jpegファイルではありません\n");
        exit(1);
    }
    else{
        printf("Jpegファイルです\n");
    }
    tifa=Thdchk(fp);
    if(tifa==0){
        printf("Exifが見つかりません\n");
        exit(1);
    }

    printf("tiffヘッダ先頭アドレス %02x\n",tifa);
   
    fseek(fp,tifa,SEEK_SET);
    fread(buff,1,8,fp); //tiffヘッダ8byte取得

    printf("TIFFヘッダ ");
    for(i=0;i<8;i++){
        printf(" %02x ",buff[i]);
    }
    printf("\n");

    struct tifhead *thd;
    thd=(struct tifhead *)buff; //tiffヘッダ8byteを構造体thdに渡す

    //endian flagセット
    if(thd->endian == 0x4d4d){
        endian_f='B'; //big endian
    }else if(thd->endian ==0x4949){
        endian_f='L'; //little endian
    }
    //0th IFD先頭アドレス
    zthadr= thd->zerothifd;
        if(endian_f == 'B') //ビッグエンディアン
        zthadr = bxl4(zthadr);
    printf("0th IFD先頭アドレス %08x\n",zthadr);
    zthadr+=tifa;
    printf("tiffヘッダ先頭アドレスを加算した0th IFD先頭アドレス %08x\n",zthadr);
   
    fseek(fp,zthadr,SEEK_SET);
     fread(&entry,2,1,fp); //0thifdのエントリー数2バイト取得
    if(endian_f =='B')
        entry=bxl2(entry); //

    printf("0th ifd の先頭2byte(エントリー数) %04x H \n",entry);

    wrk=fread(buff,1,entry*12 ,fp); //エントリー数✕12byte読み込む
    printf("0th ifdエントリー数✕12バイト読み込む\n");
    for(i=0;i<entry;i++){
        for(j=0;j<12;j++){
            printf(" %02x ",buff[i*12+j]);       
        }
        printf("\n");
    }

    wrk2=0;
    Ifd  *ifd_entry=(Ifd *)buff;
        for(i=0;i<entry;i++,ifd_entry++){
        wrk2=ifd_entry->entry_tag;
            if(endian_f =='B'){//ビッグエンディアン
            wrk2=bxl2(wrk2);//
        }
        if(wrk2 == 0x8825)
            break;
    }
    if(wrk2 != 0x8825){
        printf("GPS情報が見つかりません\n");
        exit(1);
    }
   
    gpsifd=ifd_entry->entry_value; //GPS情報があるオフセットアドレス
    if(endian_f=='B')//ビッグエンディアン
        gpsifd=bxl4(gpsifd);
        printf("GPS情報があるアドレス gpsifd= %08x \n",gpsifd);
    gpsifd+=tifa; //オフセットアドレスを加算
        printf("GPS情報があるアドレス+tiffヘッダ先頭アドレス gpsifd= %08x \n",gpsifd);

    fseek(fp,gpsifd,SEEK_SET); //fpをgps IFDの先頭に移動
     fread(&entry,2,1,fp); //gps ifdのエントリー数2バイト取得
    if(endian_f =='B')
        entry=bxl2(entry); //
   
    printf("GPS ifd の先頭2byte(エントリー数) %04x H \n",entry);

    wrk=fread(buff,1,entry*12 ,fp); //エントリー数✕12byte読み込む

    printf("GPS ifdエントリー数✕12バイト読み込む\n");
    for(i=0;i<entry;i++){
        for(j=0;j<12;j++){
            printf(" %02x ",buff[i*12+j]);       
        }
        printf("\n");
    }

    Ifd *gps_entry=(Ifd *)buff;
    if(endian_f=='B'){
        while(gps_entry->entry_tag!=0x0100){
            gps_entry++; //北緯または南緯のタグまでポインタ移動
        }
        charw=(unsigned char)gps_entry->entry_value;
        //printf("%c  ",charw); //NまたはSの表示

        while(gps_entry->entry_tag!=0x0200){
            gps_entry++; //緯度のタグまでポインタ移動
        }
        subadr=bxl4(gps_entry->entry_value)+tifa; //緯度データのあるアドレス
        printf(" 緯度データのあるアドレス %08x\n",subadr);
        fseek(fp,subadr,SEEK_SET); //緯度データのあるアドレスに移動
        fread(buff,1,24,fp);//度分秒のデータを読み込む
       
        printf("緯度データ\n");
        for(i=0;i<24;i++)
            printf(" %02x ",buff[i]);
        printf("\n");

        Gpoint *latitude=(Gpoint *)buff;
        degree=(float)bxl4(latitude->deg)/(float)bxl4(latitude->d_deno);
        minute=(float)bxl4(latitude->min)/(float)bxl4(latitude->m_deno);
        second=(float)bxl4(latitude->sec)/(float)bxl4(latitude->s_deno);
        printf("%1.0f°%1.0f'%1.2f''",degree,minute,second);
        printf("%c \n",charw); //NまたはSの表示

        while(gps_entry->entry_tag!=0x0300){
            gps_entry++; //東経または西経のタグのポインタまで移動
        }
        charw=(unsigned char)gps_entry->entry_value;

        while(gps_entry->entry_tag!=0x0400){
            gps_entry++; //経度のタグまでポインタ移動
        }
        subadr=bxl4(gps_entry->entry_value)+tifa; //経度データのあるアドレス
        printf(" 経度データのあるアドレス %08x\n",subadr);
        fseek(fp,subadr,SEEK_SET); //経度データのあるアドレスに移動
        fread(buff,1,24,fp);//度分秒のデータを読み込む
       
        printf("経度データ\n");
        for(i=0;i<24;i++)
            printf(" %02x ",buff[i]);
        printf("\n");
   
        Gpoint *longitude=(Gpoint *)buff;
        degree=(float)bxl4(longitude->deg)/(float)bxl4(longitude->d_deno);       
        minute=(float)bxl4(longitude->min)/(float)bxl4(longitude->m_deno);
        second=(float)bxl4(longitude->sec)/(float)bxl4(longitude->s_deno);
        printf("%1.0f°%1.0f'%1.2f''",degree,minute,second);
        printf("%c \n",charw); //EまたはWの表示

    }else{  //リトルエンディアンの場合
        while(gps_entry->entry_tag!=0x0001){
            gps_entry++; //北緯または南緯のタグまでポインタ移動
        }
        charw=(unsigned char)gps_entry->entry_value;

        while(gps_entry->entry_tag!=0x0002){
            gps_entry++; //緯度のタグまでポインタ移動
        }
        subadr=gps_entry->entry_value+tifa; //緯度データのあるアドレス
        printf(" 緯度データのあるアドレス %08x\n",subadr);

        fseek(fp,subadr,SEEK_SET); //緯度データのあるアドレスに移動
        fread(buff,1,24,fp);//度分秒のデータを読み込む
       
        printf("緯度データ\n");
        for(i=0;i<24;i++)
            printf(" %02x ",buff[i]);
        printf("\n");
       
        Gpoint *latitude=(Gpoint *)buff;
        degree=(float)latitude->deg/(float)latitude->d_deno;
        minute=(float)latitude->min/(float)latitude->m_deno;
        second=(float)latitude->sec/(float)latitude->s_deno;
        printf("%1.0f°%1.0f'%1.2f''",degree,minute,second);
        printf("%c \n",charw);//NまたはSの表示

        while(gps_entry->entry_tag!=0x0003){
            gps_entry++; //東経または西経のタグのポインタまで移動
        }
        charw=(unsigned char)gps_entry->entry_value;
       
        while(gps_entry->entry_tag!=0x0004){
            gps_entry++; //経度のタグまでポインタ移動
        }
        subadr=gps_entry->entry_value+tifa; //経度データのあるアドレス
        printf(" 経度データのあるアドレス %08x\n",subadr);
       
        fseek(fp,subadr,SEEK_SET); //経度データのあるアドレスに移動
        fread(buff,1,24,fp);//度分秒のデータを読み込む
       
        printf("経度データ\n");
        for(i=0;i<24;i++)
            printf(" %02x ",buff[i]);
        printf("\n");
       
        Gpoint *longitude=(Gpoint *)buff;
        degree=(float)longitude->deg/(float)longitude->d_deno;
        minute=(float)longitude->min/(float)longitude->m_deno;
        second=(float)longitude->sec/(float)longitude->s_deno;
        printf("%1.0f°%1.0f'%1.2f''",degree,minute,second);
        printf("%c \n",charw); //EまたはWの表示
    }
    return 0;
}

/************************
 * 2byte(unsigned short int型)
 *  bigendian -> little
 ***********************/
unsigned short int bxl2(unsigned short int addr){
    unsigned short int xaddr;
    xaddr =(addr>>8) & 0x00ff;
    xaddr |=(addr<<8)  & 0xff00;
    return xaddr;
}
/************************
 * 4byte(unsigned int型)
 *  bigendian -> little
 ***********************/
unsigned int bxl4(unsigned int addr){
    unsigned int xaddr;
    xaddr=(addr>>24)  & 0x000000ff;
    xaddr |=(addr>>8)  & 0x0000ff00;
    xaddr |=(addr<<8)  & 0x00ff0000;
    xaddr |=(addr<<24) & 0xff000000;
    return xaddr;
}
/***********************
 * ファイルサイズを取得
 * *********************/
fpos_t Getfilesize(FILE *fp){
    fpos_t fsize;
    fseek(fp, 0, SEEK_END); //ファイルを最終ポイントに移動
    fgetpos(fp, &fsize); //ファイルサイズを取得
    return fsize;
}
/*******************************************
 * jpegファイルの確認 (先頭がff d8ならjpeg )
 *  戻り値res=1:jpeg   0:jpeg以外
 *******************************************/
int Jpgchk(FILE *fp){
    unsigned char c,d;
    int res;
    fseek(fp,0,SEEK_SET);//ファイルポインタを先頭にセット
    res=0;
    c=fgetc(fp);
    d=fgetc(fp);
    if ((c == 0xff)&& (d==0xd8))
        res=1; //jpegファイルです
    return res;
}
/********************************************
 * Tiff Header check
 *   1. 'Exif'という文字列を見つける
 *   2. 'Exif'の文字列の後ろ3バイト目
 *    が'MM'または'II'になっていることを確認
 *   3. オフセットアドレスを返す
 ********************************************/
int Thdchk(FILE *fp){
    unsigned char c;
    int res;
    fseek(fp,0,SEEK_SET);//ファイルポインタを先頭にセット
    res=0;
    while((c=fgetc(fp))!=EOF){
        if(c=='E'){
            if((c=fgetc(fp))!=EOF){
                if(c=='x'){
                    if((c=fgetc(fp))!=EOF){
                        if(c=='i'){
                            if((c=fgetc(fp))!=EOF){
                                if(c=='f'){
                                    res+=6;
                                    //printf("Exif offset addr=%x\n",res);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        res++;
        if(res>100000){
                        res=0;
            break;
        }
    }
    return res;
}

コンパイル
$  gcc  exif_read.c  -o  exif_read

実行例
 $./exif_read DSC_0073.JPG
ファイルサイズは 2243649です。
Jpegファイルです
tiffヘッダ先頭アドレス 0c
TIFFヘッダ  4d  4d  00  2a  00  00  00  08
0th IFD先頭アドレス 00000008
tiffヘッダ先頭アドレスを加算した0th IFD先頭アドレス 00000014
0th ifd の先頭2byte(エントリー数) 000d H
0th ifdエントリー数✕12バイト読み込む
 01  00  00  03  00  00  00  01  0c  c0  00  00
 01  01  00  03  00  00  00  01  09  90  00  00
 01  0e  00  02  00  00  00  09  00  00  00  aa
 01  0f  00  02  00  00  00  06  00  00  00  b4
 01  10  00  02  00  00  00  07  00  00  00  ba
 01  12  00  03  00  00  00  01  00  01  00  00
 01  1a  00  05  00  00  00  01  00  00  00  c2
 01  1b  00  05  00  00  00  01  00  00  00  ca
 01  28  00  03  00  00  00  01  00  02  00  00
 01  32  00  02  00  00  00  14  00  00  00  d2
 02  13  00  03  00  00  00  01  00  01  00  00
 87  69  00  04  00  00  00  01  00  00  00  e6
 88  25  00  04  00  00  00  01  00  00  03  3a
GPS情報があるアドレス gpsifd= 0000033a
GPS情報があるアドレス+tiffヘッダ先頭アドレス gpsifd= 00000346
GPS ifd の先頭2byte(エントリー数) 000d H
GPS ifdエントリー数✕12バイト読み込む
 00  00  00  01  00  00  00  04  02  02  00  00
 00  01  00  02  00  00  00  02  4e  00  00  00
 00  02  00  05  00  00  00  03  00  00  03  dc
 00  03  00  02  00  00  00  02  45  00  00  00
 00  04  00  05  00  00  00  03  00  00  03  f4
 00  05  00  01  00  00  00  01  00  00  00  00
 00  06  00  05  00  00  00  01  00  00  04  0c
 00  0c  00  02  00  00  00  02  4b  00  00  00
 00  0d  00  05  00  00  00  01  00  00  04  14
 00  0e  00  02  00  00  00  02  54  00  00  00
 00  0f  00  05  00  00  00  01  00  00  04  1c
 00  12  00  02  00  00  00  07  00  00  04  24
 00  1b  00  07  00  00  00  0f  00  00  04  2c
 緯度データのあるアドレス 000003e8
緯度データ
 00  00  00  24  00  00  00  01  00  00  00  29  00  00  00  01  00  00  00  35  00  00  00  01
36°41'53.00''N
 経度データのあるアドレス 00000400
経度データ
 00  00  00  8a  00  00  00  01  00  00  00  2f  00  00  00  01  00  00  00  04  00  00  00  01
138°47'4.00''E
$