撮った写真を地図システムから見るのは簡単ではない。 前システムでも少し不具合を抱えたまま使っていたので、これも解決したい。
jpegファイルには写真を撮影した位置の極座標値などが埋め込まれている。 前システムではこの位置情報を取り出していたが、時間がかかるため、予め管理ファイルを作成していた[1]。 新システムはこれらの管理ファイルを最小限に抑えることを考えているため、 写真については、jpegからの位置情報抽出は行なわず、特別な管理ファイルは作成しない。
撮影日時はファイル名から分かる。GPS履歴から、撮影日時にどこにいたかが分かる。 jpegファイルに埋め込まれている座標値はGPSで得たものであるから精度的には同じである。
写真表示モードに変えたとき、 時刻をキー、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秒ほどで終わるので、その後のモード切替ならば問題がないようだ。
idの付与方法をシンプルにした。
これは前システムであったエラーである。 数年前に作ったプログラムであり、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;
};
当面のプログラム[]は