3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elmとleafletでつくるgeolocationの再現機とデータ解析

Last updated at Posted at 2018-04-30

 今回の記事では前記事でDBに蓄えたgeo情報を使って、移動した軌跡を地図上に再現する方法を示します。同じプロジェクトを拡張して、検索ページを追加して、検索のためのRest APIを追加します。
Elmとleafletでつくるgeolocationの発信機・受信機 - Qiita

 今回の技術的なポイントは以下の通りです。

1.PhoenixでRest APIを実装する
2.Ecto.Queryを使ってデータを取得する
3.Timexで時間文字列をunixtimeに変換する
4.Leaflet.jsで、polylineでラインを描き、複数のマーカをgroup化する
5.Leaflet.jsでlatlng.distanceTo()で距離を計算する
6.ElmからRest API CALLを行う
7.Elmで簡単なデータ解析を行う

#1.再現・データ解析ページ
 まず再現・データ解析ページ(Page2)を追加します。このページは開始時刻と終了時刻を入力すると、APIでその時間帯に歩いたデータを取得し、地図上にラインを描くというものです。抜粋したポイントの時刻もマーカで表示します。ついでにトータル距離や時間、速度、最高速度などのデータ解析を行い表示してくれます。ダイエットや健康促進にご利用いただけます?

image.png

 最高速度は10秒間毎の速度の最大値を取っていますが、バラツキが激しいですね。10秒間でなく、せいぜい3分毎程度のものを測らないと意味がないかもしれません。

 上の地図のラインは、以下のようなDBテーブルをもとに描いています。
image.png

 設定ですが手動で行います。routerとcontroller、viewと設定しますが全て基本的なものです。

##1-1.router

lib/leaflet_channel_web/router.ex
  scope "/", LeafletChannelWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/page2", Page2Controller, :redraw
  end

##1-2.controller

lib/leaflet_channel_web/controllers/page2_controller.ex
defmodule LeafletChannelWeb.Page2Controller do
  use LeafletChannelWeb, :controller

  def redraw(conn, _params) do
    render conn, "page2.html"
  end
end

##1-3.view

lib/leaflet_channel_web/views/page2_view.ex
defmodule LeafletChannelWeb.Page2View do
  use LeafletChannelWeb, :view
end

##1-4.template (leaflet.jsコード)

 テンプレートのためのディレクトリを作ります。

mkdir lib/leaflet_channel_web/templates/page2

 テンプレートですが、leaflet.jsを使って地図を表示するためのJavaScriptコードを含みます。

lib/leaflet_channel_web/templates/page2/page2.html.eex
<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>
    <a href="/">トップ</a>
    <br /><br />
    <div id="elm-area"></div>
    <br /><br />
    <div id="mapid"></div>

    <script src="/js/vendor/page2.js"></script>
    <script>
        const app = Elm.Page2.embed(document.getElementById("elm-area"));
        const point0 = [35.7102, 139.8132];
        let polyline = null;
        let grp = null;
        let zoom = 15;
        let dist = -1;
        let latlng1, latlng2;

        const 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);

        mymap.setView(point0, zoom);

        app.ports.portReqGeo.subscribe( (geogeos) => {
            var obj = [];
            geogeos.forEach(function(geogeo) {
                latlng1 = L.latLng(geogeo[0].lat, geogeo[0].lng);
                latlng2 = L.latLng(geogeo[1].lat, geogeo[1].lng);
                dist = latlng1.distanceTo(latlng2);
                obj.push ({geo1: geogeo[0], geo2: geogeo[1], dist: dist});
            })
            app.ports.portResNeo.send(obj);
        })


        app.ports.portLocations.subscribe( (latlngs) => {
          if( polyline ) {
              mymap.removeLayer(polyline);
          }
          if( latlngs.length > 0 ) {
              let p1 = latlngs[0];
              mymap.setView(p1, zoom);
              polyline = L.polyline(latlngs, {color: 'red'}).addTo(mymap);
          } else {
              mymap.setView(point0, zoom);
          }
        })

        app.ports.portMarkers.subscribe( (geos) => {
          if( grp ) {
            grp.clearLayers()
          }
          grp = L.layerGroup([]);

          geos.forEach(function(geo) {
            var time = unixTime2ymd(geo.time);
            //var marker = L.marker([geo.lat,geo.lng]).addTo(mymap).bindPopup(time).openPopup();
            var marker = L.marker([geo.lat,geo.lng]).bindPopup(time).openPopup();
            grp.addLayer(marker);
          });
          grp.addTo(mymap);
        });

        function unixTime2ymd(intTime){
            // var d = new Date( intTime );
            var d = new Date( intTime * 1000 );
            var year  = d.getFullYear();
            var month = d.getMonth() + 1;
            var day  = d.getDate();
            var hour = ( '0' + d.getHours() ).slice(-2);
            var min  = ( '0' + d.getMinutes() ).slice(-2);
            var sec   = ( '0' + d.getSeconds() ).slice(-2);

            return( year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + sec );
        }

    </script>
  </body>
</html>

 このテンプレートはleaflet.jsのコードが地図を表示する部分です。Elmコードからportを通してコントロールされています。Elmのport定義の抜粋を以下に示します。

portの定義
-- OUTGOING PORT
port portLocations : List (List Float) -> Cmd msg
port portMarkers : List Geo -> Cmd msg
port portReqGeo : List (Geo, Geo) -> Cmd msg

-- INCOMING PORT
port portResNeo : (List Neo -> msg) -> Sub msg

 それぞれのportの説明です。

portの説明
-- OUTGOING PORT
port portLocations : 歩いた軌跡のラインを描きます
port portMarkers : ポイントを抜粋して時刻を表示するマーカを描きます
port portReqGeo : 2地点の位置情報から距離を計算するためのリクエストです。

-- INCOMING PORT
port portResNeo : 2地点の距離の計算結果を返します。

 3つのOUTGOING PORTsは同じタイミングで発行されますので、1つにまとめることが可能ですが、機能が別なので分けています。それぞれのportで必要とする引数はElm側で加工してから渡しています。JavaScriptのコードは最小限に抑えて、データの加工などはElmに任せた方が全体的にスッキリと書ける気がします。

 最後に、時刻表示のマーカですが、複数表示するのでgroup化します。描き直すときにクリアーしますが、group毎一括して行えますので。

app.ports.portMarkers.subscribe
        app.ports.portMarkers.subscribe( (geos) => {
          if( grp ) {
            grp.clearLayers()
          }
          grp = L.layerGroup([]);

          geos.forEach(function(geo) {
#
            grp.addLayer(marker);
          });
          grp.addTo(mymap);
        });

#2.Rest APIの追加

 検索ページからリクエストを投げるAPIです。これもrouterとcontoroller、viewを定義していきます。手動で設定します。

##2-1.router

lib/leaflet_channel_web/router.ex
#
  scope "/api", LeafletChannelWeb do
    pipe_through :api
    get "/points", PointController, :index
  end
#

##2-2.contoroller

 ここではEcto.Queryを使ってDBからデータを取得しています。問い合わせの定義は、通常のSQLをラッピングしてElixir構造体で表現しています。Ectoの設定については前回の記事を参考にしてください。
Elmとleafletでつくるgeolocationの発信機・受信機 - Qiita

lib/leaflet_channel_web/controllers/point_controller.ex
defmodule LeafletChannelWeb.PointController do
  use LeafletChannelWeb, :controller

  alias LeafletChannel.Point

  # Imports only from/2 of Ecto.Query
  import Ecto.Query, only: [from: 2]

  def index(conn, %{"start" => start, "stop" => stop} ) do
    start = Timex.parse!(start<>" Asia/Tokyo", "%Y-%m-%d %H:%M:%S %Z" , :strftime) |> Timex.to_unix
    stop = Timex.parse!(stop<>" Asia/Tokyo", "%Y-%m-%d %H:%M:%S %Z" , :strftime) |> Timex.to_unix
    points = LeafletChannel.Repo.all(from p in Point, where: p.time > ^start and  p.time < ^stop, order_by: [desc: p.time])

    render(conn, "index.json", points: points)
  end
end

 Timex.parse!でTime Zoneを指定しないと9時間ずれてしまうので注意してください。%Z で指定します。

iex(23)> t=Timex.parse!("2013-03-05 12:30:45 Asia/Tokyo", "%Y-%m-%d %H:%M:%S %Z", :strftime)
#DateTime<2013-03-05 12:30:45+09:00 JST Asia/Tokyo>
iex(24)> t |> Timex.to_unix
1362454245

 timexは以下のようにしてインストールします。

mix.exs
#
  defp deps do
    [
#
      {:timex, "~> 3.1"}
    ]
  end
#
mix deps.get

##2-3.view

 Elmクライアントに返すJsonを定義します。

lib/leaflet_channel_web/views/point_view.ex
defmodule LeafletChannelWeb.PointView do
  use LeafletChannelWeb, :view
  alias LeafletChannelWeb.PointView

  def render("index.json", %{points: points}) do
    %{data: render_many(points, PointView, "point.json")}
  end

  def render("point.json", %{point: point}) do
    %{id: point.id,
      lat: point.lat,
      lng: point.lng,
      time: point.time}
  end
end

##2-4.Ectoあれこれ

 本筋とはあまり関係ありませんが。

 以下のコマンドでPoint テーブルを表示できます。

$ iex -S mix
iex(3)> LeafletChannel.Point |> LeafletChannel.Repo.all

 もうすこし複雑な問い合わせです。

alias LeafletChannel.Point

import Ecto.Query, only: [from: 2]

LeafletChannel.Repo.all(from p in Point, where: p.time > 1525055400 and  p.time < 1588224600, order_by: [desc: p.time])

 PostgreSQLのコマンドでも直接確認できます。

sudo -u postgres psql leaflet_channel_dev -c 'select * from point'

 開発中はDBを何度もリセットしますね。

mix ecto.drop
mix ecto.create
mix ecto.migrate

#3.Elmの環境設定

 brunch-config.jsにPage2.elmを追加します。

assets/brunch-config.js
#
    elmBrunch: {
      elmFolder: "elm",
      mainModules: ["App.elm","Page2.elm"], // Elmプログラム
      outputFolder: "../static/vendor/js"  // Elmの実行形をstaticに吐き出す
    }
#

(※重要)outputFolderにはvendorサブディレクトリが指定されていることに注意してください。brunchにおいてはvendorにあるfileが優先的にロードされるようです。つまりElmのjsファイルが安全にロードされます。単にoutputFolder を "../static/js"と vendor抜きで指定すると、かなりの確率でElmのjsファイルのロードに失敗してしまいます。

 以下のようなパッケージをインストールします。

assets/elm/elm-package.json
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "."
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0",
        "elm-lang/http": "1.0.0 <= v < 2.0.0",
        "elm-lang/http": "1.0.0 <= v < 2.0.0",
        "saschatimme/elm-phoenix": "0.3.0 <= v < 1.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}
elm-github-install

#4.Elmプログラム

##4-1.プログラム概略
 今回のElmプログラムは、前回の記事のものよりPhoenix channelを使わない分すっきりしています。その代わりにHttpでAPIを叩いていますが。
Elmとleafletでつくるgeolocationの発信機・受信機 - Qiita

 クライアントプログラムでは、以下のような仕事をします。

(1) DBに指定した時刻幅のgeoデータをリクエスト(API)する
(2) 歩いた地図を描き、軌跡を赤ラインで描画する
(3) 抜粋したポイントの時刻をマーカで表示する
(4)トータルの距離や時間、速度、最高速度などのデータ解析を行う

 (1)はPhoenixにhttpsリクエスト(API)を発行しています。
 (2) - (4) は(1)で得られた情報を加工してJavaScriptのleaflet.jsに指示を出しています。以下のportに対応しています。

portの説明
-- OUTGOING PORT
(2) port portLocations : 歩いた軌跡のラインを描きます
(3) port portMarkers : ポイントを抜粋して時刻を表示するマーカを描きます
(4) port portReqGeo : 2地点の位置情報から距離を計算するためのリクエストです。

-- INCOMING PORT
(4) port portResNeo : 2地点の距離の計算結果を返します。

 特に(4)については leaflet.jsの関数 latlng1.distanceTo(latlng2) を利用して、2ポイント間の距離を計算し、データ解析を行います。

##4-2.Elmの全コード
 それでは以下にElmの全コードを示します。

assets/elm/Page2.elm
port module Page2 exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Task exposing (Task)
import Http
import Json.Encode as E exposing (Value)
import Json.Decode as Decode

import Time exposing (Time)


minSpeed : Float
minSpeed = 50.0 -- 分速のMin。これ以下は異常値。
maxSpeed : Float
maxSpeed = 300.0 -- 分速のMax。これ以上は異常値。


-- OUTGOING PORT
port portLocations : List (List Float) -> Cmd msg
port portMarkers : List Geo -> Cmd msg
port portReqGeo : List (Geo, Geo) -> Cmd msg

-- INCOMING PORT
port portResNeo : (List Neo -> msg) -> Sub msg


geosUrl =
    "/api/points"


main =
    Html.program
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }

-- MODEL
type alias Geo =
              { id  : Int
              , lat : Float
              , lng : Float
              , time : Time
              }

type alias Neo =
              { geo1  : Geo
              , geo2 : Geo
              , dist : Float
              }

type alias Model =
              { geos : List Geo -- viewで使わないならModelに置く必要はない?
              , neos : List Neo
              , start : String
              , stop : String -- Do not use end! keyword in elixir
              }

start : String
start = "2018-05-01 06:30:00"
stop : String
stop = "2020-05-01 06:30:00"

init : ( Model, Cmd Msg )
init =
    Model [] [] start stop ! []


-- UPDATE
type Msg
    = GetGeos
    | MsgNewGeos (Result Http.Error (List Geo))
    | ResNeo (List Neo)
    | ChangeStart String
    | ChangeStop String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  case msg of
    GetGeos ->
      (model,  (getGeosList model) )

    MsgNewGeos (Ok newgeos) ->
      let
          len = List.length newgeos
          _ = Debug.log "list len=" len
      in
      { model | geos=newgeos } !
         [ portLocations <| List.map latlng <| newgeos
         , portMarkers <| List.filterMap isGeoSelect <| List.indexedMap (,) <| newgeos
         , portReqGeo <| List.map2 (,) newgeos (List.drop 1 newgeos)  -- [(g1,g2),(g2,g3),(g3,g4)...]
         ]


    MsgNewGeos (Err _) ->
      (model, Cmd.none)

    ResNeo neos ->
      let
          len = List.length neos
          _ = Debug.log "neos len=" len
      in
      {model | neos=neos} ! []

    ChangeStart start ->
      { model | start=start } ! []

    ChangeStop stop ->
      { model | stop=stop } ! []


isGeoSelect (n, geo) =
    case n%18==0 of
        True  -> Just geo
        False -> Nothing


latlng : Geo -> List Float
latlng geo =
      {--let
          _ = Debug.log "geo time=" geo.time
      in--}
      [ geo.lat, geo.lng ]

getGeosList : Model -> Cmd Msg
getGeosList model =
    Http.send MsgNewGeos (requestGeos model)

requestGeos : Model -> Http.Request (List Geo)
requestGeos model =
    let
       pointsUrl = geosUrl ++ "?start=" ++ model.start ++ "&stop=" ++ model.stop
    in
    Http.get pointsUrl ( Decode.field "data" ( Decode.list geo ) )

geo : Decode.Decoder Geo
geo =
    Decode.map4 toGeo
        (Decode.field "id" Decode.int)
        (Decode.field "lat" Decode.float)
        (Decode.field "lng" Decode.float)
        (Decode.field "time" Decode.float)

toGeo : Int -> Float -> Float -> Time -> Geo
toGeo id lat lng time =
    Geo id lat lng time



-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ input [ value model.start, onInput ChangeStart ] []
    , input [ value model.stop, onInput ChangeStop ] []
    , button [ onClick GetGeos ] [ text "リスト取得" ]
    , div [] ( viewNeos model )
    ]

viewNeos model =
    case (List.length model.geos)<2 || (List.length model.neos)<2 of
        True -> []
        False -> viewNeos2 model

viewNeos2 model =
    let
        neos = List.filter (\neo -> neo.dist>0) model.neos
        dist =  List.foldr (\neo acc -> neo.dist+acc) 0 neos
        diff =  getTimeDiff(model.geos)
        speed = if diff==0 then -1 else (dist/diff)*60 -- 分速
        speeds = groupAve 18 <| List.filter (\s -> s>minSpeed && s<maxSpeed) <| List.map getSpeed neos
        ave = case List.isEmpty(speeds) of
                  True -> -1
                  False -> (List.sum speeds) / toFloat(List.length(speeds))
        max = case List.maximum speeds of
                  Just m -> m
                  Nothing -> -1
        min = case List.minimum speeds of
                  Just m -> m
                  Nothing -> -1
        -- _ = Debug.log "speeds=" speeds
    in
    [ h3 [] [text "解析結果"]
    , p [] [text <| "距離(m) = "++(toString dist)]
    , p [] [text <| "時間(分) = "++(toString (diff/60))]
    , p [] [text <| "速度(分速) = "++(toString speed)]
    , p [] [text <| "速度(有効平均) = "++(toString ave)]
    , p [] [text <| "最高速度 = "++(toString max)]
    , p [] [text <| "最低速度 = "++(toString min)]
    -- , ul [] (List.map viewNeo model.neos)
    ]


getTimeDiff geos =
    let
        t1 = headTime geos
        t9 = headTime <| List.reverse geos
    in
    t1 - t9


headTime geos =
    case List.head geos of
        Just geo -> geo.time
        Nothing -> 0

getSpeed neo =
    case neo.geo1.time > neo.geo2.time of
        True  -> (neo.dist / (neo.geo1.time - neo.geo2.time)) * 60
        False -> -1

-- ある程度まとまった時間の分速を求める=> 10秒x18=3分毎の速度
groupAve : Int -> List Float -> List Float
groupAve n speeds =
    case List.isEmpty speeds of
        True -> []
        False -> let
                   head = List.take n speeds
                   ave = (List.sum head) / toFloat(List.length(head))
                   tail = List.drop n speeds
                   aves = groupAve n tail
                 in
                   ave::aves

viewNeo neo =
    li []
       [ text <| toString <| neo.geo1.time
       , text " - "
       , text <| toString <| neo.geo2.time
       , text " - "
       , text <| toString <| neo.dist
       ]


-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
      Sub.batch [ portResNeo ResNeo ]

##4-3.Port

 以下のコードが3つのOUTGOING port呼び出しと、それぞれの引数のためのデータ加工ですが、大変スッキリ書けていると自画自賛したくなります。Elmの記述性・効率性は素晴らしい!

OUTGOING-PORT
#
    MsgNewGeos (Ok newgeos) ->
      let
          len = List.length newgeos
          _ = Debug.log "list len=" len
      in
      { model | geos=newgeos } !
         [ portLocations <| List.map latlng <| newgeos
         , portMarkers <| List.filterMap isGeoSelect <| List.indexedMap (,) <| newgeos
         , portReqGeo <| List.map2 (,) newgeos (List.drop 1 newgeos)  -- [(g1,g2),(g2,g3),(g3,g4)...]
         ]
 #

 以下がINCOMING portをリッスンしているsubscriptionsの定義です。

INCOMING-PORT
subscriptions : Model -> Sub Msg
subscriptions model =
      Sub.batch [ portResNeo ResNeo ]

##4-4.基本データ

 以下が基本的なデータ定義です。GeoはDBから取得したpoint情報を入れるためのものです。Neoは、Geoをもとにleaflet.jsに計算させた2point間の距離を入れるためのものです。

基本データ
type alias Geo =
              { id  : Int
              , lat : Float
              , lng : Float
              , time : Time
              }

type alias Neo =
              { geo1  : Geo
              , geo2 : Geo
              , dist : Float
              }

##4-5.Rest API CALL

 以下の部分が、PhoenixのRest APIにリクエストを投げて、DBの検索結果を受け取る部分です。インターフェースをできるだけシンプルに設計すれば、この程度ならDecoderもシンプルに書けます。

API呼び出し
#
getGeosList : Model -> Cmd Msg
getGeosList model =
    Http.send MsgNewGeos (requestGeos model)

requestGeos : Model -> Http.Request (List Geo)
requestGeos model =
    let
       pointsUrl = geosUrl ++ "?start=" ++ model.start ++ "&stop=" ++ model.stop
    in
    Http.get pointsUrl ( Decode.field "data" ( Decode.list geo ) )

geo : Decode.Decoder Geo
geo =
    Decode.map4 toGeo
        (Decode.field "id" Decode.int)
        (Decode.field "lat" Decode.float)
        (Decode.field "lng" Decode.float)
        (Decode.field "time" Decode.int)

toGeo : Int -> Float -> Float -> Int -> Geo
toGeo id lat lng time =
    Geo id lat lng time
 #

##4-6.簡単なデータ解析

 以下がGeoリストとNeoリストを元に簡単なデータ解析を行い、表示している部分です。VIEWにおいて行っています。geo情報は、歩いたり止まったりしていて、必ずしもきれいな情報になっていない部分もあります。またブラウザの制限かなとも思いますが、環境によっては、そもそも常に正常なgeolocationが取得できるとも限らないようです。実際には異常値を取り除いてより正確な「速度」を割り出す工夫も必要なのでしょう。面白い課題だと思います。

データ解析
-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ input [ value model.start, onInput ChangeStart ] []
    , input [ value model.stop, onInput ChangeStop ] []
    , button [ onClick GetGeos ] [ text "リスト取得" ]
    , div [] ( viewNeos model )
    ]

viewNeos model =
    case (List.length model.geos)<2 || (List.length model.neos)<2 of
        True -> []
        False -> viewNeos2 model

viewNeos2 model =
    let
        neos = List.filter (\neo -> neo.dist>0) model.neos
        dist =  List.foldr (\neo acc -> neo.dist+acc) 0 neos
        diff =  getTimeDiff(model.geos)
        speed = if diff==0 then -1 else (dist/diff)*60 -- 分速
        speeds = groupAve 18 <| List.filter (\s -> s>minSpeed && s<maxSpeed) <| List.map getSpeed neos
        ave = case List.isEmpty(speeds) of
                  True -> -1
                  False -> (List.sum speeds) / toFloat(List.length(speeds))
        max = case List.maximum speeds of
                  Just m -> m
                  Nothing -> -1
        min = case List.minimum speeds of
                  Just m -> m
                  Nothing -> -1
        -- _ = Debug.log "speeds=" speeds
    in
    [ h3 [] [text "解析結果"]
    , p [] [text <| "距離(m) = "++(toString dist)]
    , p [] [text <| "時間(分) = "++(toString (diff/60))]
    , p [] [text <| "速度(分速) = "++(toString speed)]
    , p [] [text <| "速度(有効平均) = "++(toString ave)]
    , p [] [text <| "最高速度 = "++(toString max)]
    , p [] [text <| "最低速度 = "++(toString min)]
    -- , ul [] (List.map viewNeo model.neos)
    ]


getTimeDiff geos =
    let
        t1 = headTime geos
        t9 = headTime <| List.reverse geos
    in
    t1 - t9


headTime geos =
    case List.head geos of
        Just geo -> geo.time
        Nothing -> 0

getSpeed neo =
    case neo.geo1.time > neo.geo2.time of
        True  -> (neo.dist / (neo.geo1.time - neo.geo2.time)) * 60
        False -> -1

-- ある程度まとまった時間の分速を求める=> 10秒x18=3分毎の速度
groupAve : Int -> List Float -> List Float
groupAve n speeds =
    case List.isEmpty speeds of
        True -> []
        False -> let
                   head = List.take n speeds
                   ave = (List.sum head) / toFloat(List.length(head))
                   tail = List.drop n speeds
                   aves = groupAve n tail
                 in
                   ave::aves

 今回は以上で終了です。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?