leaflet.jsはWeb上に地図を描くためのライブラリです。とても軽いと評判です。Reactだと専用のラッパーなどが用意されています。Phoenix Channelで作る最先端Webアプリ - 地図(Geo)拡張編。ElmからはPortを使って直接leaflet.jsを操るのが良いみたいです。leaflet公式サイト
今回作成したプログラムは以下のAWSサイトで動作しています。できればスマホのブラウザ(chrome)でアクセスしてみてください。あなたが移動すると地図のマーカーも移動します。PCでも構いませんが移動できないので面白くないかも。
https://s3-ap-northeast-1.amazonaws.com/elm-svg/leaflet.html
5秒ごとに自動的に現在位置を取得して、マーカーの位置を移動していきます。またzoom=15で初期化してありますが、「+」ボタン、「ー」ボタンで変更できます。
1.JavaScriptプログラム
Portを、ElmからJavaScriptのleaflet.jsを操るためのAPIと考えます。以下の3つのAPIを用意します。
-- OUTGOING PORT
portInitCurLocation : ElmからJavaScriptにlocation初期値を渡します。
portSetCurLocation : ElmからJavaScriptにlacation現在地を設定し返すように指示します。
-- INCOMING PORT
portGetCurLocation : lacation現在地を返します。
プログラムの流れ的には以下のように図示できます。
portInitCurLocation
portSetCurLocation portGetCurLocation
Elmプログラム --> JavaScriptプログラム --> Elmプログラム
さてJavaScriptプログラムのために、leaflet.htmlを作成します。leaflet.jsを読み込み、地図を表示します。ElmとのインターフェースであるPortを使います。JavaScriptコードにつきましては以下のサイトを参考にさせていただきました。
leaflet入門6|地図に現在地を表示する2
<!DOCTYPE HTML>
<html>
<head>
<title>Leaflet AND Elm</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
<style type="text/css">
<!--
#mapid { height: 400px; width: 600px}
-->
</style>
</head>
<body>
<div id="elm-area"></div>
<br /><br />
<div id="mapid"></div>
<script src="/js/app.js"></script>
<script>
const app = Elm.App.embed(document.getElementById("elm-area"));
var marker=null;
var zoom = 15;
app.ports.portInitCurLocation.subscribe( (model) => {
console.log("app.ports.portSetCurLocation.subscribe n="+model.point)
zoom = model.zoom;
mymap.setView(model.point, zoom);
})
app.ports.portSetCurLocation.subscribe( (model) => {
console.log("app.ports.portSetCurLocation.subscribe n="+model.point)
zoom = model.zoom;
setCurLocation()
})
var mymap = L.map('mapid')
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, '
}).addTo(mymap);
function setCurLocation(){
if (navigator.geolocation == false){
alert('現在地を取得できませんでした。');
return;
}
function success(e) {
var lat = e.coords.latitude;
var lng = e.coords.longitude;
mymap.setView([lat, lng], zoom);
if(marker) {
mymap.removeLayer(marker);
}
marker = L.marker([lat,lng]).addTo(mymap).bindPopup('現在地').openPopup();
var obj = new Object();
obj.zoom = zoom;
obj.point = [lat, lng];
app.ports.portGetCurLocation.send(obj);
};
function error() {
alert('現在地を取得できませんでした。');
};
navigator.geolocation.getCurrentPosition(success, error);
}
</script>
</body>
</html>
まずportInitCurLocationはElmプログラムの初期化時にCmdとして呼ばれるものです。portSetCurLocationはElmプログラムの「現在地を取得」ボタンを押したときに呼ばれます。
app.ports.portInitCurLocation.subscribe( (pos) => {
console.log("app.ports.portSetCurLocation.subscribe n="+pos)
mymap.setView(pos, 15);
})
app.ports.portSetCurLocation.subscribe( (pos) => {
console.log("app.ports.portSetCurLocation.subscribe n="+pos)
setCurLocation()
})
successはnavigator.geolocation.getCurrentPositionで現在地取得が成功した時に呼ばれます。現在地[lat, lng]をportGetCurLocationでElm側に返しています。
function success(e) {
#
app.ports.portGetCurLocation.send([lat, lng]);
};
2.Elmプログラム
Elmはこのプログラムのメインロジックを記述していきます。JavaScriptプログラムはPort APIの向こう側のサブルーティンにすぎません。なぜElmを使うのか? 私の場合はElmの純粋関数型のシンタックスが、可読性に優れたものに思えるからです。この点においては非常に優れています。
port module App exposing (..)
import Html exposing (Html, button, div, text, program)
import Html.Events exposing (onClick)
import Time exposing (Time, second)
-- OUTGOING PORT
port portInitCurLocation : Model -> Cmd msg
port portSetCurLocation : Model -> Cmd msg
-- INCOMING PORT
port portGetCurLocation : (Model -> msg) -> Sub msg
main : Program Never Model Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Point =
(Float, Float)
type alias Model =
{ zoom : Int
, point : Point
}
point0 : Point
point0 = (35.7102, 139.8132)
init : ( Model, Cmd Msg )
init =
( Model 15 point0, portInitCurLocation (Model 15 point0) )
-- UPDATE
type Msg = SetCurLocation | GetCurLocation Model | Tick Time | Increment | Decrement
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick _ ->
(model, portSetCurLocation model)
SetCurLocation ->
(model, portSetCurLocation model)
GetCurLocation newmodel ->
(newmodel, Cmd.none)
Increment ->
({ model| zoom = model.zoom + 1 }, Cmd.none)
Decrement ->
({ model| zoom = model.zoom - 1 }, Cmd.none)
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick SetCurLocation ] [ text "現在地を取得" ]
, div [] [ text "現在値: "
, text (toString<|Tuple.first<|model.point)
, text " - "
, text (toString<|Tuple.second<|model.point) ]
, div [] [ button [ onClick Decrement ] [ text "-" ]
, text (toString model.zoom)
, button [ onClick Increment ] [ text "+" ] ]
]
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ portGetCurLocation GetCurLocation
, Time.every (5 * second) Tick
]
ブラウザを開くと、以下のmodelの初期値でleaflet地図が初期化されます。zoom=15で、マーカーの位置が(35.7102, 139.8132)ですね。
point0 : Point
point0 = (35.7102, 139.8132)
init : ( Model, Cmd Msg )
init =
( Model 15 point0, portInitCurLocation (Model 15 point0) )
「現在地を取得」ボタンを押してもいいのですが、押さなくても5秒ごとに現在地のマーカーを更新していきます。
subscriptions model =
Sub.batch
#
, Time.every (5 * second) Tick
]
update msg model =
case msg of
Tick _ ->
(model, portSetCurLocation model)
JavaScript側で取得しなおした現在地は、Portを通してElmに戻されます。
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ portGetCurLocation GetCurLocation
#
]
最後に、Portでやり取りされるデータは、Elm recordとJavaScript objectで、Portを通るときに自動変換されることに注意してください。Portを通したElm Record と JavaScript Object の互換性 - Qiita
必要なパッケージをインストールします
elm-package install elm-lang/html
コンパイルします。
elm-make App.elm --output app.js
navigator.geolocationはSSL環境でのみ有効なので、AWS S3にアップロードして試します。私のアンドロイドスマホで無事表示できました。
https://s3-ap-northeast-1.amazonaws.com/elm-svg/leaflet.html
今回は以上です。