Mapbox で「地図の範囲」を示すのに LngLatBounds
というクラスがあります。
このクラスには「指定した緯度経度が含まれるように範囲を拡張する」extend(lnglat)
というメソッドがあり、検索結果のPOI群がちょうど収まる範囲を計算するのに大変便利です。
が、この extend が機能しない場合に遭遇したので気を付けましょうという記事です。
正しい結果を返すのは以下のコードです。
const bounds1 = new LngLatBounds([130, 35], [132, 37]);
bounds1.extend([133, 38]);
console.log(bounds1.toArray()); // OK - 130, 35, 133, 38
南西:[130, 35] ~ 北東[132, 37] の範囲があり、位置[133, 38] を含むように拡張したら、南西:[130, 35] ~ 北東[133, 38] になる、という例です。
次に、正しく動作しない場合は次のコードです。
const bounds2 = new LngLatBounds([132, 37], [130, 35]);
bounds2.extend([133, 38]);
console.log(bounds2.toArray()); // NG - 132, 37, 133, 38
このコードの不具合は、new LngLatBounds()
で 北東,南西 の順で緯度経度を渡してしまった事です。
LngLatBounds は指定された値の正規化は行わずそのまま使用してしまうため、[132, 37] が南西であると誤認したまま各機能が動いてしまいます。
どうしてこうなった
この不具合に気づくのに小一時間かかったわけですが、そもそもこうなったのは、「データベースに格納されている値が (北東)-(南西) だった」のが原因でした。
そしてそのDBは PostgreSQL。
地図範囲を格納するのに box 型を使っていたのですが、この仕様は、
頂点は 右上の頂点を最初 に、左下の頂点をその後に格納するよう必要に応じて並べ替えられます。
とのことで、上記の「南西:[130, 35] ~ 北東[132, 37]」は、DB には ((132,37),(130,35))
と格納されており、それをそのまま読み出して LngLatBounds にパースする処理で ((南西),(北東)) と勘違いしていました。
しかもこの仕様はご丁寧に INSERT する値が ((130,35),(132,37))
の場合は正規化(逆転)して ((132,37),(130,35))
を格納してくれちゃうのですね。
データベースとプログラミングとの境界付近では不具合が起きやすいので、特に注意する必要がありました。
おまけ
この記事では 範囲の最小=南西、最大=北東 という前提(つまり北半球)で書きましたが、南半球では・・・あっ、緯度がマイナス(赤道が0)になるだけだからよいのか。