LoginSignup
4
1

More than 3 years have passed since last update.

OSMを使って現在地の都道府県市町村を取得する

Last updated at Posted at 2021-05-08

はじめに

今作ってるゲームで位置情報を使いたいと考えていて、ただ、厳密に位置を使うのではなくて市町村レベルの情報が取得できればいいなと思って調べたので、整理も兼て書いておきます。

OSMでできないかと思い調べると、Nominatimを使うことでできるようです。
今回はブラウザで動作させているので、WebAPIのGeo Location APIと組み合わせることで実現ができました。

GitHubにソースを公開しています。

Geo Location API

ブラウザで位置情報を扱うことのできるAPIです。
使い方は結構簡単でした。

位置情報を取得するAPIは2つあります。
呼び出した時点の現在の位置を取得する Geolocation.getCurrentPosition()
定期的に現在地を取得する Geolocation.watchPosition()

今回は、Geolocation.getCurrentPosition() を使いました

MDNを見ると

getCurrentPosition(success[, error[, [options]])

となっています。
というわけで以下のように実装してみました。
オプションはMDNの説明を参照してください。

// navigator.geolocationをチェックして、GetLocation APIをサポートしているかの確認
var isGeolocSupport
if (navigator.geolocation) {
    isGeolocSupport = true;
} else {
    isGeolocSupport = false;
}

document.addEventListener('DOMContentLoaded', function() {
    if(isGeolocSupport){
        navigator.geolocation.getCurrentPosition(function(position){
            // 位置情報が取得できた場合
            var request = new XMLHttpRequest();
            var lat = position.coords.latitude;
            var lon = position.coords.longitude;
            // 何か処理する
        },
        function(error){
            // 取得できない場合
        },{
            // オプション
            enableHighAccuracy : true,
            timeout : 30 * 1000,
            maximumAge : 60 * 1000
        });
    }
});

まぁ、こんな感じで簡単に現在地を取得できます。
ちなみに、ブラウザでアクセスすると、以下のように確認ダイアログが表示されます。
image.png

Nominatim

私自身、機能を把握しきれていませんが、Wikiを見ると
OSMデータを名前や住所で探したり、OSMポイントの住所を合成して生成する(逆ジオコーディング)ツール
と説明されています。
検索とかに使えるAPIのようです。

このNominatimを使い、現在位置から住所を取得します。
Nominatimには search, reverse, lookup などが用意されています。
今回は緯度経度から住所を取得したいので、reverseを利用します。
今回は以下のオプションで利用ています。

パラメタ 説明
format json json形式で結果を取得する
zoom 18 家/建物レベルを指定
addressdetails 1 要素内で細分化した住所を含む(この指定の必要があるかは未検証)

戻ってくるjsonですが、addressに住所の情報が入ってきます。
住所の情報は地域で入ってくるキーが異なるようです。
そのあたりを考慮して結果を取得する必要があります。

キー 説明
province 都道府県
state
region 地方
county
city 市町村
district ???
suburb
town
village
neighbourhood 小字 / 字 / 丁 / 丁目 / 島嶼部
road 道?

で、その辺を考慮して実装していますが・・・ダサい。
まぁ、それはおいておいて、これで都道府県市町村を取得できます。

var request = new XMLHttpRequest();
request.onload = function () {
    var data = this.response;
    var result = document.getElementById('result');

    var addr1 = "";
    var addr2 = "";
    if(data != null && data.address != null){
        if(data.address.province){
            addr1 = data.address.province;
        }
        if(data.address.state){
            if(!addr1){
                addr1 = data.address.state;
            }else if(!addr2){
                addr2 = data.address.state;
            }
        }
        if(data.address.region){
            if(!addr1){
                addr1 = data.address.region;
            }else if(!addr2){
                addr2 = data.address.region;
            }
        }
        if(data.address.county){
            if(!addr1){
                addr1 = data.address.county;
            }else if(!addr2){
                addr2 = data.address.county;
            }
        }
        if(data.address.city){
            if(!addr1){
                addr1 = data.address.city;
            }else if(!addr2){
                addr2 = data.address.city;
            }
        }
        if(data.address.district){
            if(!addr1){
                addr1 = data.address.district;
            }else if(!addr2){
                addr2 = data.address.district;
            }
        }
        if(data.address.suburb){
            if(!addr1){
                addr1 = data.address.suburb;
            }else if(!addr2){
                addr2 = data.address.suburb;
            }
        }
        if(data.address.town){
            if(!addr1){
                addr1 = data.address.town;
            }else if(!addr2){
                addr2 = data.address.town;
            }
        }
        if(data.address.village){
            if(!addr1){
                addr1 = data.address.village;
            }else if(!addr2){
                addr2 = data.address.village;
            }
        }
        if(data.address.neighbourhood){
            if(!addr1){
                addr1 = data.address.neighbourhood;
            }else if(!addr2){
                addr2 = data.address.neighbourhood;
            }
        }
        if(data.address.road){
            if(!addr1){
                addr1 = data.address.road;
            }else if(!addr2){
                addr2 = data.address.road;
            }
        }
    }
    result.innerText = addr1 + addr2;
}   
var url = "https://nominatim.openstreetmap.org/reverse?"
        + "format=json"
        + "&lat=" + lat
        + "&lon=" + lon
        + "&zoom=18"
        + "&addressdetails=1";
request.open('GET', url, true);
request.responseType = 'json';
request.send();

ついでに近辺の情報も取得したい

欲が出て、周辺の情報も取得したくなりました。
その場合、Overpass APIを使うことで実現できます。
Overpass APIは利用するサーバーにより条件があったります。
詳しくは JA:Overpass API - OpenStreetMap Wiki を参照してください。

今回は、現在の位置を中心に、50kmの範囲にあるコンビニを取得してみました。
緯度経度を距離に換算する情報は 緯度経度のメートル換算 - teratail を参考にさせていただきました。

Overpass APIにリクエストする場合、JSONで値を送ることができないようで、formデータとして送信します。
(もし、JSONで送る方法があるならコメントで教えてくださいm(_ _)m )
いろいろ指定できるのですが、実際に利用する前に overpass turbo でパラメタを指定してどんな値が返ってくるのかを確認できます。
なので、ここで動いた条件を、formデータとして利用すると良いと思います。

var url = "https://overpass-api.de/api/interpreter?" ;
// 0.00001 が 1m
var areaLen = 0.00001 * 50 * 1000;
var formData = "data="
        + "[out:json][timeout:25];"
        + "("
        + 'node["shop"="convenience"](' + (lat + areaLen * -1) + ',' + (lon + areaLen * -1) + ',' + (lat + areaLen) + ',' + (lon  + areaLen) + ');'
        + ");"
        + "out body;";
var overpass = new XMLHttpRequest();
var logArea = document.getElementById("log");
logArea.value = "";
overpass.onload = function () {
    var data = this.response;
    var logArea = document.getElementById("log");
    data.elements.forEach(poi => {
        logArea.value += poi.tags.name + "\r\n";
    });
}
overpass.open('POST', url, true);
overpass.responseType = 'json';
overpass.send(formData);

実行するとこんな感じになります。
50kmは広すぎですね(縦横100kmになるからなぁ・・・)

image.png

最後に

どうやってこれを使っていくかは、まだ検討段階です。
IngressやポケGoのように割と精度の必要となる位置情報ゲームではなく、駅メモのような、一番近いものにアクセスするようなものを作る場合には、こういった使い方が生かせるかもしれないと思います。

参考

4
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1