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 }