{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} -- | -- Module: Data.Aviation.WX -- Copyright: (C) 2014-2016, Hans-Christian Esperer -- License: MIT -- Maintainer: Hans-Christian Esperer -- Stability: experimental -- Portability: portable -- -- Parse aviation weather reports. A qualified import is recommended. module Data.Aviation.WX( weatherParser , Weather(..) , HasWeather(..) , AsWeather(..) , Date(..) , HasDate(..) , Station(..) , Flag(..) , HasFlag(..) , AsFlag(..) , Wind(..) , HasWind(..) , Visibility(..) , HasVisibility(..) , AsVisibility(..) , Runway(..) , HasRunway(..) , AsRunway(..) , VisTrend(..) , HasVisTrend(..) , AsVisTrend(..) , RunwayCondition(..) , HasRunwayCondition(..) , AsRunwayCondition(..) , WeatherPhenomenon(..) , HasWeatherPhenomenon(..) , Cloud(..) , HasCloud(..) , AsCloud(..) , Pressure(..) , HasPressure(..) , AsPressure(..) , Trend(..) , HasTrend(..) , AsTrend(..) , WPDesc(..) , HasWPDesc(..) , AsWPDesc(..) , WPPrecipitation(..) , HasWPPrecipitation(..) , AsWPPrecipitation(..) , WPObfuscation(..) , HasWPObfuscation(..) , AsWPObfuscation(..) , WPOther(..) , HasWPOther(..) , AsWPOther(..) , Distance(..) , HasDistance(..) , AsDistance(..) , Direction(..) , HasDirection(..) , AsDirection(..) , RwyCoverType(..) , HasRwyCoverType(..) , AsRwyCoverType(..) , RunwayBraking(..) , HasRunwayBraking(..) , AsRunwayBraking(..) , Vertical(..) , HasVertical(..) , AsVertical(..) , WindDirection(..) , HasWindDirection(..) , AsWindDirection(..) , Cover(..) , HasCover(..) , AsCover(..) , CloudType(..) , HasCloudType(..) , AsCloudType(..) , WPIntensity(..) , HasWPIntensity(..) , AsWPIntensity(..) , Transition(..) , HasTransition(..) , AsTransition(..) , Unit(..) , HasUnit(..) , AsUnit(..) , ReportType(..) ) where import Control.Applicative(Alternative((<|>), some, many), optional) import Control.Lens(makeClassy, makeClassyPrisms, makeWrapped) import Control.Monad(when, void) import Data.Maybe(isNothing, catMaybes) import Data.Text(Text, pack) import Text.Parser.Char(CharParsing, space, spaces, char, satisfy, text, digit, anyChar) import Text.Parser.Combinators(try, option, choice, sepBy, sepBy1, count, unexpected) takeChars :: CharParsing f => Int -> f Text takeChars n = pack <$> count n anyChar data ReportType = MetarReport | TafReport deriving (Eq, Show, Enum) -- | Aviation weather, currently only METARs and TAFs are supported. data Weather = -- | A METeorological Aerodrome Report METAR { -- | The type of the report _reporttype :: ReportType , -- | The observation date. _metardate :: Date , -- | The designation of the observing station. _station :: Station , -- | A remark about the reported observation. _flags :: [Flag] , -- | The observed wind. _metarwind :: Maybe Wind , -- | The observed visibility. _metarvisibility :: [Visibility] , -- | The observed visibility for specific runways, -- usually reported if the runway visibility significantly -- differs from the general reported visibility. _runwayvis :: [(Runway, [Visibility], Maybe VisTrend)] , -- | Surface or close conditions of a specific runway. _runwaycond :: [RunwayCondition] , -- | Observed weather phenomena _wx :: [WeatherPhenomenon] , -- | Observed cloud layers _clouds :: [Cloud] , -- | Measured pressure _metarpressure :: Maybe Pressure , -- | Measured pressure _temperature :: Maybe Int , -- | Determined dew point _dewPoint :: Maybe Int , -- | Expected changes within the next two hours -- There can be more than one; for example a TEMPO and a BECMG prediction _weathertrend :: [Trend] , -- | RMK section (Additional parts of a METAR report that are not -- part of the official METAR message but are commonly used -- in various parts of the world; unparsed) _remark :: Maybe Text , -- _maintenance :: Bool } | -- | An automatic terminal information service report ATIS | -- | A non-scheduled METAR SPECI | -- | A terminal aerodrome forecast TAF { -- | The type of the report _reporttype :: ReportType , -- | The date the TAF was issued _tafissuedat :: Date , -- | A remark about the reported observation. _flags :: [Flag] , -- | The designation of the observing station. _station :: Station , -- | The beginning of the validity period _tafvalidfrom :: Date , -- | The end of the validity period _tafvaliduntil :: Date , -- | The initial conditions predicted to be valid -- for the duration of the TAF _tafinitialconditions :: [Transition] , -- | Zero or more predicted changes _tafchanges :: [Trend] } | -- | An aviation wx hazard message of moderate severity AIRMET | -- | A significant meteorological information message SIGMET | -- | A general aviation forecast message GAMET deriving (Eq, Show) -- | A flag describing an aviation meteorological report data Flag = -- | A message has been corrected after the beginning of -- its original validity period COR | -- | A message has been corrected prior to its -- original validity period AMD | -- | A message has been generated fully automatic -- without a plausibility check by a human AUTO deriving (Eq, Show) -- | The trend part of an observation message specifies expected -- changes in weather conditions within the next two hours. -- A Trend/Transition part of a TAF message specified expected -- changes in weather conditions within the specified range. data Trend = -- | A transition that will start within the defined -- time frame and be completed at the end of the defined -- time frame BECMG { _becmgStart :: Maybe Date , _becmgFinished :: Maybe Date , _becmgTransitions :: [Transition] } | -- | A transition that will start within the defined -- time frame and be finished at the end of the defined -- time frame TEMPO { _tempoFrom :: Maybe Date , _tempoTo :: Maybe Date , _tempoTransitions :: [Transition] } | -- | A probability specification. -- As one of my FIs (ex-atc at EDDF) used to put it: -- 30% means "I'm quite sure it won't happen but will still -- put it in here, in case it does after all." -- 40% means "I'm certain it will happen but will still -- put it with 40%, in case it does not happen after all." PROB Int Trend | -- | NOSIG is only applicable to METARs. It means that -- no significant changes are expected within the next -- two hours. NOSIG | -- | NOTAVAIL is only applicable to METARs. It means that -- the METAR message in question does not contain a TREND -- section. NOTAVAIL deriving (Eq, Show) -- | A transition in weather conditions. -- A transition can either be temporary or permanent; -- this will be encoded in the container structure. data Transition = -- | A change of wind strength or direction TransWind Wind | -- | A change of visibility TransVis [Visibility] | -- | A change of visibility for a specific runway TransRunwayVis [(Runway, [Visibility], Maybe VisTrend)] | -- | A change of weather phenomenon TransWX [WeatherPhenomenon] | -- | A change of ceiling or cloud layers TransClouds [Cloud] | -- | A change of ceiling or cloud layers TransPressure [Pressure] deriving (Eq, Show) -- | A visibility trend specifically for runway conditions data VisTrend = -- | Visibility will improve (maybe do wait a bit) VisTrendUpward | -- | Visibility will deteriorate (still, don't rush -- the take off and use proper phraseology at all times) VisTrendDownward | -- | No expected change in runway visibility conditions VisTrendNoDistinctTendency deriving (Eq, Show) -- | A pressure value. This is intentionally coded -- individually and not converted to a specific reference. data Pressure = -- | The QNH value in hectopascals. QNH is the current -- pressure at sea level, corrected for pressure and -- temperature changes at the station level. QNH Int | -- | The same as QNH, only in inches -- (Do you know the old joke?: -- * ATC: Liner 1723 descend to 3,000ft, the QNH is 1013mb. -- * Liner 1723: Uh, approach, can we have that in inches please? -- * ATC: Liner 1723 descend to 36,000 inches, the QNH is 1013mb. Altimeter Int | -- | The current pressure at station level in hectopascals. QFE Int | -- | The current pressure at sea level in hectopascals. QFF Int deriving (Eq, Show) -- | A weather phenomenon. -- This can be an observed phenomenon in the case of METARs or an -- expected phenomenon in the case of TAFs. data WeatherPhenomenon = Phenomenon { -- | The intensity of the phenomenon. _intensity :: WPIntensity , -- | The description of the weather phenomenon. _desc :: Maybe WPDesc , -- | The precipitation type of the weather phenomenon. _prec :: Maybe WPPrecipitation , -- | The effects of the phenomenon on the visibility _obfus :: Maybe WPObfuscation , -- | Other details about the phenomenon. _other :: Maybe WPOther } deriving (Eq, Show) -- | The intensity of an observed or expected weather phenomenon. data WPIntensity = -- | Light Light | -- | Moderate Moderate | -- | Heavy Heavy | -- | Only applicable to METARs. The weather phenomenon was -- observed in the vicinity of the observed area, not within -- the observed area itself. Vicinity | -- | Only applicable to METARs. The weather phenomenon was -- recently observed in the past, but was not observed at -- the time the report was issued. Recent deriving (Enum, Eq, Show) -- | The description of a weather phenomenon. data WPDesc = -- | Shallow. Shallow | -- | Patches. Patches | -- | Partial. WXPartial | -- | Low, drifting. LowDrifting | -- | Blowing. Blowing | -- | Shower. Shower | -- | Thunderstorm. Thunderstorm | -- | Freezing. Freezing deriving (Enum, Eq, Ord, Show) -- | The type of the precipitation data WPPrecipitation = -- | Drizzle. Drizzle | -- | Rain. Rain | -- | Snow. Snow | -- | Snow grains. ShowGrains | -- | Ice crystals. IceCrystals | -- | Ice pellets. IcePellets | -- | Hail. Hail | -- | Snow pellets. SnowPellets | -- | No precipication detected (fully automated measurement) NoPrecipitationDetected | -- | Unknown type of precipitation. UnknownPrecipitation deriving (Enum, Eq, Ord, Show) -- | Effects on the visibility by a weather phenomenon data WPObfuscation = -- | Mist. Visibility impaired but still greater than 1000m Mist | -- | Fog. Visibility less than 1000m. Fog | -- | Smoke. Smoke | -- | Volcanic ash. VolcanicAsh | -- | Dust. Dust | -- | Sand. Sand | -- | Haze. Haze deriving (Enum, Eq, Ord, Show) -- | Other important information about a weather phenomenon. data WPOther = -- | Dust or sand whirls. DustOrSandwhirls | -- | Squalls. Squalls | -- | Tornado. Tornado | -- | Sand storm. Sandstorm | -- | Dust storm. Duststorm deriving (Enum, Eq, Ord, Show) -- | The Distance. data Distance = -- | The distance in metres. Metres Int | -- | The distance in km. KM Int | -- | The distance in statute miles. SM Int | -- | The distance in nautical miles. NM Int deriving (Eq, Show) -- | Vertical visibility. data Visibility = -- | Ten kilometres or more. This is also used for "P6SM" which means 6 statute miles or more. -- Do note that 6SM is a little less than 10km (9.6km) TenOrMore | -- | Fifty metres or less. FiftyMetresOrLess | -- | Two kilometres or more. TwoOrMore | -- | A specific visibility. SpecificVisibility Distance (Maybe Direction) deriving (Eq, Show) -- | Directions. data Direction = -- | North. North | -- | South. South | -- | East. East | -- | West. West | -- | Northwest. NorthWest | -- | Northeast. NorthEast | -- | Southwest. SouthWest | -- | Southeast. SouthEast | -- | No direction could be determined NDV | -- | Left runway for runways of the same QFU -- (part of the runway designator) RWYLeft | -- | Right runway for runways of the same QFU -- (part of the runway designator) RWYRight | -- | Centre runway for runways of the same QFU -- (part of the runway designator) RWYCenter deriving (Eq, Show) -- | Runway specification. data Runway = -- | All runways. AllRunways | -- | A specific runway. SpecificRunway { -- | The runway's magnetic orientation, divided by ten and rounded. _runwayQFU :: Int , -- | For multiple runways with the same QFU, a left, right or centre -- selector is added. _runwayDirection :: Maybe Direction } deriving (Eq, Show) -- | The runway contamination type. data RwyCoverType = -- | The runway is not contaminated. RCTDry | -- | The runway is moist. RCTMoist | -- | The runway is wet. RCTWet | -- | The runway is convered with rime. RCTRime | -- | The runway is covered with dry snow. RCTDrySnow | -- | The runway is covered with wet snow. RCTWetSnow | -- | The runway is covered with slush. RCTSlush | -- | The runway is covered with ice. RCTIce | -- | The runway is covered with frozen ruts or ridges. RCTFZRut | -- | The runway contamination type is unknown. RCTUnknown deriving (Eq, Show, Enum) -- | Runway conditions. data RunwayCondition = -- | Specific runway conditions exist. SpecificRunwayCondition { -- | The runway for which specific conditions -- have been observed. _rwcondRunway :: Runway , -- | Whether and how the runway is contamindated. _rwcondCover :: RwyCoverType , -- | The extent of the contamination in percent. _rwcondSpread :: Maybe Int , -- | The height of the contamination in millimetres. _rwcondCoverHeight :: Maybe Int , -- | The friction coefficient or braking action value. _rwcondBrkCoeff :: RunwayBraking } | -- | The runway is closed. RwyClosed { -- | The runway that is closed. _rwclosedRunway :: Runway } | -- | The whole aerodrome is closed. ADClosed deriving (Eq, Show) -- | The measured brake efficiency of a specific runway. data RunwayBraking = -- | The friction coefficient. BrakingFriction Int | -- | The braking coefficient. BrakingEffect Int deriving (Eq, Show) -- | An obersvation date. data Date = Date { _dayOfMonth :: Int , _hour :: Int , _minute :: Int } deriving (Eq, Show) -- | An aeronautical weather station designator. newtype Station = -- | The station as identified by its aerodrome's -- ICAO code. ICAO Text deriving (Eq, Show) -- | A vertical position specification. data Vertical = -- | A vertical position with reference to the ground -- in feet. Height Int | -- | A vertical position with reference to the mean -- sea level/QNH in feet. Altitude Int | -- | A pressure altitude with reference to the -- standard QNH of 1013 hectopascals in hundrets of feet. FlightLevel Int | -- | Vertical position is not specified. VertNotSpec deriving (Eq, Show) -- | Wind information. data Wind = Wind { -- | The direction the wind is blowing from. _winddirection :: Maybe WindDirection , -- | The wind speed. _velocity :: Maybe Unit , -- | The strength of the observed gusts, if any. _gusts :: Maybe Int } deriving (Eq, Show) -- | The direction the wind is blowing from. data WindDirection = -- | The wind is blowing in equal or almost equal -- strength from a wide variety of directions. Variable | -- | The wind is blowing from the specified direction. -- Directions can be given with reference to true or -- magnetic north, depending on the type of weather -- observation/forecast message. Degrees Int | -- | The wind is blowing from a specific direction -- range. Varying { -- | The mean direction the wind is blowing from. _windmean :: Int , -- | The minimum direction the wind is blowing from. _windfrom :: Int , -- | The maximum direction the wind is blowing from. _windto :: Int } deriving (Eq, Show) -- | A speed unit. data Unit = -- | Nautical miles per hour Knots Int | -- | Statute miles per hour Miles Int | -- | Unknown (miles per second?) MPS Int | -- | Kilometres per hour KMH Int deriving (Eq, Show) -- | A cloud specification. data Cloud = -- | No specific clouds could be observed, because -- the (given) ground visibility was too low or because -- the ground is covered in clouds. VVis (Maybe Int) | -- | Clouds were observed. ObservedCloud Cover Vertical CloudType deriving (Eq, Show) -- | The type of cloud. data CloudType = -- | A cumulonimbus cloud. Cumulonimbus | -- | A developing cb cloud. ToweringCumulus | -- | A stratus cloud. Stratus | -- | A cumulus cloud. Cumulus | -- | A stratocumulus cloud. Stratocumulus | -- | An altostratus cloud. Altostratus | -- | An altocumulus cloud. Altocumulus | -- | A cirrostratus cloud. Cirrostratus | -- | A cirrus cloud. Cirrus | -- | An unclassified cloud. Unclassified deriving (Enum, Eq, Show) -- | The area that is covered. data Cover = -- | 1-2 eights are covered. FEW | -- | 3-4 eights are covered. SCT | -- | 5-7 eights are covered. BKN | -- | More than 7 eights are covered. OVC | -- | Cover not specified CoverNotSpecified deriving (Enum, Eq, Ord, Show) makeClassy ''Weather makeClassyPrisms ''Weather makeClassy ''Flag makeClassyPrisms ''Flag makeClassy ''Trend makeClassyPrisms ''Trend makeClassy ''Transition makeClassyPrisms ''Transition makeClassy ''VisTrend makeClassyPrisms ''VisTrend makeClassy ''Pressure makeClassyPrisms ''Pressure makeClassy ''WeatherPhenomenon makeClassy ''WPIntensity makeClassyPrisms ''WPIntensity makeClassy ''WPDesc makeClassyPrisms ''WPDesc makeClassy ''WPPrecipitation makeClassyPrisms ''WPPrecipitation makeClassy ''WPObfuscation makeClassyPrisms ''WPObfuscation makeClassy ''WPOther makeClassyPrisms ''WPOther makeClassy ''Distance makeClassyPrisms ''Distance makeClassy ''Visibility makeClassyPrisms ''Visibility makeClassy ''Direction makeClassyPrisms ''Direction makeClassy ''Runway makeClassyPrisms ''Runway makeClassy ''RwyCoverType makeClassyPrisms ''RwyCoverType makeClassy ''RunwayCondition makeClassyPrisms ''RunwayCondition makeClassy ''RunwayBraking makeClassyPrisms ''RunwayBraking makeClassy ''Date makeWrapped ''Station makeClassy ''Vertical makeClassyPrisms ''Vertical makeClassy ''Wind makeClassy ''WindDirection makeClassyPrisms ''WindDirection makeClassy ''Unit makeClassyPrisms ''Unit makeClassy ''Cloud makeClassyPrisms ''Cloud makeClassy ''CloudType makeClassyPrisms ''CloudType makeClassy ''Cover makeClassyPrisms ''Cover instance HasWPIntensity WeatherPhenomenon where wPIntensity = intensity . wPIntensity --instance HasWindDirection Wind where -- windDirection = -- winddirection . windDirection --instance HasUnit Wind where -- unit = -- velocity . unit stationParser :: CharParsing f => f Station stationParser = ICAO <$> takeChars 4 dateParser :: CharParsing f => f Date dateParser = Date <$> twin <*> twin <*> (twin <* text "Z") where twin = (\a b -> read [a, b]) <$> digit <*> digit dateParserSansZulu :: CharParsing f => f Date dateParserSansZulu = Date <$> twin <*> twin <*> twin where twin = (\a b -> read [a, b]) <$> digit <*> digit briefDateParser :: CharParsing f => f Date briefDateParser = Date <$> twin <*> twin <*> pure 0 where twin = (\a b -> read [a, b]) <$> digit <*> digit variableWindParser :: (Monad f, CharParsing f) => Maybe WindDirection -> f WindDirection variableWindParser (Just (Degrees meanWind)) = try $ do dir1 <- (\a b c -> read [a, b, c]) <$> digit <*> digit <*> digit _ <- char 'V' dir2 <- (\a b c -> read [a, b, c]) <$> digit <*> digit <*> digit return $ Varying meanWind dir1 dir2 variableWindParser _ = unexpected "Erroneous parameters" windParser :: (Monad f, CharParsing f) => f Wind windParser = do dir <- choice [Just <$> readwinddir, Just <$> variablewind, text "///" >> return Nothing] str <- choice [Just <$> readwindstr, text "//" >> return Nothing] gustsies <- option Nothing readgusts unit' <- readunit dir2 <- option dir (Just <$> (char ' ' >> variableWindParser dir)) return $ Wind dir2 (unit' <$> str) gustsies where variablewind = "VRB" `means` Variable readwinddir = (\a b c -> Degrees . read $ [a, b, c]) <$> digit <*> digit <*> digit readwindstr = (\a b -> read [a, b]) <$> digit <*> digit readunit = choice [ "KT" `means` Knots , "MPH" `means` Miles , "MPS" `means` MPS , "KM" `means` KMH] readgusts = (\_ b c -> Just . read $ [b, c]) <$> char 'G' <*> digit <*> digit pressureParser :: CharParsing f => f Pressure pressureParser = choice [qnha, mmhg, qnh] where qnh = (\_ a b c d -> QNH $ read [a, b, c, d]) <$> char 'Q' <*> digit <*> digit <*> digit <*> digit qnha = (\_ a b c d _ -> Altimeter $ read [a, b, c, d]) <$> text "QNH" <*> digit <*> digit <*> digit <*> digit <*> text "INS" mmhg = (\_ a b c d -> Altimeter $ read [a, b, c, d]) <$> char 'A' <*> digit <*> digit <*> digit <*> digit wxParser :: (Monad f, CharParsing f) => f WeatherPhenomenon wxParser = do spaces intsy <- intensityParser dsc <- perhaps descParser prc <- perhaps precipitationParser obfs <- perhaps obfuscationParser othr <- perhaps otherParser when ( (== 0) . Prelude.length . Prelude.filter not $ [ isNothing dsc, isNothing prc , isNothing obfs, isNothing othr ] ) $ unexpected "" return $ Phenomenon intsy dsc prc obfs othr perhaps :: Alternative m => m a -> m (Maybe a) perhaps parser = option Nothing $ Just <$> parser perhaps_ :: Alternative f => f a -> f () perhaps_ parser = void $ perhaps parser callsfor :: (CharParsing m, Monad m) => Text -> m b -> m b a `callsfor` b = text a >> b means :: (CharParsing m, Monad m) => Text -> b -> m b a `means` b = text a >> return b means' :: (CharParsing m, Monad m) => Text -> a -> m a a `means'` b = try $ spaces >> text a >> spaces >> return b descParser :: (Monad f, CharParsing f) => f WPDesc descParser = choice [ "MI" `means` Shallow , "BC" `means` Patches , "PR" `means` WXPartial , "DR" `means` LowDrifting , "BL" `means` Blowing , "SH" `means` Shower , "TS" `means` Thunderstorm , "FZ" `means` Freezing ] precipitationParser :: (Monad f, CharParsing f) => f WPPrecipitation precipitationParser = choice [ "DZ" `means` Drizzle , "RA" `means` Rain , "SN" `means` Snow , "SG" `means` ShowGrains , "IC" `means` IceCrystals , "PL" `means` IcePellets , "GR" `means` Hail , "GS" `means` SnowPellets , "// " `means` NoPrecipitationDetected , "UP" `means` UnknownPrecipitation ] obfuscationParser :: (Monad f, CharParsing f) => f WPObfuscation obfuscationParser = choice [ "BR" `means` Mist , "FG" `means` Fog , "FU" `means` Smoke , "VA" `means` VolcanicAsh , "DU" `means` Dust , "SA" `means` Sand , "HZ" `means` Haze ] otherParser :: (Monad f, CharParsing f) => f WPOther otherParser = choice [ "PO" `means` DustOrSandwhirls , "SQ" `means` Squalls , "FC" `means` Tornado , "SS" `means` Sandstorm , "DS" `means` Duststorm ] intensityParser :: (Monad f, CharParsing f) => f WPIntensity intensityParser = option Moderate $ choice [ char '-' >> return Light , char '+' >> return Heavy , "VC" `means` Vicinity , "RE" `means` Recent ] visibilityParser :: (Monad f, CharParsing f) => f Visibility visibilityParser = spaces >> choice [ tenormorendv, tenormore, sixmilesormore, arb, arb1, metres ] where tenormorendv = text "9999NDV" >> return TenOrMore tenormore = text "9999" >> return TenOrMore sixmilesormore = text "P6SM" >> return TenOrMore metres = (\a b c d dir -> SpecificVisibility (visunit $ read [a,b,c,d]) dir) <$> digit <*> digit <*> digit <*> digit <*> directionParser visunit :: Int -> Distance visunit n = if n > 5000 then KM (n `quot` 1000) else Metres n arb = (\a b unit' -> SpecificVisibility (unit' $ read [a,b])) <$> digit <*> digit <*> distanceUnitParser <*> directionParser arb1 = (\a unit' -> SpecificVisibility (unit' $ read ['0', a])) <$> digit <*> distanceUnitParser <*> directionParser directionParser :: (Monad f, CharParsing f) => f (Maybe Direction) directionParser = Nothing `option` (Just <$> choice [ "NE" `means` NorthEast, "NW" `means` NorthWest , "SE" `means` SouthEast, "SW" `means` SouthWest , "NDV" `means` NDV , "N" `means` North, "S" `means` South , "E" `means` East, "W" `means` West ]) distanceUnitParser :: (Monad f, CharParsing f) => f (Int -> Distance) distanceUnitParser = choice [ "KM" `means` KM , "SM" `means` SM , "NM" `means` NM ] cloudParser :: (Monad f, CharParsing f) => f [Cloud] cloudParser = choice [ (:[]) <$> vvisParser, cavok , catMaybes <$> sepBy1 (choice [ Just <$> clds, noclouds ]) (char ' ')] where clds = do perhaps_ space intsy <- cloudIntensityParser height <- choice [ "///" `means` VertNotSpec , (\a b c -> Height $ (* 100) $ read [a, b, c]) <$> digit <*> digit <*> digit ] cloudType' <- cloudTypeParser return $ ObservedCloud intsy height cloudType' cavok = spaces >> "CAVOK" `means` [] noclouds = choice [ clr, nsc , ncd, skc, nsw, nowx, ncd2 ] nsc = "NSC " `means` Nothing clr = "CLR " `means` Nothing skc = "SKC " `means` Nothing nsw = "NSW " `means` Nothing ncd = "NCD " `means` Nothing nowx = "// " `means` Nothing ncd2 = "////// " `means` Nothing vvisParser :: (Monad f, CharParsing f) => f Cloud vvisParser = do _ <- text "VV" choice [ "///" `means` VVis Nothing , (\a b c -> VVis . Just . read $ [a,b,c]) <$> digit <*> digit <*> digit ] cloudIntensityParser :: (Monad f, CharParsing f) => f Cover cloudIntensityParser = choice [ "FEW" `means` FEW , "SCT" `means` SCT , "BKN" `means` BKN , "OVC" `means` OVC , "///" `means` CoverNotSpecified ] cloudTypeParser :: (Monad f, CharParsing f) => f CloudType cloudTypeParser = option Unclassified $ choice [ "CB" `means` Cumulonimbus , "TCU" `means` ToweringCumulus , "ST" `means` Stratus , "CU" `means` Cumulus , "SC" `means` Stratocumulus , "AS" `means` Altostratus , "AC" `means` Altocumulus , "CS" `means` Cirrostratus , "CI" `means` Cirrus , "///" `means` Unclassified] perhapsMinus :: (Monad f, CharParsing f) => f String perhapsMinus = "" `option` (char 'M' >> return "-") tdParser :: (Monad f, CharParsing f) => f (Maybe Int, Maybe Int) tdParser = do tmpr <- choice [ text "//" >> return Nothing , Just <$> tmpParser ] _ <- char '/' dewpoint <- choice [ text "//" >> return Nothing , Just <$> tmpParser ] return (tmpr, dewpoint) where tmpParser = (\pm a b -> read (pm ++ [a, b]) :: Int) <$> perhapsMinus <*> digit <*> digit flagsParser :: (Monad f, CharParsing f) => f [Flag] flagsParser = many $ choice [ "COR" `means'` COR , "AMD" `means'` AMD , "AUTO" `means'` AUTO ] runwayvisParser :: (Monad f, CharParsing f) => f (Runway, [Visibility], Maybe VisTrend) runwayvisParser = do runway' <- runwayDesignationParser _ <- char '/' vis <- parseRwyVis vistrend <- Nothing `option` (Just <$> choice [ "D" `means` VisTrendDownward , "N" `means` VisTrendNoDistinctTendency , "U" `means` VisTrendUpward ] ) return (runway', vis, vistrend) where parseRwyVis = do worstvis <- Nothing `option` (Just <$> choice visspec <* text "V") vis <- Just <$> choice visspec return $ catMaybes [worstvis, vis] visspec = [ "M0050" `means` FiftyMetresOrLess , "P2000" `means` TwoOrMore , fourDigits >>= \a -> return $ SpecificVisibility (Metres a) Nothing , trieDigits >>= \a -> return $ SpecificVisibility (Metres a) Nothing ] runwayconditionParser :: (Monad f, CharParsing f) => f RunwayCondition runwayconditionParser = do runway' <- runwayDesignationParser _ <- char '/' choice [ "SNOCLO" `means` ADClosed , rwycond runway' ] where rwycond runway' = do cover' <- RCTUnknown `option` ((toEnum . read . (:[])) <$> digit) spread <- choice [ char '/' >> return Nothing , (Just . read . (:[])) <$> digit ] spreadheight <- choice [ text "//" >> return Nothing , Just <$> tuhDigits ] rkorbw <- tuhDigits let coff = if rkorbw <= 90 then BrakingFriction rkorbw else BrakingEffect rkorbw return $ SpecificRunwayCondition runway' cover' spread spreadheight coff fourDigits :: CharParsing f => f Int fourDigits = (\a b c d -> read [a,b,c,d]) <$> digit <*> digit <*> digit <*> digit trieDigits :: CharParsing f => f Int trieDigits = (\a b c -> read [a,b,c]) <$> digit <*> digit <*> digit tuhDigits :: CharParsing f => f Int tuhDigits = (\a b -> read [a,b]) <$> digit <*> digit runwayDesignationParser :: (Monad f, CharParsing f) => f Runway runwayDesignationParser = choice ["R88" `means` AllRunways, oneRunway] where oneRunway = do _ <- char 'R' magheading <- (\a b -> read [a,b]) <$> digit <*> digit dir <- Nothing `option` (Just <$> choice [ "L" `means` RWYLeft , "R" `means` RWYRight , "C" `means` RWYCenter ]) return $ SpecificRunway magheading dir trendParser :: (Monad f, CharParsing f) => f [Trend] trendParser = choice [ "NOSIG" `means` [NOSIG] , changesParser ] changesParser :: (Monad f, CharParsing f) => f [Trend] changesParser = some $ spaces >> transitionTypeParser where transitionTypeParser = choice [ "TEMPO" `callsfor` (TEMPO <$> parseFrom <*> parseTo <*> transitionParser) , "BECMG" `callsfor` (BECMG <$> parseFrom <*> parseTo <*> transitionParser) , "FM" `callsfor` (BECMG <$> parseFromFM <*> pure Nothing <*> transitionParser) , "PROB" `callsfor` (PROB <$> twoDigits <*> (head <$> changesParser)) ] transitionParser = sepBy1 oneTransition (char ' ') parseFromFM = do fromDate <- dateParserSansZulu void $ text " " return $ Just fromDate parseFrom = Nothing `option` do spaces fromDate <- briefDateParser void $ text "/" return $ Just fromDate parseTo = Nothing `option` (Just <$> briefDateParser) oneTransition = do spaces choice . map try $ [ TransClouds <$> cloudParser , TransWind <$> windParser , TransVis <$> some visibilityParser , TransWX <$> many wxParser , TransPressure . (:[]) <$> pressureParser , TransRunwayVis <$> sepBy runwayvisParser (char ' ') ] twoDigits :: CharParsing f => f Int twoDigits = (\a b -> read [a,b]) <$> digit <*> digit tafParser :: (Monad f, CharParsing f) => f Weather tafParser = do _ <- text "TAF" tafflags <- flagsParser identifier <- spaces >> stationParser issuedate <- spaces >> dateParser validFrom <- spaces >> briefDateParser validTo <- text "/" >> briefDateParser predictedWind <- Nothing `option` (spaces >> Just <$> windParser) spaces predictedVisibility <- [TenOrMore] `option` some visibilityParser spaces predictedRunwayvis <- sepBy runwayvisParser (char ' ') predictedWx <- many wxParser predictedClouds <- [] `option` (spaces >> cloudParser) predictedQnh <- many $ spaces >> pressureParser -- Sometimes, multiple pressure values are offered let initialConditions = catMaybes [ TransWind <$> predictedWind , Just $ TransVis predictedVisibility , Just $ TransRunwayVis predictedRunwayvis , Just $ TransWX predictedWx , Just $ TransClouds predictedClouds , Just $ TransPressure predictedQnh ] changes <- [] `option` changesParser return TAF { _reporttype=TafReport , _tafissuedat=issuedate , _flags=tafflags , _station=identifier , _tafvalidfrom=validFrom , _tafvaliduntil=validTo , _tafinitialconditions=initialConditions , _tafchanges=changes} metarParser :: (Monad f, CharParsing f) => f Weather metarParser = do _ <- text "METAR" reportflags <- flagsParser identifier <- spaces >> stationParser reportdate <- spaces >> dateParser reportflags2 <- flagsParser reportwind <- Nothing `option` (spaces >> Just <$> windParser) spaces reportvis <- [TenOrMore] `option` some visibilityParser spaces reportrunwaycond <- sepBy runwayconditionParser (char ' ') reportrunwayvis <- sepBy runwayvisParser (char ' ') reportwx <- many wxParser reportclouds <- [] `option` (spaces >> cloudParser) (reporttemp, reportdewpoint) <- (Nothing, Nothing) `option` (spaces >> tdParser) reportpressure <- Nothing `option` (spaces >> Just <$> pressureParser) void $ many $ spaces >> pressureParser -- Sometimes, multiple pressure values are offered spaces reporttrend <- [] `option` trendParser reportrmk <- maybeRMK spaces maintenance' <- or <$> optional (True <$ char '$' <|> False <$ char '=') return $ METAR MetarReport reportdate identifier (reportflags ++ reportflags2) reportwind reportvis reportrunwayvis reportrunwaycond reportwx reportclouds reportpressure reporttemp reportdewpoint reporttrend reportrmk maintenance' maybeRMK :: (Monad f, CharParsing f) => f (Maybe Text) maybeRMK = Nothing `option` do void $ choice [ text "RMK ", text " RMK " ] Just . pack <$> some (satisfy (`notElem` ("$=" :: String))) -- | An attoparsec parser that can parse METAR messages. weatherParser :: (Monad f, CharParsing f) => f Weather weatherParser = choice [ metarParser, tafParser ]