(openlayers)緯度経度で一意に場所を特定出来ない世界 または私は如何に心配するのを止めて左回りするようになったか

これで悩む人多いんじゃないかと思うんだけど、決定打となる情報は見当たらない。

あ、ちなみに私は心配するのをやめて OpenLayers 3 に乗り換えたので 3 系の情報です。

これなんだけどね、「ハングアップするぜ」問題もそうなんだけど、なんかヘンで、初期表示で「center」を日本にして「太平洋を超えて」アメリカを表示しようとすると出ない、逆に初期表示で「center」をアメリカにして同じく「太平洋を超えて」日本を表示しようとすると出ない。

なんだろうと探ってたら、「wrap longitude」にまつわる問題であることが判明。つまり上のうまくいかない「太平洋を超えて」を、マップ上で逆周りすると表示される。というよりは「WEB メルカトル上では日本は何箇所もある」。いいねぇ、国土が何倍にもなったぜ、て?

こんだけ国土が広くなれば住宅事情も改善だな

一つには EPSG:3857 (a.k.a WEB メルカトル) は「巡り巡る」ので、まずは EPSG:4326 (a.k.a WGS84) に直す際には「巡り巡った分を元に戻す」必要がある:

1     function transform_3857_4326_version0(pos) {
2       var res = ol.proj.transform(pos, 'EPSG:3857', 'EPSG:4326');
3       var worlds = Math.floor((res[0] + 180) / 360);
4       res[0] = res[0] - (worlds * 360);
5       return res;
6     }

ここまでの情報はちらほら見つかる。が、この lon, lat が「何箇所もある」(ので表示されないようにみえる)問題に対する解答はどれも、「current limitation」だとか、discussion レベルで止まってる。これが解消するまでには 1年以上は待たないとダメだろうなぁきっと。

そもそもアタシの処理は、現在表示している boundingbox から緯度経度範囲を特定しようとするのね、まずは:

1   var ext = map.getView().calculateExtent(map.getSize());
2   if (map.getView().getZoom() < 8) {
3     return;
4   }
5   var min_lon_lat = ol.proj.transform(([ext[0], ext[1]], 'EPSG:3857', 'EPSG:4326');
6   var max_lon_lat = ol.proj.transform(([ext[2], ext[3]], 'EPSG:3857', 'EPSG:4326');

この際に上の補正を入れないと -220度なんて経度が返って来るわけです。これはこの wrap についての知識をサービス(cgi-bin/simple_metar_cgi.cgi)側が持つか、OpenLayers だけで頑張るなら、雑にやるならこんな:

 1     function transform_3857_4326(pos) {
 2       var res = ol.proj.transform(pos, 'EPSG:3857', 'EPSG:4326');
 3       var worlds = Math.floor((res[0] + 180) / 360);
 4       res[0] = res[0] - (worlds * 360);
 5       return [res, (worlds * 360)]; // 補正に使った値も返す
 6     }
 7     // ...
 8     var ext = map.getView().calculateExtent(map.getSize());
 9     var min_lon_lat = transform_3857_4326([ext[0], ext[1]]);
10     var max_lon_lat = transform_3857_4326([ext[2], ext[3]]);
11     var adjusted = min_lon_lat[1];
12     // ...
13     var markerOverlay = new ol.Overlay({
14       element: imgElement,
15       position: ol.proj.fromLonLat([lon + adjusted, lat]), // 補正分を戻す
16       positioning: 'bottom-center'
17     });
18     map.addOverlay(markerOverlay);

ただ今の場合いやらしいのが、補正値を得た lon は「boundingbox」の境界のものであって、下の方の lon はそれではないんだよねぇ(範囲内にいた特定の空港の経度)。際どいケースでは多分この処理、間違う。正解は単体で得た lon/lat を再び boundingbox に収まる範囲に補正することなんだけど、さらっと書けず。うーん、あとで考えよう。

なお、「巡り巡るな!」という「wrapX: false」という制御が 3.2 から「あることになっている」んだけど、ほんとなのかなぁ、効かない。ol.source.TileJSON には有効、みたいな情報もあったのでやってみるも結果は同じ。仮にこれが有効なら使い勝手が悪くなったりしないのかな、とも思うんだけど、試せてないのでそれすらわからず。無念。






30分後くらい追記:
マジメにやればそう難しくはなかった。とはいえ結構煩雑:

 1     function calc_wrapped(lon) {
 2       return Math.floor((lon + 180) / 360) * 360;
 3     }
 4     function trans_extent(ext) {
 5       var min_lonlat = ol.proj.transform([ext[0], ext[1]], 'EPSG:3857', 'EPSG:4326');
 6       var min_lon_wrapped = calc_wrapped(min_lonlat[0]);
 7       var max_lonlat = ol.proj.transform([ext[2], ext[3]], 'EPSG:3857', 'EPSG:4326');
 8       var max_lon_wrapped = calc_wrapped(max_lonlat[0]);
 9       return [
10           [min_lonlat[0] - min_lon_wrapped, min_lonlat[1],
11            max_lonlat[0] - max_lon_wrapped, max_lonlat[1]], // adjusted (min_lon, min_lat, max_lon, max_lat)
12           [min_lonlat[0], max_lonlat[0]] // not adjusted (min_lon, max_lon)
13         ];
14     }
15     function adjust_lon_to_bbox(lon, orig_lonrange) {
16       while (!(orig_lonrange[0] <= lon && lon < orig_lonrange[1])) {
17         if (orig_lonrange[0] > lon) {
18           lon += 360;
19         } else {
20           lon -= 360;
21         }
22       }
23       return lon;
24     }
25     // ...
26     var ext = map.getView().calculateExtent(map.getSize());
27     var tr_ext = trans_extent(ext);
28     var to_api = tr_ext[0]; // -220度なんて知らない、という API 向け
29     var orig_lonrange = tr_ext[1]; // -220度、のままの lon 範囲
30     // ...
31     var markerOverlay = new ol.Overlay({
32        element: imgElement,
33        // API から返ってきた「正統派」lon を「-220度的」な経度に「戻す」。(bbox に収まる値を採用。)
34        position: ol.proj.fromLonLat([adjust_lon_to_bbox(lon, orig_lonrange), lat]),
35        positioning: 'bottom-center'
36     });
37     map.addOverlay(markerOverlay);







7:20頃追記:
上の追記が間違ってるわけではないんだけれど、API がほんの少し範囲外のものを返してしまっていることを忘れていた。R-Tree でのラフな検索後に本来は厳密な判定が必要なんだけどまださぼってて、bbox に収まらないのに境界外のものを返しちゃう。ので、上の adjust_lon_to_bbox が無限ループに陥る。

ワタシの今のケースの場合は API の方を直せばいいんだけれど、そういうことが出来ない場合は、ちょっと幅をみるようにしようね:

 1     function adjust_lon_to_bbox(lon, orig_lonrange, torr) {
 2       while (!(orig_lonrange[0] - torr <= lon && lon < orig_lonrange[1] + torr)) {
 3         if (orig_lonrange[0] > lon) {
 4           lon += 360;
 5         } else {
 6           lon -= 360;
 7         }
 8       }
 9       return lon;
10     }