This repository has been archived by the owner on Jan 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
handlers.clj
139 lines (123 loc) · 5.19 KB
/
handlers.clj
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
(ns hiposfer.kamal.services.webserver.handlers
(:require [ring.util.http-response :as code]
[compojure.core :as api]
[compojure.route :as route]
[hiposfer.kamal.specs.directions :as dirspecs]
[hiposfer.kamal.specs.resources :as resource]
[hiposfer.kamal.services.routing.directions :as dir]
[hiposfer.kamal.libs.geometry :as geometry]
[hiposfer.kamal.libs.fastq :as fastq]
[datascript.core :as data]
[clojure.edn :as edn]
[hiposfer.kamal.libs.tool :as tool]
[hiposfer.kamal.parsers.gtfs :as gtfs])
(:import (java.time ZonedDateTime)))
(def max-distance 1000) ;; meters
(defn- select
"returns a network whose bounding box contains all points"
[conns params]
(if-let [conn (first conns)]
(if (some? (data/entity @conn [:area/id (:area params)]))
(deref conn)
(recur (rest conns) params))
(code/bad-request! {:msg "unknown area" :data (:area params)})))
(defn- match-coordinates
[network params]
(for [coord (:coordinates params)
:let [node (:node/location (first (fastq/nearest-node network coord)))]
:when (some? node)
:let [dist (geometry/haversine coord node)]
:when (< dist max-distance)]
coord))
(defn- inside?
"returns a sequence of coordinates matched to the provided ones"
[network params]
(let [coords (match-coordinates network params)]
(when (= (count (:coordinates params)) (count coords))
coords)))
(defn preprocess
"checks that the passed request conforms to spec and coerce its params
if so. Returns a possibly modified request.
We do it like this instead of creating a middleware for readability. We
need access to the path parameters which are only added after the route
has been matched.
See: https://groups.google.com/forum/?hl=en#!topic/compojure/o5l9m7nbGlE"
[request spec coercer]
(let [params (tool/coerce (:params request) coercer)
errors (tool/assert params spec)]
(if (some? errors)
(code/bad-request! errors)
(assoc request :params params))))
(defn- get-area
[request]
(let [regions (:kamal/networks request)
areas (for [conn regions]
(let [id (data/q '[:find ?area .
:where [?area :area/name]]
@conn)]
(into {} (data/entity @conn id))))]
(code/ok areas)))
(def directions-coercer {:coordinates edn/read-string
:departure #(ZonedDateTime/parse %)})
(defn- get-directions
[request]
(let [networks (:kamal/networks request)
network (select networks (:params request))]
(if (inside? network (:params request))
(let [response (dir/direction network (:params request))]
(if (some? response)
(code/ok response)
(code/precondition-failed
{:code "NoRoute"
:msg "There was no route found for the given coordinates. Check for
impossible routes (e.g. routes over oceans without ferry connections)."})))
(code/precondition-failed
{:code "NoSegment"
:msg "No road segment could be matched for coordinates"}))))
(defn- get-resource
[request]
(let [regions (:kamal/networks request)
network (select regions (:params request))
k (keyword (:name (:params request)) "id")
v (gtfs/coerce (:id (:params request)))]
(code/ok (gtfs/resource (data/entity network [k v])))))
(defn- query-area
[request]
(let [regions (:kamal/networks request)
network (select regions (:params request))
q (:q (:params request))
args (:args (:params request))]
(code/ok (apply data/q q (cons network args)))))
;; ring handlers are matched in order
(defn create
"creates an API handler with a closure around the router"
[]
(api/routes
(api/GET "/area" request (get-area request))
(api/GET "/area/:area/directions" request
(-> (preprocess request ::dirspecs/params directions-coercer)
(get-directions)))
(api/GET "/area/:area/:name/:id" request
(-> (preprocess request ::resource/params {})
(get-resource)))
(api/GET "/area/:area" request
(-> (preprocess request ::resource/query {:q edn/read-string
:args edn/read-string})
(query-area)))
;; TODO: implement some persistency for user suggestions
;; (api/PUT "/area/:area/suggestions" request
;; (put-suggestions (preprocess request ::dirspecs/params directions-coercer)))
(route/not-found
(code/not-found "we couldnt find what you were looking for"))))
;; TESTS
;(fastq/nearest-node @(first @(:networks (:router hiposfer.kamal.dev/system)))
; [6.905707,49.398459])
;(time
; (direction @(first @(:networks (:router hiposfer.kamal.dev/system)))
; {:coordinates [[8.645333, 50.087314]
; [8.635897, 50.104172]]
; :departure (ZonedDateTime/parse "2018-05-07T10:15:30+02:00")
; :steps true}))
;(data/q '[:find ?id .
; :where [_ :trip/id ?id]]
; @(first @(:networks (:router hiposfer.kamal.dev/system))))