LoginSignup
5
0

More than 5 years have passed since last update.

Elmからportを通してleaflet .jsを操る

Last updated at Posted at 2018-04-16

 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

leaflet.html
<!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 &copy; <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の純粋関数型のシンタックスが、可読性に優れたものに思えるからです。この点においては非常に優れています。

App.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

 今回は以上です。

5
0
0

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
5
0