Compare commits
7 Commits
e0d395ba9f
...
ab3a5aaf9f
Author | SHA1 | Date | |
---|---|---|---|
ab3a5aaf9f | |||
f379d2d9b9 | |||
8c187fe3c9 | |||
9fd3620d9b | |||
f4a9314033 | |||
c10e5ea70d | |||
aa36f74762 |
@ -80,11 +80,11 @@ type alias Lng = Float
|
|||||||
|
|
||||||
sec x = 1 / (cos x)
|
sec x = 1 / (cos x)
|
||||||
|
|
||||||
toCoord : Lat -> Lng -> Coord
|
toCoord : Pos -> Coord
|
||||||
toCoord lat lng =
|
toCoord pos =
|
||||||
let
|
let
|
||||||
lat_rad = lat * pi / 180
|
lat_rad = pos.lat * pi / 180
|
||||||
x = (lng + 180) / 360
|
x = (pos.lon + 180) / 360
|
||||||
y = (1 - (logBase e ((tan lat_rad) + (sec lat_rad))) / pi) / 2
|
y = (1 - (logBase e ((tan lat_rad) + (sec lat_rad))) / pi) / 2
|
||||||
in
|
in
|
||||||
Coord x y
|
Coord x y
|
||||||
@ -126,27 +126,35 @@ boundingTiles centre z width height =
|
|||||||
|
|
||||||
-- MODEL
|
-- MODEL
|
||||||
|
|
||||||
|
type DragTarget = Map | Graph | StartMark | EndMark
|
||||||
|
|
||||||
type Drag
|
type Drag
|
||||||
= None
|
= None
|
||||||
| Dragging (Int, Int) (Int, Int)
|
| Dragging DragTarget (Int, Int) (Int, Int)
|
||||||
|
|
||||||
|
|
||||||
dragTo : Drag -> (Int, Int) -> Drag
|
dragTo : Drag -> (Int, Int) -> Drag
|
||||||
dragTo d dest =
|
dragTo d dest =
|
||||||
case d of
|
case d of
|
||||||
None -> None
|
None -> None
|
||||||
Dragging from to -> Dragging from dest
|
Dragging target from to -> Dragging target from dest
|
||||||
|
|
||||||
dragDelta d =
|
dragDelta target d =
|
||||||
case d of
|
case d of
|
||||||
None -> (0,0)
|
Dragging target_ (fx,fy) (tx,ty) ->
|
||||||
Dragging (fx,fy) (tx,ty) -> (fx-tx, fy-ty)
|
if target == target_
|
||||||
|
then (fx-tx, fy-ty)
|
||||||
|
else (0, 0)
|
||||||
|
_ -> (0, 0)
|
||||||
|
|
||||||
|
subTuple (fx,fy) (tx,ty) = (fx-tx, fy-ty)
|
||||||
|
|
||||||
|
|
||||||
type TrackState = Empty | Loading | Failure String | Present (List Point)
|
type TrackState = Empty | Loading | Failure String | Present (List Point)
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ centre: Coord
|
{ centre: Coord
|
||||||
, zoom: FineZoomLevel
|
, zoom: FineZoomLevel
|
||||||
, drag: Drag
|
, drag: Drag
|
||||||
, timeDrag: Drag
|
|
||||||
, startTime : Int
|
, startTime : Int
|
||||||
, duration : Int
|
, duration : Int
|
||||||
, track: TrackState }
|
, track: TrackState }
|
||||||
@ -158,7 +166,9 @@ init _ url navKey =
|
|||||||
Just (Timeline (Just s) (Just d)) -> (s, d)
|
Just (Timeline (Just s) (Just d)) -> (s, d)
|
||||||
_ -> (10,10)
|
_ -> (10,10)
|
||||||
in
|
in
|
||||||
((Model (toCoord 51.60 -0.01) (FineZoomLevel (13*8)) None None start duration Empty),
|
((Model
|
||||||
|
(toCoord (Pos 0 0 Nothing))
|
||||||
|
(FineZoomLevel (1*8)) None 0 0 Empty),
|
||||||
(fetchTrack start duration))
|
(fetchTrack start duration))
|
||||||
|
|
||||||
-- SUBSCRIPTIONS
|
-- SUBSCRIPTIONS
|
||||||
@ -181,37 +191,45 @@ fetchTrack start duration = Http.get
|
|||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= MapScale Int
|
= MapScale Int
|
||||||
| MapDragStart (Int, Int)
|
| DragStart DragTarget (Int, Int)
|
||||||
| MapDrag (Int, Int)
|
| Drag (Int, Int)
|
||||||
| MapDragFinish (Int, Int)
|
| DragFinish (Int, Int)
|
||||||
| TimeScale (Float)
|
| TimeScale (Float)
|
||||||
| TimeDragStart (Int, Int)
|
|
||||||
| TimeDrag (Int, Int)
|
|
||||||
| TimeDragFinish (Int, Int)
|
|
||||||
| Loaded (Result Http.Error (List Point))
|
| Loaded (Result Http.Error (List Point))
|
||||||
| NewUrlRequest
|
| NewUrlRequest
|
||||||
| UrlChanged
|
| UrlChanged
|
||||||
|
| Dribble String
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
|
|
||||||
update msg model = (newModel msg model, Cmd.none)
|
update msg model = (updateModel msg model, Cmd.none)
|
||||||
|
|
||||||
newModel msg model =
|
updateModel msg model =
|
||||||
case msg of
|
case msg of
|
||||||
MapScale y ->
|
MapScale y ->
|
||||||
{ model | zoom = incZoom model.zoom y }
|
{ model | zoom = incZoom model.zoom y }
|
||||||
|
|
||||||
MapDragStart (x,y) ->
|
DragStart target (x,y) ->
|
||||||
{ model | drag = Dragging (x,y) (x,y) }
|
{ model | drag = Dragging target (x,y) (x,y) }
|
||||||
|
|
||||||
MapDrag (x,y) ->
|
Drag (x,y) ->
|
||||||
{ model | drag = dragTo model.drag (x,y) }
|
{ model | drag = dragTo model.drag (x,y) }
|
||||||
|
|
||||||
MapDragFinish (x,y) ->
|
DragFinish (x,y) ->
|
||||||
{ model | drag = None,
|
case model.drag of
|
||||||
centre = translatePixels model.centre (toZoom model.zoom) (dragDelta model.drag) }
|
Dragging Map start end ->
|
||||||
|
{ model |
|
||||||
|
drag = None,
|
||||||
|
centre = translatePixels model.centre (toZoom model.zoom) (subTuple start end) }
|
||||||
|
Dragging Graph start end ->
|
||||||
|
{ model |
|
||||||
|
drag = None,
|
||||||
|
startTime =
|
||||||
|
let (delta, _) = subTuple start end
|
||||||
|
in model.startTime + delta * model.duration // portalWidth
|
||||||
|
}
|
||||||
|
_ -> model
|
||||||
TimeScale factor ->
|
TimeScale factor ->
|
||||||
let fudge = factor
|
let fudge = factor
|
||||||
len = model.duration - floor(fudge)
|
len = model.duration - floor(fudge)
|
||||||
@ -220,25 +238,26 @@ newModel msg model =
|
|||||||
, duration = len
|
, duration = len
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeDragStart (x,y) ->
|
|
||||||
{ model | timeDrag = Dragging (x,y) (x,y) }
|
|
||||||
|
|
||||||
TimeDrag (x,y) ->
|
|
||||||
{ model | timeDrag = dragTo model.timeDrag (x,y) }
|
|
||||||
|
|
||||||
TimeDragFinish (x,y) ->
|
|
||||||
{ model | timeDrag = None,
|
|
||||||
startTime =
|
|
||||||
let (delta, _) = dragDelta model.timeDrag
|
|
||||||
in model.startTime + delta * model.duration // portalWidth
|
|
||||||
}
|
|
||||||
Loaded result ->
|
Loaded result ->
|
||||||
case result of
|
case result of
|
||||||
Ok trk -> { model | track = Present trk }
|
Ok trk ->
|
||||||
|
let start = Maybe.withDefault 0 (Point.startTime trk)
|
||||||
|
duration = Point.duration trk
|
||||||
|
in
|
||||||
|
{ model
|
||||||
|
| track = Present trk
|
||||||
|
, centre = toCoord (Point.centre trk)
|
||||||
|
, zoom = FineZoomLevel ( 13 * 8)
|
||||||
|
, startTime = floor start
|
||||||
|
, duration = ceiling duration
|
||||||
|
}
|
||||||
Err (Http.BadBody e) -> { model | track = Debug.log e (Failure "e") }
|
Err (Http.BadBody e) -> { model | track = Debug.log e (Failure "e") }
|
||||||
Err e -> { model | track = Debug.log "unknown error" (Failure "e") }
|
Err e -> { model | track = Debug.log "unknown error" (Failure "e") }
|
||||||
NewUrlRequest -> model
|
NewUrlRequest -> model
|
||||||
UrlChanged -> model
|
UrlChanged -> model
|
||||||
|
Dribble message ->
|
||||||
|
let _ = Debug.log "dribble" message
|
||||||
|
in model
|
||||||
|
|
||||||
|
|
||||||
-- VIEW
|
-- VIEW
|
||||||
@ -289,23 +308,20 @@ tileImg zoom tilenumber = img [ width 256,
|
|||||||
type alias Colour = String
|
type alias Colour = String
|
||||||
|
|
||||||
measureView : String -> Colour -> (Point -> Maybe Float) -> List Point -> Svg Msg
|
measureView : String -> Colour -> (Point -> Maybe Float) -> List Point -> Svg Msg
|
||||||
measureView title colour fn allPoints =
|
measureView title colour fn points =
|
||||||
let filteredPoints = Point.downsample 300 allPoints
|
let graphHeight = 180
|
||||||
graphHeight = 180
|
startTime = Maybe.withDefault 0 (Point.startTime points)
|
||||||
startTime = case allPoints of
|
|
||||||
(p::_) -> p.time
|
|
||||||
_ -> 0
|
|
||||||
coords p = case (fn p) of
|
coords p = case (fn p) of
|
||||||
Just c ->
|
Just c ->
|
||||||
(String.fromFloat (p.time - startTime)) ++ "," ++
|
(String.fromFloat (p.time - startTime)) ++ "," ++
|
||||||
(String.fromFloat c) ++ ", "
|
(String.fromFloat c) ++ ", "
|
||||||
Nothing -> ""
|
Nothing -> ""
|
||||||
maxY = List.foldr max 0 (List.filterMap fn filteredPoints)
|
maxY = List.foldr max 0 (List.filterMap fn points)
|
||||||
minY = List.foldr min maxY (List.filterMap fn filteredPoints)
|
minY = List.foldr min maxY (List.filterMap fn points)
|
||||||
(minYaxis, maxYaxis, tickY) = looseLabels 4 minY maxY
|
(minYaxis, maxYaxis, tickY) = looseLabels 4 minY maxY
|
||||||
rangeYaxis = maxYaxis - minYaxis
|
rangeYaxis = maxYaxis - minYaxis
|
||||||
maxX = Point.duration allPoints
|
maxX = Point.duration points
|
||||||
string = String.concat (List.map coords filteredPoints)
|
string = String.concat (List.map coords points)
|
||||||
ttick = timeTick maxX
|
ttick = timeTick maxX
|
||||||
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
|
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
|
||||||
ybar n = line
|
ybar n = line
|
||||||
@ -392,13 +408,13 @@ measureView title colour fn allPoints =
|
|||||||
, xtick 5
|
, xtick 5
|
||||||
]
|
]
|
||||||
|
|
||||||
timeAxis allPoints =
|
timeClickDecoder =
|
||||||
let filteredPoints = Point.downsample 300 allPoints
|
D.map Dribble (D.at ["target", "id"] D.string)
|
||||||
graphHeight = 30
|
|
||||||
startTime = case allPoints of
|
timeAxis points =
|
||||||
(p::_) -> p.time
|
let graphHeight = 30
|
||||||
_ -> 0
|
startTime = Maybe.withDefault 0 (Point.startTime points)
|
||||||
maxX = Point.duration allPoints
|
maxX = Point.duration points
|
||||||
ttick = timeTick maxX
|
ttick = timeTick maxX
|
||||||
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
|
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
|
||||||
xtick n =
|
xtick n =
|
||||||
@ -431,13 +447,35 @@ timeAxis allPoints =
|
|||||||
, fill "#eef"
|
, fill "#eef"
|
||||||
, stroke "none"
|
, stroke "none"
|
||||||
] []
|
] []
|
||||||
|
markStart x =
|
||||||
|
let x1 = String.fromInt x
|
||||||
|
in Svg.path
|
||||||
|
[ S.d ("M " ++ x1 ++ " 40 " ++
|
||||||
|
"v -50 h 10 v 10 h -10 v -10")
|
||||||
|
, fill "#7c7"
|
||||||
|
, stroke "#4e4"
|
||||||
|
, H.id "left-marker"
|
||||||
|
, strokeWidth "3"
|
||||||
|
] []
|
||||||
|
markEnd x =
|
||||||
|
let x1 = String.fromInt x
|
||||||
|
in Svg.path
|
||||||
|
[ S.d ("M " ++ x1 ++ " 40 " ++
|
||||||
|
"v -50 h -10 v 10 h 10 v -10")
|
||||||
|
, fill "#c77"
|
||||||
|
, stroke "#e44"
|
||||||
|
, H.id "right-marker"
|
||||||
|
, strokeWidth "3"
|
||||||
|
] []
|
||||||
in
|
in
|
||||||
svg
|
svg
|
||||||
[ width portalWidth
|
[ width portalWidth
|
||||||
, height graphHeight
|
, height (graphHeight + 20)
|
||||||
, preserveAspectRatio "none"
|
, on "pointerdown" timeClickDecoder
|
||||||
|
, viewBox ("0 -10 " ++ (String.fromInt portalWidth) ++
|
||||||
|
" " ++ (String.fromInt (graphHeight + 10)))
|
||||||
]
|
]
|
||||||
(bg::xticks)
|
(bg::(markStart 22)::(markEnd 422)::xticks)
|
||||||
|
|
||||||
|
|
||||||
cadenceView : List Point -> Svg Msg
|
cadenceView : List Point -> Svg Msg
|
||||||
@ -452,7 +490,7 @@ eleView = measureView "elevation" "#4444ee" (.pos >> .ele)
|
|||||||
trackView : Int -> Int -> ZoomLevel -> List Point -> Svg Msg
|
trackView : Int -> Int -> ZoomLevel -> List Point -> Svg Msg
|
||||||
trackView leftedge topedge zoom points =
|
trackView leftedge topedge zoom points =
|
||||||
let plot p =
|
let plot p =
|
||||||
let (x, y) = pixelFromCoord (toCoord p.pos.lat p.pos.lon) zoom
|
let (x, y) = pixelFromCoord (toCoord p.pos) zoom
|
||||||
x_ = x - leftedge
|
x_ = x - leftedge
|
||||||
y_ = y - topedge
|
y_ = y - topedge
|
||||||
in (String.fromInt x_) ++ ", " ++
|
in (String.fromInt x_) ++ ", " ++
|
||||||
@ -490,10 +528,11 @@ ifTrack : Model -> (List Point -> Html msg) -> Html msg
|
|||||||
ifTrack model content =
|
ifTrack model content =
|
||||||
case model.track of
|
case model.track of
|
||||||
Present t ->
|
Present t ->
|
||||||
let (dt, _) = dragDelta model.timeDrag
|
let (dt, _) = dragDelta Graph model.drag
|
||||||
dpix = dt * model.duration // portalWidth
|
dpix = dt * model.duration // portalWidth
|
||||||
start = toFloat (model.startTime + dpix)
|
start = toFloat (model.startTime + dpix)
|
||||||
points = Point.subseq t start (toFloat model.duration)
|
points = Point.subseq t start (toFloat model.duration) |>
|
||||||
|
Point.downsample 300
|
||||||
in content points
|
in content points
|
||||||
Failure f -> Debug.log f (div [] [ Html.text "failure", Html.text f])
|
Failure f -> Debug.log f (div [] [ Html.text "failure", Html.text f])
|
||||||
Loading -> div [] [Html.text "loading"]
|
Loading -> div [] [Html.text "loading"]
|
||||||
@ -521,11 +560,12 @@ canvas centre zoom width height model =
|
|||||||
,style "left" (px -offsetX)
|
,style "left" (px -offsetX)
|
||||||
,style "top" (px -offsetY)
|
,style "top" (px -offsetY)
|
||||||
,style "lineHeight" (px 0)
|
,style "lineHeight" (px 0)
|
||||||
,Pointer.onUp (\e -> MapDragFinish (epos e))
|
,Pointer.onUp (\e -> DragFinish (epos e))
|
||||||
,Pointer.onMove (\e -> MapDrag (epos e))
|
,Pointer.onMove (\e -> Drag (epos e))
|
||||||
,Pointer.onDown (\e -> MapDragStart (epos e)) ]
|
,Pointer.onDown (\e -> DragStart Map (epos e)) ]
|
||||||
(tv :: tiles xs ys zoom)
|
(tv :: tiles xs ys zoom)
|
||||||
|
|
||||||
|
|
||||||
portalWidth = 600
|
portalWidth = 600
|
||||||
portalHeight = 600
|
portalHeight = 600
|
||||||
|
|
||||||
@ -545,7 +585,7 @@ timeWheelDecoder =
|
|||||||
|
|
||||||
viewDiv : Model -> Html Msg
|
viewDiv : Model -> Html Msg
|
||||||
viewDiv model =
|
viewDiv model =
|
||||||
let coord = translate model.centre (pixelsToCoord (toZoom model.zoom) (dragDelta model.drag))
|
let coord = translate model.centre (pixelsToCoord (toZoom model.zoom) (dragDelta Map model.drag))
|
||||||
canvasV = canvas coord (toZoom model.zoom) portalWidth portalHeight model
|
canvasV = canvas coord (toZoom model.zoom) portalWidth portalHeight model
|
||||||
epos e = Tuple.mapBoth floor floor e.pointer.clientPos
|
epos e = Tuple.mapBoth floor floor e.pointer.clientPos
|
||||||
in div [ style "display" "flex"
|
in div [ style "display" "flex"
|
||||||
@ -568,9 +608,9 @@ viewDiv model =
|
|||||||
]
|
]
|
||||||
, div [ style "display" "flex"
|
, div [ style "display" "flex"
|
||||||
, Html.Events.custom "wheel" timeWheelDecoder
|
, Html.Events.custom "wheel" timeWheelDecoder
|
||||||
, Pointer.onUp (\e -> TimeDragFinish (epos e))
|
, Pointer.onUp (\e -> DragFinish (epos e))
|
||||||
, Pointer.onMove (\e -> TimeDrag (epos e))
|
, Pointer.onMove (\e -> Drag (epos e))
|
||||||
, Pointer.onDown (\e -> TimeDragStart (epos e))
|
, Pointer.onDown (\e -> DragStart Graph (epos e))
|
||||||
, style "flex-direction" "column"
|
, style "flex-direction" "column"
|
||||||
, style "row-gap" "10px"
|
, style "row-gap" "10px"
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module Point exposing(Pos, Point, decoder, downsample, duration, subseq)
|
module Point exposing(Pos, Point, decoder, downsample, duration, subseq, startTime, centre)
|
||||||
|
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
|
|
||||||
@ -63,6 +63,35 @@ duration points =
|
|||||||
(p::ps) -> (last p ps).time - p.time
|
(p::ps) -> (last p ps).time - p.time
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
|
startTime points =
|
||||||
|
case points of
|
||||||
|
(p::ps) -> Just p.time
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
type Bound = Bound Pos Pos | NoBound
|
||||||
|
|
||||||
|
extendBound : Pos -> Bound -> Bound
|
||||||
|
extendBound pos b =
|
||||||
|
let {lat, lon} = pos
|
||||||
|
in case b of
|
||||||
|
(Bound p1 p2) ->
|
||||||
|
Bound
|
||||||
|
(Pos (min lat p1.lat) (min lon p1.lon) Nothing)
|
||||||
|
(Pos (max lat p2.lat) (max lon p2.lon) Nothing)
|
||||||
|
NoBound ->
|
||||||
|
Bound pos pos
|
||||||
|
|
||||||
|
bounds points =
|
||||||
|
List.foldr extendBound NoBound (List.map .pos points)
|
||||||
|
|
||||||
|
centre points =
|
||||||
|
case bounds points of
|
||||||
|
Bound min max -> Pos
|
||||||
|
((max.lat + min.lat) / 2)
|
||||||
|
((max.lon + min.lon) / 2)
|
||||||
|
Nothing
|
||||||
|
NoBound -> Pos 0 0 Nothing
|
||||||
|
|
||||||
subseq points start dur =
|
subseq points start dur =
|
||||||
case points of
|
case points of
|
||||||
[] -> []
|
[] -> []
|
||||||
|
Loading…
Reference in New Issue
Block a user