-
Notifications
You must be signed in to change notification settings - Fork 16
/
Geo3x3.fr
86 lines (71 loc) · 2.8 KB
/
Geo3x3.fr
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
module Geo3x3 where
import Java.Lang (StringBuffer(append,toString))
import Prelude.PreludeBase(StringJ(charAt))
import Prelude.PreludeIO (Ref(put,get,modify))
import Prelude.Math (floor)
import Data.Char (digit)
type Lat = Double
type Lng = Double
type Level = Int
type Unit = Double
type Encoder s = ST s String
type Decoder s = ST s (Lat,Lng,Level,Unit)
encode :: Lat -> Lng -> Level -> String
encode lat lng level = ST.run (f lat lng level)
where
f :: Lat -> Lng -> Level -> Encoder s
f lat lng level =
if level < 1 then return ""
else do
refState <- Ref.new (0,0,0)
refRes <- StringBuffer.new ""
let (!c,!lng') = if lng >= 0
then ('E',lng)
else ('W',lng + 180)
!lat' = lat + 90
!unit = 180
in do put refState (lat',lng',unit)
append refRes c
forM_ [1..level-1] $ \_ -> do
(lat,lng,unit) <- get refState
let !unit' = unit / 3
x = fromIntegral $ Double.long $ floor $ lng / unit'
y = fromIntegral $ Double.long $ floor $ lat / unit'
!c = (from $ ord '0' + x + y * 3 + 1)::Char
!lng' = lng - (fromIntegral x) * unit'
!lat' = lat - (fromIntegral y) * unit'
in do put refState (lat',lng',unit')
append refRes c
toString refRes
decode :: String -> (Lat,Lng,Level,Unit)
decode code = ST.run (f code)
where
f :: String -> Decoder s
f code =
if length code == 0 then return (0,0,0,0)
else do
refState <- Ref.new (0,0,1,180)
let (begin,isWest) =
case charAt code 0 of
c | c == '-' || c == 'W' -> (1,True)
c | c == '+' || c == 'E' -> (1,False)
_ -> (0,False)
clen = length code
let loop = \i ->
when (i < clen) $
let n = digit (charAt code i) 10
in when (1 <= n && n <= 9) $ do
modify refState $ \(lat,lng,level,unit) ->
let !unit' = unit / 3
(!lng',!lat') = let n' = n - 1
in (lng + (fromIntegral $ n' `mod` 3) * unit',
lat + (fromIntegral $ n' `div` 3) * unit')
!level' = level + 1
in (lat',lng',level',unit')
loop $ i + 1
in loop begin
modify refState $ \(lat,lng,level,unit) ->
let !lat' = let lat' = lat + unit / 2 in lat' - 90
!lng' = let lng' = lng + unit / 2 in if isWest then lng' -180 else lng'
in (lat',lng',level,unit)
get refState