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
|
||||
"lat" lat
|
||||
"lon" lon
|
||||
"dist" radius-nm]
|
||||
"dist" (.toFixed radius-nm 1)]
|
||||
(map str)
|
||||
(string/join "/"))]
|
||||
(p/let [http-result (util/http-get url {:headers {:api-auth api-key}})]
|
||||
@ -69,15 +69,10 @@
|
||||
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.
|
||||
|
||||
(defn prune-history [history now]
|
||||
(let [h (filterv #(< (- now (:time %)) max-history-age-ms) history)]
|
||||
(defn prune-history [history now config]
|
||||
(let [h (filterv #(< (- now (:time %)) (:max-history-age-ms config)) history)]
|
||||
h))
|
||||
|
||||
|
||||
@ -109,9 +104,9 @@
|
||||
updated-db))
|
||||
|
||||
|
||||
(defn prune-histories [db now]
|
||||
(defn prune-histories [db now config]
|
||||
(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))
|
||||
|
||||
@ -144,10 +139,10 @@
|
||||
(first args))
|
||||
|
||||
|
||||
(defn update-history-db [db new-data now]
|
||||
(defn update-history-db [db new-data now config]
|
||||
(-> db
|
||||
(update-history-db-add-new-data new-data now)
|
||||
(prune-histories now)
|
||||
(prune-histories now config)
|
||||
(prune-records now)))
|
||||
|
||||
|
||||
@ -170,12 +165,6 @@
|
||||
(/ (.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]
|
||||
(str (:icao ac) " " (:lat ac) " " (:lon ac)
|
||||
" #" (:registration ac) " " (:alt ac) " " (:curviness ac) " "
|
||||
@ -194,15 +183,15 @@
|
||||
image-path))
|
||||
|
||||
|
||||
(defn circling? [ac]
|
||||
(and (> (geo/flight-curviness (:history ac)) curviness-threshold-degrees)
|
||||
(defn circling? [ac config]
|
||||
(and (> (geo/flight-curviness (:history ac)) (:curviness-threshold-degrees config))
|
||||
(> (:alt ac) 300)))
|
||||
|
||||
|
||||
;; Returns a vector of two elements,
|
||||
;; [updated-database potentially-circling-aircraft]
|
||||
|
||||
(defn detect-circles [db now]
|
||||
(defn detect-circles [db now config]
|
||||
(log-verbose "Detecting circles")
|
||||
(loop [old-db (seq db)
|
||||
new-db {}
|
||||
@ -213,7 +202,7 @@
|
||||
ac (assoc ac
|
||||
:curviness curviness
|
||||
:normalized-curviness (geo/flight-normalized-curviness (:history ac)))
|
||||
currently-circling? (circling? ac)
|
||||
currently-circling? (circling? ac config)
|
||||
previously-circling? (:started-circling-time ac)]
|
||||
(cond
|
||||
(and currently-circling?
|
||||
@ -371,11 +360,6 @@
|
||||
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]
|
||||
(p/let [icao (:icao ac)
|
||||
recent-positions (recent-history (:history ac))
|
||||
@ -393,11 +377,11 @@
|
||||
(log-info "%s: Closest airport is %s, distance: %s km"
|
||||
(:icao ac) (:label airport-properties) (:distance airport-properties))
|
||||
(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"
|
||||
(:icao ac)
|
||||
(:distance airport-properties)
|
||||
minimum-airport-distance-km
|
||||
(:minimum-airport-distance-km config)
|
||||
(:label airport-properties)
|
||||
())
|
||||
(do
|
||||
@ -458,55 +442,88 @@
|
||||
(p/recur (rest acs))))))
|
||||
|
||||
|
||||
(def history-db-path "advisory-circular.db")
|
||||
(def secrets-path "secrets.yaml")
|
||||
(def default-config
|
||||
{
|
||||
;; 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]
|
||||
(-> (merge-with merge
|
||||
secrets
|
||||
{:adsbx {:url (.-adsbxUrl commander)}}
|
||||
{:twitter {:enabled? (.-tweeting commander)}}
|
||||
{:pelias {:url (.-peliasUrl commander)}})
|
||||
(assoc :basestation-sqb (.-basestationSqb commander)
|
||||
:lat (.-lat commander)
|
||||
:lon (.-lon commander)
|
||||
:radius-nm (.-radius commander))))
|
||||
(defn build-config-from-commander [commander]
|
||||
(cond-> {}
|
||||
(.-adsbxUrl commander)
|
||||
(assoc-in [:adsbx :url] (.-adsbxUrl commander))
|
||||
;; Note that we're distinguishing from the situation where
|
||||
;; --tweeting or --no-tweeting is supplied, in which case the
|
||||
;; tweeting field will be true or false, from the situation where
|
||||
;; it's not suppled, in which case it will be undefined/nil.
|
||||
(not (nil? (.-tweeting 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]
|
||||
(-> commander
|
||||
(.requiredOption "--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)
|
||||
(.requiredOption "--adsbx-url <url>" "ADSBX API url")
|
||||
(.requiredOption "--pelias-url <url>" "Base pelias geocoder URL")
|
||||
(.option "--radius <radius>" "Radius of the circle of interest, in nautical miles" 20 parse-number)
|
||||
(.option "--lat <lat>" "Latitude of the circle of region of interest" parse-number)
|
||||
(.option "--lon <lat>" "Longitude of the circle of the region of interest" parse-number)
|
||||
(.option "--adsbx-url <url>" "ADSBX API url")
|
||||
(.option "--pelias-url <url>" "Base pelias geocoder URL")
|
||||
(.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 "--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)))
|
||||
(let [start-time (current-time)]
|
||||
(p/then (p/all [(read-history-db history-db-path)
|
||||
(util/read-config secrets-path)])
|
||||
(fn [[db secrets]]
|
||||
(p/let [config (build-config secrets commander)
|
||||
data (get-adsbexchange-live-data
|
||||
{:url (get-in config [:adsbx :url])
|
||||
:api-key (get-in config [:adsbx :api-key])
|
||||
:lat (:lat config)
|
||||
:lon (:lon config)
|
||||
:radius-nm (:radius-nm config)})
|
||||
now (current-time)
|
||||
[new-db potential-circles] (-> db
|
||||
(update-history-db (:aircraft data) now)
|
||||
(detect-circles now))]
|
||||
(p/do
|
||||
(when potential-circles
|
||||
(doseq [ac potential-circles]
|
||||
(log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac)))
|
||||
(process-potential-circles potential-circles config now))
|
||||
(write-history-db new-db history-db-path)
|
||||
(p/let [base-config (util/read-config (.-config commander))
|
||||
cli-config (build-config-from-commander commander)
|
||||
secrets (util/read-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
|
||||
{:url (get-in config [:adsbx :url])
|
||||
:api-key (get-in config [:adsbx :api-key])
|
||||
:lat (:lat config)
|
||||
:lon (:lon config)
|
||||
:radius-nm (* (:radius-km config) 0.539957)})
|
||||
now (current-time)
|
||||
[new-db potential-circles] (-> db
|
||||
(update-history-db (:aircraft data) now config)
|
||||
(detect-circles now config))]
|
||||
(p/do
|
||||
(when potential-circles
|
||||
(doseq [ac potential-circles]
|
||||
(log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac)))
|
||||
(process-potential-circles potential-circles config now))
|
||||
(write-history-db new-db (:history-db-path config))
|
||||
(let [end-time (current-time)]
|
||||
(log-info
|
||||
"Completed processing in %f seconds: tracking %s aircraft; %s potential circles"
|
||||
(/ (- end-time start-time) 1000)
|
||||
(count new-db)
|
||||
(count potential-circles)))))))))
|
||||
(count potential-circles)))))))
|
||||
|
@ -17,13 +17,15 @@
|
||||
(.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]
|
||||
(.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]
|
||||
(log-verbose "Reading config file %s" path)
|
||||
@ -48,3 +50,19 @@
|
||||
(p/let [result (.get request
|
||||
(clj->js (merge {:url (str url) :gzip true} options)))]
|
||||
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