# Algoliaとleafletで位置情報検索を機能を構築する
Algoliaのデモで割とウケがイイ(?)のは、AIRPORTS - GEO SEARCH DEMOだったりするのですが、自分でも位置情報を使ってこういうのやってみたいな、と。
たまたま、ググってたら日本の空港|オープンデータ共有&ダウンロード|LinkDataを見つけて、更にLeafletというオープンソースのライブラリを使ってそれっぽいことをやっているHow to build a breakfast restaurant locator with nuxtjs and algolia searchという記事を見かけたので、これに沿っていっていってみようと思います。
# プロジェクトのセットアップ
npxコマンドで nuxt-leaflet-algolia という nuxtアプリを作ります。
$ npx create-nuxt-app nuxt-leaflet-algolia
ほぼデフォルトのまんまで👇こんな感じで作っていきます。
create-nuxt-app v2.15.0
✨ Generating Nuxt.js project in nuxt-leaflet-algolia
? Project name nuxt-leaflet-algolia
? Project description My praiseworthy Nuxt.js project
? Author name shinodogg
? Choose programming language JavaScript
? Choose the package manager Yarn
? Choose UI framework Tailwind CSS
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
👇のように、出来たディレクトリにいって、yarnコマンド叩けば始められるよ、と。
🎉 Successfully created project nuxt-leaflet-algolia
To get started:
cd nuxt-leaflet-algolia
yarn dev
To build & start for production:
cd nuxt-leaflet-algolia
yarn build
yarn start
今回はleafletを使うので、諸々ソレ系のを入れていきます。vue2-leafletはleafletのwrapperでnuxtと一緒に使う場合はnuxt-leafletを使うのがオススメ、と。 (色々よく分かっておらず恐縮ですが…w)
yarn add vue2-leaflet leaflet nuxt-leaflet
nuxt-leafletを使わずに自前で設定する方法もあるようですが、大人しく modules にnuxt-leafletを追加。
/*
** Nuxt.js modules
*/
modules: [
"nuxt-leaflet"
],
# vue2-leafletを使ってシンプルな地図を表示
- vue2-leafletのコンポーネントを使ってtemplateの中でマークアップしていく
- l-mapを使って地図に追加の属性を加えていく
- l-tile-layerで、今回はopenstreetmapの情報を使って構造化したりスタイルを設定したり
そして、pages/index.vueはマルっと書き換え。
<template>
<div>
<l-map
style="min-height: 100vh"
:zoom="zoom"
:center="center"
:options="{zoomControl: false}"
>
<l-tile-layer :url="url"></l-tile-layer>
</l-map>
</div>
</template>
<script>
import { LMap, LTileLayer } from "vue2-leaflet";
export default {
components: {
LMap,
LTileLayer
},
data() {
return {
url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
zoom: 10,
center: [47.31322, -1.319482]
};
}
};
</script>
とりあえず👇のようにズドンと地図が出ました。
そして、leafletのmapにマーカーを追加していきます。後々実際の空港データで置き換えるとして、まずはテストデータをl-map配下のl-tile-layerの下でl-markerとして👇こんな感じ。
<l-marker
v-for="geoloc in [
{ lat: 42.57101, lng: -0.54945 },
{ lat: 47.5125, lng: 16.03823 },
{ lat: 47.532612, lng: 16.154989 }
]"
:key="geoloc"
:lat-lng="geoloc"
/>
👇地図上にプロットされました。
# Algoliaのセットアップ
今回は airport_japan というインデックスを作ってそこにデータを入れていきます。 👇こんな感じにしてみようかな
- name - 名称
- _geoloc - 緯度経度
- address - 住所
- iata - NRTとかHNDとかそういうヤツ
👇のエクセルデータから、
👇必要なところを抽出して
👇テキストエディタに貼り付けてからタブをカンマに変換して、ツールにかけてそれっぽく
ここまではイイんだけど、Algoliaの場合👇のフォーマットにしてあげる必要があるため、
"_geoloc": {
"lat": 緯度,
"lng": 軽度
},
ちょっとした正規表現を使った置換のテクニックが必要になりますが、、まぁ👇こんな感じにしてあげます。
で、このJSONをアップロードして、Searchable attributesの設定とかをしてやると👇ぽゆい感じになりました。
まぁ、世の中にはalgolia-csv-jsっていう便利なツールもあったりしますよ、と。
# vue-instantsearchで開発
Algoliaのツール群をインストールします
yarn add algoliasearch vue-instantsearch
そして、components/Map.vueにleaflet周りの実装を移して、index.vueは👇こんな感じに。(AlgoliaのAppID, API Key, Index名はそれぞれ置き換える)
<template>
<div class="h-full w-full">
<ais-instant-search
:search-client="searchClient"
:index-name="algoliaIndex"
>
<mj-map />
</ais-instant-search>
</div>
</template>
<script>
import MjMap from "~/components/Map.vue";
import algoliasearch from "algoliasearch/lite";
import { AisInstantSearch } from "vue-instantsearch";
const ALGOLIA_APP_ID = "<insert-your-app-id>";
const ALGOLIA_SEARCH_ONLY_API_KEY = "<insert-your-api-key>";
const ALGOLIA_INDEX_NAME = "breakfast_restaurant";
const algoliaClient = algoliasearch(
ALGOLIA_APP_ID,
ALGOLIA_SEARCH_ONLY_API_KEY
);
export default {
components: {
MjMap,
AisInstantSearch
},
data() {
return {
algoliaIndex: ALGOLIA_INDEX_NAME,
searchClient: algoliaClient
}
}
};
</script>
そして、マーカーをAlgoliaのインデックスから取得。コレはMap.vueの中で行う感じだけど、$.axiosとかしなくてもイイ感じにやってくれちゃって便利ね、と。(個人的にはいつかライブラリを使わずに自前でホゲホゲしてちゃんと理解したい気持ちを持ち続けていますが…笑)
<script>
import { createWidgetMixin } from 'vue-instantsearch';
import { connectGeoSearch } from 'instantsearch.js/es/connectors';
export default {
mixins: [createWidgetMixin({ connector: connectGeoSearch })],
};
</script>
そんなこんなでMap.vueは👇で(特にl-markerのところはstate.itemsをループ回して〜と言う形)一丁上がり。
<template>
<l-map
class="min-h-screen z-10"
:zoom="zoom"
:center="center"
:options="{ zoomControl: true }"
>
<l-tile-layer :url="url" />
<div v-if="state">
<l-marker
v-for="item in state.items"
:key="item.objectID"
:lat-lng="item._geoloc"
/>
</div>
</l-map>
</template>
<script>
import { LMap, LTileLayer, LMarker } from "vue2-leaflet";
import { createWidgetMixin } from "vue-instantsearch";
import { connectGeoSearch } from "instantsearch.js/es/connectors";
export default {
name: "MjMap",
components: {
LMap,
LTileLayer,
LMarker
},
mixins: [createWidgetMixin({ connector: connectGeoSearch })],
data() {
return {
url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
zoom: 12,
center: [47.5125, 16.03823]
};
}
};
</script>
# 最初に日本がフォーカスされるように
上記のMap.vueだと center: [47.5125, 16.03823] なので、ヨーロッパが最初に表示されてしまいます。
東京 緯度経度でググったら、35.6804° N, 139.7690° Eとのことと、ズームがちょうどよくなるようにして👇こんな感じにんしてみました。
zoom: 5,
center: [35.6804, 139.7690]
👇ぽゆい感じになりまいした。が、デフォルトのヒット件数でひかかって全部出てない気がする。。。
が、そう言えば検索窓が無いな、と。ということで👇を入れて、
import "instantsearch.css/themes/algolia.css";
👇コンポーネントもちゃんとアレして、、
import { AisInstantSearch, AisSearchBox, AisPoweredBy } from "vue-instantsearch";
〜略〜
export default {
components: {
MjMap,
AisInstantSearch,
AisSearchBox,
AisPoweredBy
},
👇検索窓を付けてあげて
<div class="search">
<ais-search-box placeholder="空港検索" />
<ais-powered-by />
</div>
👇『北海道』で検索したら、、なぜか大阪にマーカーついてるな。。。笑
👇検索結果みると、全部北海道のようにみえるんだけども。。。
# 引き続き、、
冒頭でご紹介した AIRPORTS - GEO SEARCH DEMO に近づけるように継続して開発を進めてみたいと思います。
んま、ハッカソンとかでサクっと検索エンジンと地図を連携させてーみたいのがやりたい!みたいな時とかに良いんじゃないかな?と。
今回はleafletを使ってみましたが、もちろんMapboxやGoogle Maps等もご利用いただけます。
今の時期だったら、モバイルで取得した位置情報とかIPアドレスとかで自分がいるところから一番近い病院や検査機関を〜とかになるのでしょうか。