フロントエンド開発で好きなマップを作る


「みんなでテイクアウトMAP」を例に説明

  • 「テイクアウト」とあるけど、別に「トイレ」でも「酒屋」でもOK
  • データソースはOpenStreetMap。オープンデータで登録不要のAPIがあるので便利
    • 自前でデータベースと中に入れるデータを整備するよりも遥かに効率的
    • 不足したデータはみんなで登録することで、他プロジェクトの役にも立つ
    • OpenStreetMapは、プロジェクト間のデータ共有プラットフォームが本質
  • WebサーバーはGitHubをお借りする(Pages利用)ので無料
  • OpenStreetMapのデータはOverPass APIを利用するので登録不要&無料
  • 後はHTMLとJavaScriptでコードを書くだけでテイクアウトマップが作れる

OverPass APIについて

  • 大雑把に書くと、JavaScriptからOpenStreetMapの「データ」を取得するAPIのこと
    • 「データ」とは緯度経度付きの地物(施設、建物、道路、川、森、石碑など)
  • ビジネスなど大量アクセスしない限り、登録不要・無料で使えるAPIサーバーがある
  • クエリをAPIを呼ぶことで、欲しいデータだけを抜き出せる(例えば酒屋だけ抜き出す)
  • 簡単な使い方は、OpenStreetMap Advent Calendar 2019「OverPass APIのススメ」参照

Overpass APIでテイクアウト店舗を取得する

  • システム構成図

  • GitHub: みんなでテイクアウトMAP <開発版>

  • 動作画面 / デモサイト

  • 動作画面のポイント

    • 画面左側
      • 背景地図は何でも良い。例えばGoogle Maps(開発が必要だけど)でもOK
        • もちろん、OpenStreetMap標準タイルサーバーを利用してもOK
        • 今回は日本でもサービスが始まった Maptiler を利用してみた
      • OverPass APIで取得した店舗に合ったアイコンを表示させている
      • 背景地図は画像だけ利用し、APIで取得したデータを上に表示させている
    • 画面右側
      • OverPass APIで取得した店舗をリスト(ソート順は中心位置からの近さ)
      • GeoJsonで取得できるので、サーバーを別に用意する必要は無い

ソースコードの説明

OverPass APIの利用

  • OvPassCntクロージャのgetでデータを取得している
  • getの引数(targets)の内容は ["takeaway","delivery"] といった単なる配列
  • コード後半の targets.forEach でループさせて Conf.target[key].ovpass でOverPass APIに投げるクエリを取得させている
takeawayLib.js
var OvPassCnt = (function () {
    var Cache = { "geojson": [], "targets": [] };   // Cache variable
    var LLc = { "NW": { "lat": 0, "lng": 0 }, "SE": { "lat": 0, "lng": 0 } }; // latlng cache area

    return {
        get: function (targets) {
            return new Promise((resolve, reject) => {
                // 中略 // 
                targets.forEach(key => {
                    for (let ovpass in Conf.target[key].ovpass) { query += Conf.target[key].ovpass[ovpass] + maparea };
                });
                let url = OvServer + '?data=[out:json][timeout:30];(' + query + ');(._;>;);out meta qt;';
                console.log("GET: " + url);
                jqXHRs.push($.get(url, () => { DisplayStatus.progress(100) }));
  • 最終的にOverPass APIには以下のような内容で投げている(サンプルはシンプルにしてます)
  • 実際にクエリを投げてみるとJsonで返ってくることが分かる
GET: https://overpass-api.de/api/interpreter?data=[out:json][timeout:30];(node["takeaway"](34.689042740830814,135.44517159461978,34.715592149311874,135.48583388328555););(._;>;);out meta qt;

APIの返答結果をマーカー表示

  • OverPass APIから取得したデータを変数に格納して、それをマーカー表示させる部分
  • マーカー表示には leaflet.js のDivIconを利用している(背景地図もleaflet.js)
takeawayLib.js
var Marker = (function () {
    var markers = [];

    return {
        set: function (target) {
            markers[target] = [];
            let pois = PoiCont.get_target(target);
            pois.geojson.forEach(function (node, idx) {
                let tags = node.properties;
                let name = tags.name == undefined || Conf.local.TextViewZoom > map.getZoom() ? "" : tags.name;
                let keyn = tags.amenity !== undefined ? "amenity" : "shop";
                let icon = Conf.icon[keyn][tags[keyn]];
                icon = "./image/" + (icon !== undefined ? icon : Conf.icon.default);
                let html = '<div class="d-flex align-items-center"><img class="icon" src="' + icon + '"><span class="icon">' + name + '</span></div>'
                icon = L.divIcon({ "className": 'icon', "iconAnchor": [8, 8], "html": html });
                markers[target].push(L.marker(new L.LatLng(pois.latlng[idx].lat, pois.latlng[idx].lng), { icon: icon, draggable: false }));
                markers[target][markers[target].length - 1].addTo(map).on('click', e => Takeaway.view(e.target.takeaway_id));
            });
        },

詳しくはソースで!

  • 紹介した「みんなでテイクアウトMAP」はオープンソース(MITライセンス)で公開中
  • OverPass APIに渡すクエリと周辺を書き換えると、例えば「トイレマップ」なんかも作れる
  • GitHub: みんなでテイクアウトMAP <開発版>
  • (参考) OpenStreetMap Wiki: JA:Overpass API

もし良ければ、「みんなでテイクアウトMAP」を開発した理由も読んで頂けるとありがたいです。