diff --git a/actransit.nim b/actransit.nim index ef77592..324d29e 100644 --- a/actransit.nim +++ b/actransit.nim @@ -14,3 +14,25 @@ proc format_predictions*(prd: JSONNode): string = "No prediction available" else: prd["prd"].getElems.map(time => time["prdctdn"].getStr).join(", ") + +proc home_predictions*: string = + let client = newHttpClient() + const home_nl = "51067" + const home_12 = "58995" + const home_29 = "56557" + + let nl_predictions = client.get_predictions(home_nl, "NL").format_predictions + let twelve_predictions = client.get_predictions(home_12, "12").format_predictions + let twenty_nine_predictions = client.get_predictions(home_29, "29").format_predictions + + &"Home\nNL: {nl_predictions}\n12: {twelve_predictions}\n29: {twenty_nine_predictions}" + +proc office_predictions*: string = + let client = newHttpClient() + const office_nl = "56565" + const office_12 = "57111" + + let nl_predictions = client.get_predictions(office_nl, "NL").format_predictions + let twelve_predictions = client.get_predictions(office_12, "12").format_predictions + + &"Oakland Office\nNL: {nl_predictions}\n12: {twelve_predictions}" diff --git a/metar.nim b/metar.nim new file mode 100644 index 0000000..28dae4b --- /dev/null +++ b/metar.nim @@ -0,0 +1,56 @@ +import std/[httpclient, os, sequtils, sugar, strutils, xmlparser, xmltree] +import strformat + +proc getMetar(client: HttpClient, code: string): XMLNode = + let requestUrl = &"https://aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString={code}" + let metarData = client.getContent(requestUrl) + parseXml(metarData) + +proc childString(node: XMLnode, name: string): string = + let node = node.child(name) + if node != nil: + result = node.innerText + else: + result = "" + +type SkyCondition* = object + skyCover: string + cloudBaseAgl: string + +type MetarData* = object + rawText*: string + stationId*: string + observationTime*: string + temperature*: string + dewPoint*: string + windHeading*: string + windSpeedKnots*: string + visibility*: string + altimiter*: string + flightCategory*: string + skyConditions*: seq[SkyCondition] + +proc newMetarData*(xmlData: XMLNode): MetarData = + let metar = xmlData.child("data").child("METAR") + + result = MetarData( + rawText: metar.childString("raw_text"), + stationId: metar.childString("station_id"), + observationTime: metar.childString("observation_time"), + temperature: metar.childString("temp_c"), + dewPoint: metar.childString("dewpoint_c"), + windHeading: metar.childString("wind_dir_degrees"), + visibility: metar.childString("visibility_statute_mi"), + altimiter: metar.childString("altim_in_hg"), + flightCategory: metar.childString("flight_category") + ) + + for skyConditionXml in metar.findAll("sky_condition"): + result.skyConditions.add( + SkyCondition( + skyCover: skyConditionXml.attr("sky_cover"), + cloudBaseAgl: skyconditionXml.attr("cloud_base_ft_agl"))) + +proc getMetars*(airports: seq[string]): seq[MetarData] = + let client = newHttpClient() + airports.map(airportCode => client.getMetar(airportCode)).map(metarXml => newMetarData(metarXml)) diff --git a/toolbox.nim b/toolbox.nim index cccc61f..e490903 100644 --- a/toolbox.nim +++ b/toolbox.nim @@ -1,47 +1,93 @@ import gintro/[gtk4, gobject, gio, adw] -import std/[httpclient, with] -import strformat +import std/[with] import actransit +import metar -proc home_predictions: string = - let client = newHttpClient() - const home_nl = "51067" - const home_12 = "58995" - const home_29 = "56557" +const airports = @["khwd", "koak", "klvk", "khaf"] - let nl_predictions = client.get_predictions(home_nl, "NL").format_predictions - let twelve_predictions = client.get_predictions(home_12, "12").format_predictions - let twenty_nine_predictions = client.get_predictions(home_29, "29").format_predictions +proc clearMetars(metar_list: gtk4.ListBox) = + for i in 1..airports.len: + let metar_row = metar_list.get_row_at_index(0) + metar_list.remove(metar_row) - &"Home\nNL: {nl_predictions}\n12: {twelve_predictions}\n29: {twenty_nine_predictions}" +proc addMetars(metar_list: gtk4.ListBox) = + let metars = getMetars(airports) -proc office_predictions: string = - let client = newHttpClient() - const office_nl = "56565" - const office_12 = "57111" + for metar in metars: + let metar_label = newLabel() + with metar_label: + text = metar.rawText.cstring + halign = Align.start + wrap = true + metar_list.append(metar_label) - let nl_predictions = client.get_predictions(office_nl, "NL").format_predictions - let twelve_predictions = client.get_predictions(office_12, "12").format_predictions - - &"Oakland Office\nNL: {nl_predictions}\n12: {twelve_predictions}" +proc button_refresh_signal(b: Button, + widgets: tuple[vs: adw.ViewStack, home: Label, office: Label, metar_list: gtk4.ListBox]) = + if widgets.vs.get_visible_child_name() == "act_view": + widgets.home.text = home_predictions().cstring + widgets.office.text = office_predictions().cstring + elif widgets.vs.get_visible_child_name() == "metar_view": + clearMetars(widgets.metar_list) + addMetars(widgets.metar_list) proc activate(app: adw.Application) = let window = adw.newApplicationWindow(app) + view_stack = adw.newViewStack() + switcher_bar = adw.newViewSwitcherBar() + + bart_box = newBox(Orientation.vertical, 0) + + metar_box = newBox(Orientation.vertical, 0) + metar_list = newListBox() + + act_box = newBox(Orientation.vertical, 0) home_label = newLabel() office_label = newLabel() refresh_button = newButtonFromIconName("view-refresh") header = adw.newHeaderBar() - mainBox = newBox(Orientation.vertical, 0) - with mainBox: + main_box = newBox(Orientation.vertical, 0) + + + with main_box: append header + append view_stack + append switcher_bar + + let + act_view_page = view_stack.add_titled(act_box, "act_view", "AC Transit") + bart_view_page = view_stack.add_titled(bart_box, "bart_view", "BART") + metar_view_page = view_stack.add_titled(metar_box, "metar_view", "Metar") + + act_view_page.set_icon_name("go-home") + bart_view_page.set_icon_name("view-grid") + metar_view_page.set_icon_name("weather-clear") + + with view_stack: + set_visible_child_name "act_view" + vexpand = true + + with switcher_bar: + reveal = true + stack = view_stack + valign = Align.end + + with act_box: append home_label append office_label + with metar_box: + append metar_list + with header: pack_end refresh_button + with metar_list: + selectionMode = SelectionMode.none + showSeparators = true + addMetars(metar_list) + with refresh_button: iconName = "view-refresh" @@ -51,6 +97,7 @@ proc activate(app: adw.Application) = marginBottom = 10 marginStart = 10 marginEnd = 10 + halign = Align.start with office_label: text = office_predictions().cstring @@ -58,17 +105,15 @@ proc activate(app: adw.Application) = marginBottom = 10 marginStart = 10 marginEnd = 10 + hexpand = true + halign = Align.start - proc button_refresh_signal(b: Button, labels: tuple[home: Label, office: Label]) = - labels.home.text = home_predictions().cstring - labels.office.text = office_predictions().cstring - - refresh_button.connect("clicked", button_refresh_signal, (home_label, office_label)) + refresh_button.connect("clicked", button_refresh_signal, (view_stack, home_label, office_label, metar_list)) with window: title = "Toolbox" defaultSize = (300, 500) - content = mainBox + content = main_box show() proc initAdw(app: adw.Application) =