aboutsummaryrefslogtreecommitdiffstats
path: root/src/LngLat.elm
blob: a97d22b17030c2e43dc230da158051ecf7dc55ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
module LngLat exposing (LngLat, decodeFromObject, decodeFromPair, encodeAsObject, encodeAsPair, map, toString)

{-| Encodes geographic position.

@docs LngLat, encodeAsPair, encodeAsObject, decodeFromPair, decodeFromObject, map, toString

-}

import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode exposing (Value)


{-| A LngLat represents a geographic position.
-}
type alias LngLat =
    { lng : Float
    , lat : Float
    }


{-| Most implementations seem to encode these as a 2 member array.
-}
encodeAsPair : LngLat -> Value
encodeAsPair { lng, lat } =
    Encode.list Encode.float [ lng, lat ]


{-| Most implementations seem to encode these as a 2 member array.
-}
decodeFromPair : Decoder LngLat
decodeFromPair =
    Decode.list Decode.float
        |> Decode.andThen
            (\l ->
                case l of
                    [ lng, lat ] ->
                        Decode.succeed (LngLat lng lat)

                    _ ->
                        Decode.fail "Doesn't apear to be a valid lng lat pair"
            )


{-| We can also encode as an `{lng: 32, lat: 435}` object.
-}
encodeAsObject : LngLat -> Value
encodeAsObject { lng, lat } =
    Encode.object [ ( "lng", Encode.float lng ), ( "lat", Encode.float lat ) ]


{-| We can also encode from an `{lng: 32, lat: 435}` object.
-}
decodeFromObject : Decoder LngLat
decodeFromObject =
    Decode.map2 LngLat (Decode.field "lng" Decode.float) (Decode.field "lat" Decode.float)


{-| -}
map : (Float -> Float) -> LngLat -> LngLat
map f { lng, lat } =
    { lng = f lng, lat = f lat }


toDMS : Float -> String -> String -> String
toDMS angle pos neg =
    let
        absAngle =
            abs angle

        degrees =
            truncate absAngle

        minutes =
            (absAngle - toFloat degrees) * 60

        seconds =
            (minutes - toFloat (truncate minutes)) * 60 |> round

        prefix =
            if angle > 0 then
                pos
            else
                neg
    in
    String.fromInt degrees ++ "° " ++ String.fromInt (truncate minutes) ++ "' " ++ String.fromInt seconds ++ "\"" ++ prefix


{-| Returns a text representation suitable for humans.
-}
toString : LngLat -> String
toString { lng, lat } =
    toDMS lat "N" "S" ++ " " ++ toDMS lng "E" "W"