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"