pluscodeを使って位置情報の近接エリアを取得する方法
Google Mapsとかで位置情報(緯度経度)をコード化して使われてるアルゴリズムなんだけど、まぁ似たものだとGeoHashとかがあるんだけど
そのpluscodeを使ってポイントとなる近接エリアを描画してみた
Google Maps APIが現在有料化されてて使えないのでopenstreetmapとleafletを使って描画する
pluscodeをエンコードとデコードできる関数を作る
別になくてもopen-location-codeのパッケージを使えばできる
const CODE_ALPHABET = "23456789CFGHJMPQRVWX";
function encode_pluscode(lat, lng, codeLength = 8) {
let remLat = lat + 90;
let remLng = lng + 180;
let size = 20;
let code = "";
const loops = codeLength / 2;
for (let i = 0; i < loops; i++) {
let latIndex = Math.floor(remLat / size);
let lngIndex = Math.floor(remLng / size);
const maxLatIndex = (i === 0) ? 8 : 19;
const maxLngIndex = (i === 0) ? 17 : 19;
if (latIndex > maxLatIndex) {
latIndex = maxLatIndex;
}
if (lngIndex > maxLngIndex) {
lngIndex = maxLngIndex;
}
code += CODE_ALPHABET[latIndex] + CODE_ALPHABET[lngIndex];
remLat -= latIndex * size;
remLng -= lngIndex * size;
size /= 20;
if (i === 3) {
code += "+";
}
}
return code;
}
function decode_pluscode(code) {
const cleanCode = code.replace("+", "");
let remLat = 0;
let remLng = 0;
let size = 20;
const loops = cleanCode.length / 2;
for (let i = 0; i < loops; i++) {
if (i > 0) {
size /= 20;
}
const latChar = cleanCode[i * 2];
const lngChar = cleanCode[i * 2 + 1];
const latIndex = CODE_ALPHABET.indexOf(latChar.toUpperCase());
const lngIndex = CODE_ALPHABET.indexOf(lngChar.toUpperCase());
remLat += latIndex * size;
remLng += lngIndex * size;
}
const latMin = remLat - 90;
const lngMin = remLng - 180;
const latMax = latMin + size;
const lngMax = lngMin + size;
return {
lat: (latMin + latMax) / 2,
lng: (lngMin + lngMax) / 2,
size: size,
bounds: {
southWest: [latMin, lngMin],
northEast: [latMax, lngMax]
}
};
}
これを使ってpluscodeを描画しつつ近接エリアをブロックで描画していく
描画HTML側
- 中心となるポイント(ここでは帝国ホテル)にピンを設定
- 近接のエリアにあるポイント(ここでは日比谷公園)にピンを設定
- 中心から隣接するエリアのpluscodeを取得して描画
HTMLは省略
// leafletを初期化
const map = L.map("map").setView([35.6725, 139.7570], 16);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map);
// 日比谷公園のピンを設定
const hibiyaPark = { name: "日比谷公園", lat: 35.6732, lng: 139.7558 };
const parkCode8 = encode_pluscode(hibiyaPark.lat, hibiyaPark.lng, 8);
const parkMarker = L.marker([hibiyaPark.lat, hibiyaPark.lng]).addTo(map);
parkMarker.bindTooltip(`${hibiyaPark.name}(${parkCode8})`, { permanent: true, direction: "top", offset: [0, -10] });
// 帝国ホテルのピンを設定
const userLocation = { name: "帝国ホテル", lat: 35.6723, lng: 139.7583 };
const userExactCode = encode_pluscode(userLocation.lat, userLocation.lng, 10);
const userAreaCode8 = encode_pluscode(userLocation.lat, userLocation.lng, 8);
const userMarker = L.marker([userLocation.lat, userLocation.lng]).addTo(map);
userMarker.bindTooltip(`${userLocation.name}(${userExactCode})`, { permanent: true, direction: "top", offset: [0, -10] });
const centerInfo = decode_pluscode(userAreaCode8);
const centerLat = centerInfo.lat;
const centerLng = centerInfo.lng;
const step = centerInfo.size;
for (let dLat = 1; dLat >= -1; dLat--) {
for (let dLng = -1; dLng <= 1; dLng++) {
// 隣マスの中心座標を計算
const nLat = centerLat + (dLat * step);
const nLng = centerLng + (dLng * step);
const neighborCode8 = encode_pluscode(nLat, nLng, 8);
// 隣マスの四角形の範囲を中心点からサイズ(step)の半分ずつ広げて計算
const half = step / 2;
const nBounds = { southWest: [nLat - half, nLng - half], northEast: [nLat + half, nLng + half] };
L.rectangle([nBounds.southWest, nBounds.northEast], { weight: 2, fillColor: "#0066cc", fillOpacity: 0.1 })
.addTo(map)
.bindTooltip(`<code>${neighborCode8}</code>`, { permanent: true, direction: "center"});
}
}
実際、描画してみると

という感じに中央となるポイントから隣接するエリアのpluscodeを取得してエリアを描画している
余談
実はこんなことしなくてもpluscodeの桁数を変える事で領域となるエリアを大きさがちゃんと存在する
- 6桁だと約5.5kmほど
- 8桁だと約270mほど
- 10桁だと約14mほど
という感じなのでそれでエリアを特定したりはできるのだが8桁から6桁に下げた場合の距離の大きさがあまりにもでかすぎるということで
今回のような起点となるポイントから隣接するエリアのpluscodeを取得してエリアを描画するっていう仕組みを利用している
また、空間インデックスライブラリ(turf.jsなど)を使ってやるっていう手もあるっぽい