import std/[httpclient, os, sequtils, sugar, strutils, xmlparser, xmltree] import Tables import strformat import NimQml import macros import typeinfo 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"))) type AirportRoles {.pure.} = enum RawMetar = UserRole + 1 FlightCategory = UserRole + 2 StationId = UserRole + 3 proc readConfig(): seq[string] = let metarConfigDir = getConfigDir() & "/metarweather" let metarConf = metarConfigDir & "/metarweather.conf" if not dirExists(metarConfigDir): createDir(metarConfigDir) if not fileExists(metarConf): return @[] let config = readFile(metarConf) if config.isEmptyOrWhiteSpace: return @[] return config.strip().split(",") proc writeConfig(airports: seq[string]) = let config = airports.join(",") let metarConfigDir = getConfigDir() & "/metarweather" let metarConf = metarConfigDir & "/metarweather.conf" writeFile(metarConf, config) proc getMetars(airports: seq[string]): seq[MetarData] = let client = newHttpClient() airports.map(airportCode => client.getMetar(airportCode)).map(metarXml => newMetarData(metarXml)) QtObject: type MetarList* = ref object of QAbstractListModel airports*: seq[MetarData] proc delete(self: MetarList) = self.QAbstractListModel.delete proc setup(self: MetarList) = self.QAbstractListModel.setup proc newMetarList*(airports: seq[MetarData]): MetarList = new(result, delete) result.airports = airports result.setup method rowCount(self: MetarList, index: QModelIndex = nil): int = self.airports.len method data(self: MetarList, index: QModelIndex, role: int): QVariant = if not index.isValid: return if index.row < 0 or index.row >= self.airports.len: return let airport = self.airports[index.row] let airportRole = role.AirportRoles case airportRole: of AirportRoles.RawMetar: result = newQVariant(airport.rawText) of AirportRoles.FlightCategory: result = newQVariant(airport.flightCategory) of AirportRoles.StationId: result = newQVariant(airport.stationId) method roleNames(self: MetarList): Table[int, string] = { AirportRoles.RawMetar.int:"rawMetar", AirportRoles.FlightCategory.int:"flightCategory", AirportRoles.StationId.int:"stationId"}.toTable QtObject: type ApplicationLogic* = ref object of QObject app: QApplication metarList: MetarList proc delete*(self: ApplicationLogic) = self.QObject.delete self.metarList.delete proc setup(self: ApplicationLogic) = self.QObject.setup proc getMetarList(self: ApplicationLogic): QVariant {.slot.} = return newQVariant(self.metarList) proc metarListChanged(self: ApplicationLogic, metarList: QVariant) {.signal.} proc newApplicationLogic*(app: QApplication, airports: seq[MetarData]): ApplicationLogic = new(result, delete) result.app = app result.metarList = newMetarList(airports) result.setup() proc refresh(self: ApplicationLogic) {.slot.} = let airports = self.metarList.airports.map(metar => metar.stationId) self.metarList.delete self.metarList = newMetarList(airports.getMetars()) self.metarListChanged(newQVariant(self.metarList)) proc addAirport(self: ApplicationLogic, code: string) {.slot.} = let airports = self.metarList.airports.map(metar => metar.stationId) & code writeConfig(airports) self.metarList.delete self.metarList = newMetarList(airports.getMetars()) self.metarListChanged(newQVariant(self.metarList)) proc deleteAirport(self: ApplicationLogic, code: string) {.slot.} = let airports = self.metarList.airports.map(metar => metar.stationId) .filter(stationId => stationId != code) writeConfig(airports) self.metarList.delete self.metarList = newMetarList(airports.getMetars()) self.metarListChanged(newQVariant(self.metarList)) QtProperty[QVariant] metarList: read = getMetarList notify = metarListChanged proc mainProc() = let app = newQApplication() defer: app.delete() let engine = newQQmlApplicationEngine() defer: engine.delete() let airports = readConfig() let metarData = getMetars(airports) let logic = newApplicationLogic(app, metarData) let logicVariant = newQVariant(logic) engine.setRootContextProperty("logic", logicVariant) let appDirPath = app.applicationDirPath & "/" & "metar.rcc" QResource.registerResource(appDirPath) engine.load(newQUrl("qrc:///main.qml")) app.exec() when isMainModule: mainProc() GC_fullcollect()