Now reads config.yaml.
Configuration now comes from config.yaml. Command line flags override config.yaml values. Added --config and --secrets to specify the path to config.yaml and secrets.yaml.
This commit is contained in:
parent
807b5d7d44
commit
074f7c77c3
@ -60,7 +60,7 @@
|
|||||||
(let [url (->> [url
|
(let [url (->> [url
|
||||||
"lat" lat
|
"lat" lat
|
||||||
"lon" lon
|
"lon" lon
|
||||||
"dist" radius-nm]
|
"dist" (.toFixed radius-nm 1)]
|
||||||
(map str)
|
(map str)
|
||||||
(string/join "/"))]
|
(string/join "/"))]
|
||||||
(p/let [http-result (util/http-get url {:headers {:api-auth api-key}})]
|
(p/let [http-result (util/http-get url {:headers {:api-auth api-key}})]
|
||||||
@ -69,15 +69,10 @@
|
|||||||
result))))
|
result))))
|
||||||
|
|
||||||
|
|
||||||
;; We keep position reports going back this far.
|
|
||||||
|
|
||||||
(def max-history-age-ms (* 25 60 1000))
|
|
||||||
|
|
||||||
|
|
||||||
;; Given a vector of position history, removes old entries.
|
;; Given a vector of position history, removes old entries.
|
||||||
|
|
||||||
(defn prune-history [history now]
|
(defn prune-history [history now config]
|
||||||
(let [h (filterv #(< (- now (:time %)) max-history-age-ms) history)]
|
(let [h (filterv #(< (- now (:time %)) (:max-history-age-ms config)) history)]
|
||||||
h))
|
h))
|
||||||
|
|
||||||
|
|
||||||
@ -109,9 +104,9 @@
|
|||||||
updated-db))
|
updated-db))
|
||||||
|
|
||||||
|
|
||||||
(defn prune-histories [db now]
|
(defn prune-histories [db now config]
|
||||||
(reduce-kv (fn [m k v]
|
(reduce-kv (fn [m k v]
|
||||||
(assoc m k (update v :history prune-history now)))
|
(assoc m k (update v :history prune-history now config)))
|
||||||
{}
|
{}
|
||||||
db))
|
db))
|
||||||
|
|
||||||
@ -144,10 +139,10 @@
|
|||||||
(first args))
|
(first args))
|
||||||
|
|
||||||
|
|
||||||
(defn update-history-db [db new-data now]
|
(defn update-history-db [db new-data now config]
|
||||||
(-> db
|
(-> db
|
||||||
(update-history-db-add-new-data new-data now)
|
(update-history-db-add-new-data new-data now)
|
||||||
(prune-histories now)
|
(prune-histories now config)
|
||||||
(prune-records now)))
|
(prune-records now)))
|
||||||
|
|
||||||
|
|
||||||
@ -170,12 +165,6 @@
|
|||||||
(/ (.getTime (js/Date.)) 1))
|
(/ (.getTime (js/Date.)) 1))
|
||||||
|
|
||||||
|
|
||||||
;; This is how many degrees of turning we need to see over
|
|
||||||
;; max-history-age-ms ms to consider it a potential circling aircraft.
|
|
||||||
|
|
||||||
(def curviness-threshold-degrees 1440)
|
|
||||||
|
|
||||||
|
|
||||||
(defn ac-desc [ac]
|
(defn ac-desc [ac]
|
||||||
(str (:icao ac) " " (:lat ac) " " (:lon ac)
|
(str (:icao ac) " " (:lat ac) " " (:lon ac)
|
||||||
" #" (:registration ac) " " (:alt ac) " " (:curviness ac) " "
|
" #" (:registration ac) " " (:alt ac) " " (:curviness ac) " "
|
||||||
@ -194,15 +183,15 @@
|
|||||||
image-path))
|
image-path))
|
||||||
|
|
||||||
|
|
||||||
(defn circling? [ac]
|
(defn circling? [ac config]
|
||||||
(and (> (geo/flight-curviness (:history ac)) curviness-threshold-degrees)
|
(and (> (geo/flight-curviness (:history ac)) (:curviness-threshold-degrees config))
|
||||||
(> (:alt ac) 300)))
|
(> (:alt ac) 300)))
|
||||||
|
|
||||||
|
|
||||||
;; Returns a vector of two elements,
|
;; Returns a vector of two elements,
|
||||||
;; [updated-database potentially-circling-aircraft]
|
;; [updated-database potentially-circling-aircraft]
|
||||||
|
|
||||||
(defn detect-circles [db now]
|
(defn detect-circles [db now config]
|
||||||
(log-verbose "Detecting circles")
|
(log-verbose "Detecting circles")
|
||||||
(loop [old-db (seq db)
|
(loop [old-db (seq db)
|
||||||
new-db {}
|
new-db {}
|
||||||
@ -213,7 +202,7 @@
|
|||||||
ac (assoc ac
|
ac (assoc ac
|
||||||
:curviness curviness
|
:curviness curviness
|
||||||
:normalized-curviness (geo/flight-normalized-curviness (:history ac)))
|
:normalized-curviness (geo/flight-normalized-curviness (:history ac)))
|
||||||
currently-circling? (circling? ac)
|
currently-circling? (circling? ac config)
|
||||||
previously-circling? (:started-circling-time ac)]
|
previously-circling? (:started-circling-time ac)]
|
||||||
(cond
|
(cond
|
||||||
(and currently-circling?
|
(and currently-circling?
|
||||||
@ -371,11 +360,6 @@
|
|||||||
recent-hist))
|
recent-hist))
|
||||||
|
|
||||||
|
|
||||||
;; If the centroid of the aircraft's positions is less than this close
|
|
||||||
;; to an airport, then it's probably just doinf flight training.
|
|
||||||
(def minimum-airport-distance-km 2.5)
|
|
||||||
;;(def minimum-airport-distance-miles 0)
|
|
||||||
|
|
||||||
(defn process-potential-circle [ac config now]
|
(defn process-potential-circle [ac config now]
|
||||||
(p/let [icao (:icao ac)
|
(p/let [icao (:icao ac)
|
||||||
recent-positions (recent-history (:history ac))
|
recent-positions (recent-history (:history ac))
|
||||||
@ -393,11 +377,11 @@
|
|||||||
(log-info "%s: Closest airport is %s, distance: %s km"
|
(log-info "%s: Closest airport is %s, distance: %s km"
|
||||||
(:icao ac) (:label airport-properties) (:distance airport-properties))
|
(:icao ac) (:label airport-properties) (:distance airport-properties))
|
||||||
(log-info "%s: No airports nearby" (:icao ac)))
|
(log-info "%s: No airports nearby" (:icao ac)))
|
||||||
(if (and airport-properties (<= (:distance airport-properties) minimum-airport-distance-km))
|
(if (and airport-properties (<= (:distance airport-properties) (:minimum-airport-distance-km config)))
|
||||||
(log-info "%s: Filtering out because it's %s km (minimum is %s) from %s"
|
(log-info "%s: Filtering out because it's %s km (minimum is %s) from %s"
|
||||||
(:icao ac)
|
(:icao ac)
|
||||||
(:distance airport-properties)
|
(:distance airport-properties)
|
||||||
minimum-airport-distance-km
|
(:minimum-airport-distance-km config)
|
||||||
(:label airport-properties)
|
(:label airport-properties)
|
||||||
())
|
())
|
||||||
(do
|
(do
|
||||||
@ -458,55 +442,88 @@
|
|||||||
(p/recur (rest acs))))))
|
(p/recur (rest acs))))))
|
||||||
|
|
||||||
|
|
||||||
(def history-db-path "advisory-circular.db")
|
(def default-config
|
||||||
(def secrets-path "secrets.yaml")
|
{
|
||||||
|
;; We keep position reports going back this far.
|
||||||
|
:max-history-age-ms (* 25 60 1000)
|
||||||
|
;; This is how many degrees of turning we need to see over
|
||||||
|
;; max-history-age-ms ms to consider it a potential circling
|
||||||
|
;; aircraft.
|
||||||
|
:curviness-threshold-degrees 1440
|
||||||
|
;; If the centroid of the aircraft's positions is less than this
|
||||||
|
;; close to an airport, then it's probably just doinf flight
|
||||||
|
;; training.
|
||||||
|
:minimum-airport-distance-km 2.5
|
||||||
|
:history-db-path "advisory-circular.db"
|
||||||
|
:twitter {:enabled? true}})
|
||||||
|
|
||||||
|
|
||||||
(defn build-config [secrets commander]
|
(defn build-config-from-commander [commander]
|
||||||
(-> (merge-with merge
|
(cond-> {}
|
||||||
secrets
|
(.-adsbxUrl commander)
|
||||||
{:adsbx {:url (.-adsbxUrl commander)}}
|
(assoc-in [:adsbx :url] (.-adsbxUrl commander))
|
||||||
{:twitter {:enabled? (.-tweeting commander)}}
|
;; Note that we're distinguishing from the situation where
|
||||||
{:pelias {:url (.-peliasUrl commander)}})
|
;; --tweeting or --no-tweeting is supplied, in which case the
|
||||||
(assoc :basestation-sqb (.-basestationSqb commander)
|
;; tweeting field will be true or false, from the situation where
|
||||||
:lat (.-lat commander)
|
;; it's not suppled, in which case it will be undefined/nil.
|
||||||
:lon (.-lon commander)
|
(not (nil? (.-tweeting commander)))
|
||||||
:radius-nm (.-radius commander))))
|
(assoc-in [:twitter :enabled?] (.-tweeting commander))
|
||||||
|
(.-peliasUrl commander)
|
||||||
|
(assoc-in [:pelias :url] (.-peliasUrl commander))
|
||||||
|
(.-basestationSqb commander)
|
||||||
|
(assoc :basestation-sqb (.-basestationSqb commander))
|
||||||
|
(.-lat commander)
|
||||||
|
(assoc :lat (.-lat commander))
|
||||||
|
(.-lon commander)
|
||||||
|
(assoc :lon (.-lon commander))
|
||||||
|
(.-radius commander)
|
||||||
|
(assoc :radius (.-radius commander))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn build-config [config cli-config secrets]
|
||||||
|
(util/deep-merge default-config config cli-config secrets))
|
||||||
|
|
||||||
(defn main [& args]
|
(defn main [& args]
|
||||||
(-> commander
|
(-> commander
|
||||||
(.requiredOption "--lat <lat>" "Latitude of the circle of region of interest" parse-number)
|
(.option "--lat <lat>" "Latitude of the circle of region of interest" parse-number)
|
||||||
(.requiredOption "--lon <lat>" "Longitude of the circle of the region of interest" parse-number)
|
(.option "--lon <lat>" "Longitude of the circle of the region of interest" parse-number)
|
||||||
(.requiredOption "--adsbx-url <url>" "ADSBX API url")
|
(.option "--adsbx-url <url>" "ADSBX API url")
|
||||||
(.requiredOption "--pelias-url <url>" "Base pelias geocoder URL")
|
(.option "--pelias-url <url>" "Base pelias geocoder URL")
|
||||||
(.option "--radius <radius>" "Radius of the circle of interest, in nautical miles" 20 parse-number)
|
(.option "--radius <radius>" "Radius of the circle of interest, in km" parse-number)
|
||||||
(.option "--basestation-sqb <path>" "Path to a basestation.sqb database file")
|
(.option "--basestation-sqb <path>" "Path to a basestation.sqb database file")
|
||||||
(.option "--no-tweeting" "Do not tweet.")
|
(.option "--tweeting" "Enables tweeting")
|
||||||
|
(.option "--no-tweeting" "Do not tweet")
|
||||||
|
(.option "--config <path>" "Path to the configuration yaml file" "config.yaml")
|
||||||
|
(.option "--secrets <path>" "Path to the secrets yaml file" "secrets.yaml")
|
||||||
(.parse (.-argv js/process)))
|
(.parse (.-argv js/process)))
|
||||||
(let [start-time (current-time)]
|
(let [start-time (current-time)]
|
||||||
(p/then (p/all [(read-history-db history-db-path)
|
(p/let [base-config (util/read-config (.-config commander))
|
||||||
(util/read-config secrets-path)])
|
cli-config (build-config-from-commander commander)
|
||||||
(fn [[db secrets]]
|
secrets (util/read-config (.-secrets commander))
|
||||||
(p/let [config (build-config secrets commander)
|
config (build-config base-config cli-config secrets)
|
||||||
|
_1 (pprint/pprint base-config)
|
||||||
|
_2 (pprint/pprint cli-config)
|
||||||
|
_3 (pprint/pprint config)
|
||||||
|
db (read-history-db (:history-db-path config))
|
||||||
data (get-adsbexchange-live-data
|
data (get-adsbexchange-live-data
|
||||||
{:url (get-in config [:adsbx :url])
|
{:url (get-in config [:adsbx :url])
|
||||||
:api-key (get-in config [:adsbx :api-key])
|
:api-key (get-in config [:adsbx :api-key])
|
||||||
:lat (:lat config)
|
:lat (:lat config)
|
||||||
:lon (:lon config)
|
:lon (:lon config)
|
||||||
:radius-nm (:radius-nm config)})
|
:radius-nm (* (:radius-km config) 0.539957)})
|
||||||
now (current-time)
|
now (current-time)
|
||||||
[new-db potential-circles] (-> db
|
[new-db potential-circles] (-> db
|
||||||
(update-history-db (:aircraft data) now)
|
(update-history-db (:aircraft data) now config)
|
||||||
(detect-circles now))]
|
(detect-circles now config))]
|
||||||
(p/do
|
(p/do
|
||||||
(when potential-circles
|
(when potential-circles
|
||||||
(doseq [ac potential-circles]
|
(doseq [ac potential-circles]
|
||||||
(log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac)))
|
(log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac)))
|
||||||
(process-potential-circles potential-circles config now))
|
(process-potential-circles potential-circles config now))
|
||||||
(write-history-db new-db history-db-path)
|
(write-history-db new-db (:history-db-path config))
|
||||||
(let [end-time (current-time)]
|
(let [end-time (current-time)]
|
||||||
(log-info
|
(log-info
|
||||||
"Completed processing in %f seconds: tracking %s aircraft; %s potential circles"
|
"Completed processing in %f seconds: tracking %s aircraft; %s potential circles"
|
||||||
(/ (- end-time start-time) 1000)
|
(/ (- end-time start-time) 1000)
|
||||||
(count new-db)
|
(count new-db)
|
||||||
(count potential-circles)))))))))
|
(count potential-circles)))))))
|
||||||
|
@ -17,13 +17,15 @@
|
|||||||
(.readFile fs-promises path (clj->js options)))
|
(.readFile fs-promises path (clj->js options)))
|
||||||
|
|
||||||
|
|
||||||
;; Writes a file, returns a promise that resolves to no arguments on success.
|
;; Writes a file, returns a promise that resolves to no arguments on
|
||||||
|
;; success.
|
||||||
|
|
||||||
(defn write-file [path data options]
|
(defn write-file [path data options]
|
||||||
(.writeFile fs-promises path data (clj->js options)))
|
(.writeFile fs-promises path data (clj->js options)))
|
||||||
|
|
||||||
|
|
||||||
;; Reads a YAML config file. Returns a promise that resolves to the parsed YAML.
|
;; Reads a YAML config file. Returns a promise that resolves to the
|
||||||
|
;; parsed YAML.
|
||||||
|
|
||||||
(defn read-config [path]
|
(defn read-config [path]
|
||||||
(log-verbose "Reading config file %s" path)
|
(log-verbose "Reading config file %s" path)
|
||||||
@ -48,3 +50,19 @@
|
|||||||
(p/let [result (.get request
|
(p/let [result (.get request
|
||||||
(clj->js (merge {:url (str url) :gzip true} options)))]
|
(clj->js (merge {:url (str url) :gzip true} options)))]
|
||||||
result)))))
|
result)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; From https://github.com/puppetlabs/clj-kitchensink/blob/cfea4a16e4d2e15a2d391131a163b4eeb60d872e/src/puppetlabs/kitchensink/core.clj#L311-L332
|
||||||
|
|
||||||
|
(defn deep-merge
|
||||||
|
"Deeply merges maps so that nested maps are combined rather than replaced.
|
||||||
|
For example:
|
||||||
|
(deep-merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
|
||||||
|
;;=> {:foo {:bar :baz, :fuzz :buzz}}
|
||||||
|
;; contrast with clojure.core/merge
|
||||||
|
(merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
|
||||||
|
;;=> {:foo {:fuzz :quzz}} ; note how last value for :foo wins"
|
||||||
|
[& vs]
|
||||||
|
(if (every? map? vs)
|
||||||
|
(apply merge-with deep-merge vs)
|
||||||
|
(last vs)))
|
||||||
|
10
src/test/lemondronor/circlebot/util_test.cljs
Normal file
10
src/test/lemondronor/circlebot/util_test.cljs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
(ns lemondronor.circlebot.util-test
|
||||||
|
(:require [cljs.test :refer (deftest is testing)]
|
||||||
|
[lemondronor.circlebot.util :as util]))
|
||||||
|
|
||||||
|
(deftest deep-merge
|
||||||
|
(is (= (util/deep-merge
|
||||||
|
{:adsbx {:url "http://bar"}}
|
||||||
|
{:adsbx {:url "http://foo"}}
|
||||||
|
{:adsbx {:secret "123"}})
|
||||||
|
{:adsbx {:url "http://foo", :secret "123"}})))
|
Loading…
Reference in New Issue
Block a user