biscuit

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

tiles.fnl (4907B)


      1 (local req (require :http.request))
      2 (local { : dict_to_query } (require :http.util))
      3 (local json (require :json))
      4 (local cqueues (require :cqueues))
      5 
      6 (import-macros { : define-tests : expect : expect= : expect-near } :assert)
      7 (local { : view } (require :fennel))
      8 
      9 (local f% string.format)
     10 
     11 (fn sinh [x] (/ (- 1 (math.exp (* -2 x))) (* 2 (math.exp (- x)))))
     12 
     13 (define-tests :sinh
     14   (expect (< (math.abs  (- (sinh 2) 3.626860407847)) 0.001)))
     15 
     16 (fn tile->latlon [xtile ytile zoom]
     17   (let [n (^ 2 zoom)
     18         lon-deg (- (/ (* xtile 360) n) 180.0)
     19         lat-rad (math.atan
     20                  (sinh (* math.pi (- 1 (/ (* 2 ytile) n))))
     21                  )]
     22 
     23     (values (/ (* lat-rad 180) math.pi) lon-deg)))
     24 
     25 (define-tests
     26   :tile->latlon
     27   (let [(lat lon) (tile->latlon 0 0 0)]
     28     (expect= lon -180)
     29     (expect-near lat 85.05112877)
     30     )
     31 
     32   (let [(lat lon) (tile->latlon 232798 103246 18)]
     33     (expect-near lon 139.699401855)
     34     (expect-near lat 35.6595278648)
     35     ))
     36 
     37 (fn latlon->tile [lat lon zoom]
     38   (let [n (^ 2 zoom)
     39         x (* n (/ (+ lon 180) 360))
     40         t1 (/ (* lat math.pi) 180)
     41         t (math.log (+ (math.tan t1) (/ 1 (math.cos t1))))
     42         y (* (- 1 (/ t math.pi)) (/ n 2))]
     43     (values x y)))
     44 
     45 (define-tests :latlon->tile
     46   (let [(x y) (latlon->tile 52.1234 -0.53 17)]
     47     (expect= (math.floor x) 65343)
     48     (expect= (math.floor y) 43221)))
     49 
     50 
     51 (fn overpass [lat lon zoom]
     52   (let [width (/ 360 (^ 2 zoom))
     53         n lat
     54         w lon
     55         s (- lat width)
     56         e (+ lon width)]
     57     (->
     58      [
     59       "[out:json];"
     60       (f% "way(%f,%f,%f,%f)['highway'];" s w n e)
     61       "(._;>;);"
     62       "out;"
     63       ]
     64      (table.concat  "\n"))))
     65 
     66 (fn label-coords [points]
     67   (var biggest 0)
     68   (var biggest-n 0)
     69   (for [i 2 (# points)]
     70     (let [[x1 y1] (. points (- i 1))
     71           [x2 y2]  (. points i)
     72           dist
     73           (+ (* (- x2 x1)  (- x2 x1))
     74              (* (- y2 y1)  (- y2 y1)))]
     75       (when (>= dist biggest)
     76         (set biggest dist)
     77         (set biggest-n (- i 1)))))
     78   (let [[x y] (. points biggest-n)
     79         [nx ny] (. points (+ 1 biggest-n))
     80         angle (math.atan (- nx x) (- y ny))]
     81     [(/ (+ nx x) 2) (/ (+ ny y) 2) angle]))
     82 
     83 
     84 (fn canvas [elements zoom]
     85   (let [nodes {}
     86         lines {}]
     87     (each [_ e (ipairs elements)]
     88       (case e.type
     89         :node (tset nodes e.id e)
     90         :way
     91         (let [points
     92               (icollect [_ nd (ipairs e.nodes)]
     93                 (let [node (. nodes nd)
     94                       (tx ty) (latlon->tile node.lat node.lon zoom)]
     95                   [ tx ty ]))]
     96           (tset
     97            lines
     98            e.id
     99            {
    100             :name (?. e :tags :name)
    101             :tags e.tags
    102             :label-place (label-coords points)
    103             : points
    104             }))))
    105     lines))
    106 
    107 
    108 (fn file-exists? [name]
    109   (match (io.open name :r)
    110     f (do (f:close) true)
    111     _ false))
    112 
    113 (fn unparsed-for-xyz [x y zoom]
    114   (let [(lat lon) (tile->latlon x y zoom)
    115         o (overpass lat lon zoom)
    116         r
    117         (req.new_from_uri
    118          "https://overpass-api.de/api/interpreter")
    119         query  { :data o }]
    120     (tset r.headers ":method" "POST")
    121     (r:set_body (dict_to_query query))
    122     (let [(headers stream) (r:go)]
    123       (if (= (headers:get  ":status") "429")
    124           nil
    125           (stream:get_body_as_string)))))
    126 
    127 ;; if we have json in disk, return it
    128 ;; if we have an empty file on disk, that signifies a request in
    129 ;;   flight, so return a "pending" sentinel
    130 ;; if we have no disk file, kick off a request and send "pending" sentinel
    131 
    132 ;; we'd like to have a way for completed background fetch to signal
    133 ;; so that the map can be redrawn
    134 
    135 (fn tile-name [x y zoom]
    136   (.. x "_" y "_" zoom))
    137 
    138 
    139 (fn tile-status- [x y zoom]
    140   (let [k (tile-name x y zoom)
    141         pathname (.. "/tmp/tiles/" k ".json")
    142         payload (match (io.open pathname :r)
    143                   f (with-open [i f] (i:read "*a"))
    144                   _ nil)
    145         (valid val) (if payload (pcall json.decode payload))]
    146     (if (or (= payload nil) (not valid))
    147         (values :missing pathname)
    148         (= payload "")
    149         (values :pending)
    150         valid
    151         (values :json val)
    152         true
    153         (values :unknown))))
    154 
    155 (fn tile-status [x y zoom]
    156   (let [(s v) (tile-status- x y zoom)]
    157     (values s v)))
    158 
    159 (fn fetch [cq x y zoom cb]
    160   (print "fetch" x y zoom)
    161   (case (tile-status x y zoom)
    162     (:pending) nil
    163     (:json val) (cb (canvas (. val :elements) zoom))
    164     (:missing pathname)
    165     (cq:wrap
    166      (fn []
    167        (print "getting " pathname)
    168        (var payload nil)
    169        (with-open [f (io.open pathname :w)]
    170          (while (not payload)
    171            (set payload (unparsed-for-xyz x y zoom))
    172            (when (not payload)
    173              (print "sleeping " pathname)
    174              (cqueues.sleep (math.random 2 6))))
    175          (print "got " pathname)
    176          (f:write payload))
    177        (fetch cq x y zoom cb)))))
    178 
    179 { : fetch : latlon->tile :name tile-name }