トップ地図アプリMap > 撮った写真を見る[Windows版]

撮った写真を見る[Windows版]

1.はじめに

撮った写真を地図システムから見るのは簡単ではない。 前システムでも少し不具合を抱えたまま使っていたので、これも解決したい。

jpegファイルには写真を撮影した位置の極座標値などが埋め込まれている。 前システムではこの位置情報を取り出していたが、時間がかかるため、予め管理ファイルを作成していた[1]。 新システムはこれらの管理ファイルを最小限に抑えることを考えているため、 写真については、jpegからの位置情報抽出は行なわず、特別な管理ファイルは作成しない。

撮影日時はファイル名から分かる。GPS履歴から、撮影日時にどこにいたかが分かる。 jpegファイルに埋め込まれている座標値はGPSで得たものであるから精度的には同じである。

2.Windows版C#プログラム

前処理

写真表示モードに変えたとき、 時刻をキー、jpegファイルのパス名を値とした Dictionary を作成する。

個々のファイルにアクセスするのではなく、ディレクトリにアクセスするだけのため、 実行時間は短く、メモリ使用量も少ない。写真表示モードをオフにしたとき解放する。 オフからオンに切り替えたとき、その時点までに撮影した写真が対象になる。

    public static Dictionary<int,string> dictPhoto;

    public static void Load() {
        dictPhoto = new Dictionary<int,string>();
        Load("d:/gisdata_tb/roll2015");
        Load("d:/gisdata_tb/roll2020");
    }

    public static void Load(string dir) {
        string[] files = Directory.GetFiles(dir, "*.jpg");
        foreach (var path in files) {
            int date, year, month, day, hour, minute, second;
            int ix = path.IndexOf("WIN");
            if (ix < 0) continue;
            var v = path.Substring(ix).Split(new char[]{'_', '.', ' '});
            if (v.Length == 4) {            //WIN_20150330_125146.JPG
                date = int.Parse(v[1]);
                var hms = int.Parse(v[2]);
                hour = hms / 10000;
                minute = (hms - hour*10000) / 100;
                second = hms % 100;
            } else if (v.Length == 7) {     //WIN_20161119_13_34_11_Pro.jpg
                date = int.Parse(v[1]);     //WIN_20200402_10_31_46_Scan.jpg
                hour = int.Parse(v[2]);
                minute = int.Parse(v[3]);
                second = int.Parse(v[4]);
            } else {
                continue;
            }
            year = date / 10000;
            month = (date - year*10000) / 100;
            day = date % 100;
            var elapsed = new DateTime(year,month,day,hour,minute,second) - 
                          new DateTime(2015,1,1);
            dictPhoto[(int)elapsed.TotalSeconds] = path;
        }
    }

画面内で撮られた写真を抽出する

dictPhotoのキーは秒単位であるため、 GPS軌跡を秒単位でスキャンしてその時刻に写真をとっていないか調べる。 GPS軌跡の表示プログラムを参考にして作成した。I/O処理はないため、実行は瞬時である。 撮影位置に「+」マークを表示する。

    void ViewPhoto(Graphics g) {
        if (GPSLog.logs == null) return;
        Stopwatch sw = Stopwatch.StartNew();
        Font fontB8 = new Font("Meiryo UI", 8, FontStyle.Bold);

        listPhotoInfo = new List<PhotoInfo>(); // 写真の撮影位置情報

        int lon1 = (int)(Zone.X2Lon((CX-W/2)/256.0, zoom)*1000000);
        int lat1 = (int)(Zone.Y2Lat((CY+H/2)/256.0, zoom)*1000000);
        int lon2 = (int)(Zone.X2Lon((CX+W/2)/256.0, zoom)*1000000);
        int lat2 = (int)(Zone.Y2Lat((CY-H/2)/256.0, zoom)*1000000);
        for (int n = 0; n < GPSLog.logs.Length; n++) {
            var log = GPSLog.logs[n];
            if (log.maxlon < lon1 || log.minlon > lon2) continue;
            if (log.maxlat < lat1 || log.minlat > lat2) continue;
            int ilon = log.ilon;
            int ilat = log.ilat;
            int itime = log.itime;
            DateTime dt = GPSLog.FromUnixTime(itime);
            int secs = (int)((dt - new DateTime(2015,1,1)).TotalSeconds);
            for (int ix = 0; ix < log.diffs.Length; ) {
                int dlon = (int)Lib.ParseSInt64(log.diffs, ref ix);
                int dlat = (int)Lib.ParseSInt64(log.diffs, ref ix);
                int dalt = (int)Lib.ParseSInt64(log.diffs, ref ix);
                int dtime = (int)Lib.ParseSInt64(log.diffs, ref ix);
                for (int time = secs; time < secs + dtime; time++) {
                    if (PhotoInfo.dictPhoto.ContainsKey(time)) {
                        // この時刻に撮られた写真がある
                        int x = (int)(Zone.Lon2X(ilon/1000000.0, zoom)*256 - (CX - W/2));
                        int y = (int)(Zone.Lat2Y(ilat/1000000.0, zoom)*256 - (CY - H/2));
                        g.DrawString("+", fontB8, Brushes.Red, x-4, y-4);
                        var path = PhotoInfo.dictPhoto[time];
                        listPhotoInfo.Add(new PhotoInfo(x,y,path));
                    } 
                }
                ilon += dlon;
                ilat += dlat;
                secs += dtime;
            }
        }
        sw.Stop();
        //Monitor.WriteLine("Map.GetPhotoPosition: " + sw.Elapsed.ToString());
    }

マーカーをクリックするとその写真を表示する

ここは前システムのPhotoViewerを使う。

バグフィックス

起動後最初の表示モードではマーカーが表示されない

少しスクロールすると表示される。

起動後すぐに写真表示モードに変えた場合、GPS履歴データの読み込みが終わっていないのが原因のようだ。 ノートパソコンの場合、5秒ほどで終わるので、その後のモード切替ならば問題がないようだ。

PhotoViewerで次の写真表示でエラーとなる

idの付与方法をシンプルにした。

PhotoViewerが一度しか使えない

これは前システムであったエラーである。 数年前に作ったプログラムであり、waitPhotoViewer が何のための変数か分からない。 コメントアウトすると PhotoViewer は何度でも起動できる。同時に複数起動もできる。

    void mouseDown() {
        if (GIS.Flag["view_photo"] && listPhotoInfo != null) {
            PhotoInfo piNearest = null;
            int mindist = int.MaxValue;
            foreach (PhotoInfo pi in listPhotoInfo) {
                int dist = Math.Abs(pi.x-MarkX) + Math.Abs(pi.y-MarkY);
                if (dist < mindist) {
                    piNearest = pi;
                    mindist = dist;
                }
            }
            if (mindist < 50 /*&& waitPhotoViewer == 0*/) {
                PhotoViewer pv = new PhotoViewer(piNearest, this);
                pv.Owner = gis; // PhotoViewerを常に前面に表示する
                pv.Show();
                waitPhotoViewer = 2; 
            }   // 写真撮影位置に近い所をクリック/タップした
        }
    }

ここはコメントアウトせず、PhotoViewerをクローズするとき、waitPhotoViewer を 0 にすれば、 何度でも、同時にはひとつだけ起動できる。

        FormClosing += (s,e) => { 
            timer.Dispose(); 
            map.waitPhotoViewer = 0;
        };

当面のプログラム[]は

A.リファレンス

[1]