Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature blt and ostwind #22

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@
[bk/ring-gzip "0.1.1"]
[secretary "1.2.3"]
[org.clojure/data.zip "0.1.1"]
[clj-luhn "0.1.3"]
[org.clojars.jws/ring-etag-middleware "0.1.2-SNAPSHOT"]
[com.newrelic.agent.java/newrelic-api "3.13.0"]
[org.clojure/java.jdbc "0.4.2"]
[org.xerial/sqlite-jdbc "3.7.15-M1"]
[digest "1.4.4"]]

:plugins [[lein-cljsbuild "1.0.4"]
[lein-less "1.7.5"]
[lein-shell "0.4.0"]]

:uberjar-name "tramboard-clj.jar"
:source-paths ["src/clj"]
:cljsbuild {:builds {:app {:source-paths ["src/cljs"]}}}
:less {:source-paths ["src/less"]
:target-path "resources/public/css"}

:profiles {:dev {:source-paths ["env/dev/src/clj"]
:dependencies [[figwheel-sidecar "0.3.3"]
Expand Down
24 changes: 24 additions & 0 deletions src/clj/tramboard_clj/api/blt.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(ns tramboard-clj.api.blt
(:require [cheshire.core :as json]
[ring.util.codec :as codec]
[clj-time.core :as t]
[clj-time.format :as f]
[clojure.string :as str]
[org.httpkit.client :as http]
[clojure.tools.html-utils :as html]
[tramboard-clj.api.wml :as wml]
))

;the code is in blt.clj

(def query-stations-base-url "http://online.fahrplan.zvv.ch/bin/ajax-getstop.exe/dny?start=1&tpl=suggest2json&REQ0JourneyStopsS0A=7&getstop=1&noSession=yes&REQ0JourneyStopsB=25&REQ0JourneyStopsS0G=")
(defn station-base-url [id]
(str "http://data.wemlin.com/rest/v0/networks/blt/stations/DI-0000" id "/" (f/unparse wml/wml-formatter (t/now)) "/" (f/unparse wml/wml-formatter (t/plus (t/now) (t/hours 2)))))

; TODO error handling

(defn station [id sbbid]
(wml/station id sbbid station-base-url))

(defn query-stations [query]
(wml/query-stations query query-stations-base-url))
51 changes: 51 additions & 0 deletions src/clj/tramboard_clj/api/common.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
(ns tramboard-clj.api.common
(:require [ring.util.codec :as codec]
[clj-time.core :as t]
[clj-time.format :as f]
[cheshire.core :as json]
[org.httpkit.client :as http]
[clojure.xml :as xml]
[clojure.zip :as zip]
[clojure.data.zip.xml :refer [text xml-> xml1->]]
[tramboard-clj.api.zvv :as zvv]
))


; this merges the two results from SBB and VBL
; If SBB has realtime, take that
; Maybe way too complicated...
(defn- hash-realtime-data [dept get-hash]
(let [departure (dept :departure)]
{(keyword (get-hash dept)) dept}))

(defn- combine-platform [x sbbentry]
(if (and (not (nil? sbbentry)) (not (nil? (sbbentry :platform))) (nil? (x :platform)))
(assoc-in x [:platform] (sbbentry :platform))
x))

(defn- get-new-hashmap [main sbbhashmap get-hash]
(into {} (for [x (main :departures)
:let [sbbentry (sbbhashmap (keyword (get-hash x)))]]
(if (or (nil? sbbentry) (nil? ((sbbentry :departure) :realtime)))
{(keyword (get-hash x)) (combine-platform x sbbentry)}
{(keyword (get-hash x)) sbbentry}))))

(defn combine-results [main sbb get-hash]
(let [sbbhashmap (into {} (map #(hash-realtime-data % get-hash) (sbb :departures)))
newhashmap (get-new-hashmap main sbbhashmap get-hash)
meta (sbb :meta)
]
{:meta (if (nil? meta) (main :meta) meta)
:departures (map #(dissoc % :dt) (sort-by (juxt :dt :name) (vals (merge sbbhashmap newhashmap))) )}))


; TODO error handling
(defn do-api-call2 [url transform-fn url2 transform-fn2 get-hash]
(let [response (http/get url {:socket-timeout 2000 :conn-timeout 3000})
response2 (http/get url2)]
(combine-results (if (= 200 (:status @response)) (transform-fn (:body @response)) {:error (:status @response)}) (transform-fn2 (:body @response2)) get-hash)))

(defn do-api-call [url transform-fn]
(let [response (http/get url)]
(transform-fn (:body @response))))

25 changes: 25 additions & 0 deletions src/clj/tramboard_clj/api/ost.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(ns tramboard-clj.api.ost
(:require [cheshire.core :as json]
[ring.util.codec :as codec]
[clj-time.core :as t]
[clj-time.format :as f]
[clojure.string :as str]
[org.httpkit.client :as http]
[clojure.tools.html-utils :as html]
[tramboard-clj.api.wml :as wml]

))

;the code is in blt.clj

(def query-stations-base-url "http://online.fahrplan.zvv.ch/bin/ajax-getstop.exe/dny?start=1&tpl=suggest2json&REQ0JourneyStopsS0A=7&getstop=1&noSession=yes&REQ0JourneyStopsB=25&REQ0JourneyStopsS0G=")
(defn station-base-url [id]
(str "http://data.wemlin.com/rest/v0/networks/ostwind/stations/DI-0000" id "/" (f/unparse wml/wml-formatter (t/now)) "/" (f/unparse wml/wml-formatter (t/plus (t/now) (t/hours 2)))))

; TODO error handling

(defn station [id sbbid]
(wml/station id sbbid station-base-url))

(defn query-stations [query]
(wml/query-stations query query-stations-base-url))
47 changes: 5 additions & 42 deletions src/clj/tramboard_clj/api/vblbvb.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
[clojure.xml :as xml]
[clojure.zip :as zip]
[clojure.data.zip.xml :refer [text xml-> xml1->]]
[tramboard-clj.api.zvv :as zvv]))
[tramboard-clj.api.zvv :as zvv]
[tramboard-clj.api.common :as c]))

(def vbl-timezone (t/time-zone-for-id "Europe/Zurich"))
(def vbl-date-formatter (f/with-zone (f/formatter "dd.MM.YYYYHH:mm") vbl-timezone))
Expand Down Expand Up @@ -100,50 +101,12 @@
(let [stations (zip-str (clojure.string/replace response-body "encoding=\"ISO-8859-1\"" ""))]
{:stations (map vbl-station (xml-> stations :sf :p))}))

(defn- hash-realtime-data [dept get-hash]
(let [departure (dept :departure)]
{(keyword (get-hash dept)) dept}))

; this merges the two results from SBB and VBL
; If SBB has realtime, take that
; Maybe way too complicated...

(defn- combine-platform [x sbbentry]
(if (and (not (nil? sbbentry)) (not (nil? (sbbentry :platform))) (nil? (x :platform)))
(assoc-in x [:platform] (sbbentry :platform))
x))

(defn- get-new-hashmap [main sbbhashmap get-hash]
(into {} (for [x (main :departures)
:let [sbbentry (sbbhashmap (keyword (get-hash x)))]]
(if (or (nil? sbbentry) (nil? ((sbbentry :departure) :realtime)))
{(keyword (get-hash x)) (combine-platform x sbbentry)}
{(keyword (get-hash x)) sbbentry}))))

(defn- combine-results [main sbb get-hash]
(let [sbbhashmap (into {} (map #(hash-realtime-data % get-hash) (sbb :departures)))
newhashmap (get-new-hashmap main sbbhashmap get-hash)
meta (sbb :meta)
]
{:meta (if (nil? meta) (main :meta) meta)
:departures (map #(dissoc % :dt) (sort-by (juxt :dt :name) (vals (merge sbbhashmap newhashmap))) )}))

; TODO error handling
(defn- do-api-call2 [url transform-fn url2 transform-fn2 get-hash]
(let [response (http/get url {:socket-timeout 2000 :conn-timeout 3000})
response2 (http/get url2)]
(combine-results (if (= 200 (:status @response)) (transform-fn (:body @response)) {:error (:status @response)}) (transform-fn2 (:body @response2)) get-hash)))

(defn- do-api-call [url transform-fn]
(let [response (http/get url)]
(transform-fn (:body @response))))

; TODO error handling
(defn station [id sbbid request-url get-hash]
(let [request-url-sbb (str zvv/station-base-url sbbid)]
(if (nil? sbbid)
(do-api-call request-url transform-station-response)
(do-api-call2 request-url transform-station-response request-url-sbb (zvv/transform-station-response sbbid) get-hash))))
(c/do-api-call request-url transform-station-response)
(c/do-api-call2 request-url transform-station-response request-url-sbb (zvv/transform-station-response sbbid) get-hash))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we name that differently ? :)


(defn query-stations [query request-url]
(do-api-call request-url transform-query-stations-response))
(c/do-api-call request-url transform-query-stations-response))
169 changes: 169 additions & 0 deletions src/clj/tramboard_clj/api/wml.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
(ns tramboard-clj.api.wml
(:require [cheshire.core :as json]
[ring.util.codec :as codec]
[clj-time.core :as t]
[clj-time.format :as f]
[clojure.string :as str]
[org.httpkit.client :as http]
[tramboard-clj.api.zvv :as zvv]
[tramboard-clj.api.common :as c]
[digest]
))

; if the hash making fails due to too different names, fix it here
(defn map-station-name [text]
(case text
"Zürich, Zürich HB" "Zürich HB"
"Zürich, Flughafen" "Zürich Flughafen"
"St. Gallen AB, Bahnhof AB" "St. Gallen AB"
"Pfäffikon SZ, Pfäffikon" "Pfäffikon SZ"
text))

(defn- map-category [text]
(case text
"NFB" "bus"
"3" "bus"
"VBSG" "bus"
"train"))


(def wml-timezone (t/time-zone-for-id "Europe/Zurich"))
(def wml-formatter (f/with-zone (f/formatter "yyyyMMdd'T'HHmm") wml-timezone))

(defn- get-hash [dept]
(str "t" (digest/md5 (str (clojure.string/replace (dept :to) ", Bahnhof" "") (dept :name)((dept :departure) :scheduled)))))

(def wml-date-formatter (f/with-zone (f/formatter "yyyyMMdd'T'HHmmss") wml-timezone))

(defn- wml-parse-datetime [timestamp]
(if (nil? timestamp)
nil
(str (-> (f/parse wml-date-formatter timestamp) .secondOfMinute .withMinimumValue))))

(defn hexy
"This will convert the bytes to CSS RGB Value"
([x] (hexy (nth x 0) (nth x 1) (nth x 2)))
([r g b] (reduce str
(cons "#"
(map #(format "%02X" (bit-and 0xFF %)) [r g b])))))

; calculate luhn number for the ID call
(defn str->ints [s]
(map #(Character/digit % 10) s))

(defn double-every-second [coll]
(map #(%1 %2)
(cycle [identity #(* % 2)])
(reverse coll)))

(defn ints->digits [coll]
(mapcat #(str->ints (str %)) coll))

(def add (partial reduce +))

(defn luhny? [s]
(-> s
str->ints
double-every-second
ints->digits
add
(mod 10)
))

(defn- add-luhny-digit [s]
(let [ diff (luhny? (str s "0"))
digit (if (= diff 0) diff (- 10 diff))
]
(str s "-" digit)
)
)

(defn- format-date [date time]
(str (f/parse wml-date-formatter (str date " " time))))

(defn- format-place [data]
(let [place (data "place")
name (data "name")
splitname (str/split name , #" ", 2)]
(if (= (splitname 0) place)
(if (> (count splitname) 1)
(str place ", " (splitname 1))
place)
(str place ", " name))))


; TODO add 1 day to realtime if it is smaller than scheduled (scheduled 23h59 + 3min delay ...)
(defn- wml-departure [wml-journey]
(let [product (wml-journey "line")
color (product "colors")
colorfg (color "fg")
colorbg (color "bg")
platform (wml-journey "platform")
line (or (product "line") (product "name"))
attributes-bfr (wml-journey "attributes_bfr")
timestamprt (if (true? (wml-journey "real_time"))
(wml-parse-datetime (wml-journey "iso8601_real_time_sec"))
nil
)
timestamp (wml-parse-datetime (wml-journey "iso8601_time_sec"))]
{:name (product "line_name")
:type (map-category (or (product "transportMapping") ((product "agency") "id")))
:accessible (not (empty? (filter #(contains? #{"6" "9"} (% "code")) attributes-bfr)))
:colors {:fg (hexy colorfg)
:bg (hexy colorbg)
}
:to (->> (clojure.string/replace (format-place (wml-journey "end_station")) "St.Gallen" "St. Gallen")
(map-station-name)
)
:platform (if (= platform "") nil platform)
:dt (or timestamprt timestamp)
:departure {:scheduled timestamp
:realtime timestamprt
}}))

; TODO tests (=> capture some data from blt api)
(defn- transform-station-response [id]
(fn [response-body]
(let [data (json/parse-string response-body)]
{:meta {:station_id id
:station_name (format-place data)}
:departures (map wml-departure (data "departures"))})))

(defn- to-coordinate [string]
(if (nil? string) nil
(double (/ (read-string string) 1000000))))

(defn- wml-station [wml-station]
(let [id nil]
{:id (wml-station "extId")
:name (wml-station "value")
:location {:lat (to-coordinate (wml-station "ycoord")) :lng (to-coordinate (wml-station "xcoord"))}}))

(defn- transform-query-stations-response [response-body]
(let [unparsed (reduce #(clojure.string/replace-first %1 %2 "") response-body [";SLs.showSuggestion();" "SLs.sls="])
data (json/parse-string unparsed)
stations (data "suggestions")]
{:stations (map wml-station (remove #(or (nil? (% "extId")) (not= "1" (% "type"))) stations))}))

; TODO error handling
(defn- do-api-call [url transform-fn]
(let [response (http/get url)]
(transform-fn (:body @response))))

(defn- replaceholder [input placeholder replace]
(clojure.string/replace input (str "{{" placeholder "}}") replace))

; TODO error handling
(defn station [id sbbid station-base-url]
(let [stripped_id (clojure.string/replace id #"^0*" "")
newid (subs stripped_id 2 (count stripped_id))
request-url (station-base-url (add-luhny-digit newid))
request-url-sbb (str zvv/station-base-url sbbid)]
(if (nil? sbbid)
(c/do-api-call request-url (transform-station-response id))
(c/do-api-call2 request-url (transform-station-response id) request-url-sbb (zvv/transform-station-response sbbid) get-hash))))

(defn query-stations [query query-stations-base-url]
(let [request-url (str query-stations-base-url (codec/url-encode query))]
(c/do-api-call request-url transform-query-stations-response)))

3 changes: 2 additions & 1 deletion src/clj/tramboard_clj/core/views.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
[tramboard-clj.api.zvv]
[tramboard-clj.api.vbl]
[tramboard-clj.api.bvb]

[tramboard-clj.api.blt]
[tramboard-clj.api.ost]
[clojure.java.jdbc :refer :all]
)
(:import com.newrelic.api.agent.Trace))
Expand Down
10 changes: 8 additions & 2 deletions src/cljs/tramboard_clj/script/util.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
(:require-macros [cljs.core.async.macros :refer [go]]))

(defonce locations
[{:id :ch_zh :name "Zurich City & Canton" :short-label "Zurich" :flag-class "ch_zh" :api "zvv" :active true}
[
{:id :ch :name "Whole Switzerland" :short-label "Switzerland" :flag-class "ch" :api "ch" :active true}
{:id :ch_zh :name "Zurich City & Canton" :short-label "Zurich" :flag-class "ch_zh" :api "zvv" :active true}
{:id :ch_ge :name "Geneva City & Canton" :short-label "Geneva" :flag-class "ch_ge" :api "gva" :active true}
{:id :ch_lu :name "Lucerne & parts of Central Switzerland" :short-label "Lucerne" :flag-class "ch_lu" :api "vbl" :active true}
{:id :ch_bs :name "Basel City" :short-label "Basel" :flag-class "ch_bs" :api "bvb" :active true}
{:id :ch :name "Rest of Switzerland" :short-label "Switzerland" :flag-class "ch" :api "ch" :active true}])
;{:id :ch_nw :name "Nordwestschweiz" :short-label "Nordwestschweiz" :flag-class "ch_bs" :api "blt" :active true}
;{:id :ch_os :name "Ostschweiz" :short-label "Ostschweiz" :flag-class "ch_os" :api "ost" :active true}
])



(defn get-location [location-id]
(let [found-locations (filter #(= location-id (:id %)) locations)]
Expand Down
Binary file modified stations.sqlite
Binary file not shown.