概要
日本のサービスだと、位置情報から最寄駅を出したいなんて要件はよくあるかと思います。
ただ最寄駅といっても要件によって定義が曖昧ですよね。
例えば徒歩15分以内は最寄駅かなんて、判断する人によっても、その場所が車社会かなどによっても違ってきます。
今後の振れ幅を考えると外部API依存で身動きとりづらくなってしまうのもあれなので、自前で実装することにしました!
駅データの獲得
駅データ.jpで駅データを獲得します。
http://www.ekidata.jp/
駅データを加工
パフォーマンスも考え、awkを使ってrequireできるjavascriptの形に加工します。
$ tail -n +2 station20170403free.csv | awk 'BEGIN {FS=",";OFS=",";print "exports.default = ["} {print " {id:"$2",name:\""$3"\",lng:"$10",lat:"$11"},"} END {print "]"}' > station_data.js
$ head -n 3 station_data.js
exports.default = [
{id:1110101,name:"函館",lng:140.726413,lat:41.773709},
{id:1110102,name:"五稜郭",lng:140.733539,lat:41.803557},
$1
が同じ駅でも路線が異なると別で採番されるID。
$2
が同じ駅だと同じ数値で採番されるIDになります。
今回は要件上、路線を意識する必要はないので$2
だけを採用しました。
また要件次第では$2
のidでuniqを取った方が使いやすいと思います。
例えば近い順で何件かとってきたい場合、そのままだと「新宿駅だらけ」のような問題も起きてしまいます。
その場合、同じ駅でも路線によって位置情報や名前が異なるケースもあるので、どちらにしろAPI側で戻り値がuniqueになるようにゴニョゴニョする必要があります。
緯度・軽度から距離を計算する関数の作成
駅データ、APIに渡す値ともに緯度・経度なので、緯度経度から距離を計算する関数を作成します。
下記URLを参考に実装してみました。
http://www.ic.daito.ac.jp/~mizutani/gps/measuring_earth.html
function calcDistance (lat1, lng1, lat2, lng2) {
const Re = 6378.137 // 地球の半径
function radians (deg) {
return deg * Math.PI / 180
}
function rectangularCoordinateSystem (lat, lng) {
const h = radians(lat)
const t = radians(lng)
const x = Math.cos(h) * Math.cos(t)
const y = Math.cos(h) * Math.sin(t)
const z = Math.sin(h)
return [x, y, z]
}
function innerProduct(vector1, vector2) {
return vector1.reduce((carry, value, index) => {
return carry + value * vector2[index]
}, 0)
}
const v1 = rectangularCoordinateSystem(lat1, lng1)
const v2 = rectangularCoordinateSystem(lat2, lng2)
return Re * Math.acos(innerProduct(v1, v2))
}
最寄りの駅を獲得
現在地からの直線距離を全ての駅に対して計算して、近い順にソート。そして先頭のデータが最寄駅です。
const stations = require('station_data.js').default
function getNearestStation (lat, lng) {
return stations.map((station) => {
station.distance = calcDistance(lat, lng, station.lat, station.lng)
return station
}).sort((station1, station2) =>
station1.distance - station2.distance
).slice(0, 1)
}
これで直線距離が最短の駅を獲得!
Lambda関数として実装する
あとは、今回作った関数をLambdaで呼び出すだけです。
module.exports.getNearestStation = function(event, context, callback) {
const nearestStation = getNearestStation(event.lat, event.lng)
callback(null, {
statusCode: 200,
body: JSON.stringify(nearestStation)
})
})
}
完成!
これですごい簡単ですができました!
実際の現場では、GoogleMapAPIと連携して徒歩で何分かも取得しつつ、近い順に何件か取得するロジックで使っています。
オープンデータを提供くださっている方々のおかげで、自前でもなんとかなるものですね!