Skip to content

Commit

Permalink
Changes due to Amendment 79 going into effect.
Browse files Browse the repository at this point in the history
   1) Removal of Runway conditions in METAR report
   2) Addition of mandatory field in TCA
   3) Changes to report re-supendend ash in VAA
   4) Updates to test procedures due to #1-3
  • Loading branch information
mgoberfield committed Nov 2, 2020
1 parent b000b19 commit d402d82
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 225 deletions.
3 changes: 0 additions & 3 deletions gifts/common/xmlConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@
CVCTNCLDS = 'SigConvectiveCloudType'
NIL = 'nil'
RECENTWX = 'AerodromeRecentWeather'
RWYDEPST = '0-20-086'
RWYCNTMS = '0-20-087'
RWYFRCTN = '0-20-089'
SEACNDS = '0-22-061'
SWX_PHENOMENA = 'SpaceWxPhenomena'
SWX_LOCATION = 'SpaceWxLocation'
Expand Down
92 changes: 2 additions & 90 deletions gifts/metarEncoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ def __init__(self):
self._Logger = logging.getLogger(__name__)
#
# Create dictionaries of the following WMO codes
neededCodes = [des.CLDAMTS, des.WEATHER, des.RECENTWX, des.CVCTNCLDS, des.SEACNDS, des.RWYDEPST, des.RWYCNTMS,
des.RWYDEPST, des.RWYFRCTN]
neededCodes = [des.CLDAMTS, des.WEATHER, des.RECENTWX, des.CVCTNCLDS, des.SEACNDS]
try:
self.codes = deu.parseCodeRegistryTables(des.CodesFilePath, neededCodes, des.PreferredLanguageForTitles)
except AssertionError as msg:
Expand All @@ -39,15 +38,13 @@ def __init__(self):
setattr(self, 'vcnty', self.pcp)

self.observedTokenList = ['temps', 'altimeter', 'wind', 'vsby', 'rvr', 'pcp', 'obv', 'vcnty',
'sky', 'rewx', 'ws', 'seastate', 'rwystate']
'sky', 'rewx', 'ws', 'seastate']

self.trendTokenList = ['wind', 'pcp', 'obv', 'sky']

self._re_unknwnPcpn = re.compile(r'(?P<mod>[-+]?)(?P<char>(SH|FZ|TS))')
self._re_cloudLyr = re.compile(r'(VV|FEW|SCT|BKN|OVC|///|CLR|SKC)([/\d]{3})?(CB|TCU|///)?')
self._TrendForecast = {'TEMPO': 'TEMPORARY_FLUCTUATIONS', 'BECMG': 'BECOMING'}
self._RunwayDepositDepths = {'92': '100', '93': '150', '94': '200',
'95': '250', '96': '300', '97': '350', '98': '400'}

def __call__(self, decodedMetar, tacString):

Expand Down Expand Up @@ -724,91 +721,6 @@ def seastate(self, parent, token):
except KeyError:
pass

def rwystate(self, parent, tokens):

for token in tokens:

indent1 = ET.SubElement(parent, 'iwxxm:runwayState')
if token['state'] == 'SNOCLO':
indent1.set('nilReason', des.NIL_SNOCLO_URL)
indent1.set('xsi:nil', 'true')
continue

indent2 = ET.SubElement(indent1, 'iwxxm:AerodromeRunwayState')
indent2.set('allRunways', 'false')
#
# Attributes set first
if len(token['runway']) == 0 or token['runway'] == '88':
indent2.set('allRunways', 'true')

if token['runway'] == '99':
indent2.set('fromPreviousReport', 'true')

if token['state'][:4] == 'CLRD':
indent2.set('cleared', 'true')
#
# Runway direction
if indent2.get('allRunways') == 'false':
indent3 = ET.SubElement(indent2, 'iwxxm:runway')
if token['runway'] == '99':
indent3.set('nilReason', self.codes[des.NIL][des.NA][0])
else:
self.runwayDirection(indent3, token['runway'])
#
# Runway deposits
if token['state'][0].isdigit():
indent3 = ET.SubElement(indent2, 'iwxxm:depositType')
uri, title = self.codes[des.RWYDEPST][token['state'][0]]
indent3.set('xlink:href', uri)
if (des.TITLES & des.RunwayDeposit):
indent3.set('xlink:title', title)
#
# Runway contaminates
if token['state'][1].isdigit():
indent3 = ET.SubElement(indent2, 'iwxxm:contamination')
try:
uri, title = self.codes[des.RWYCNTMS][token['state'][1]]
except KeyError:
uri, title = self.codes[des.RWYCNTMS]['15']

indent3.set('xlink:href', uri)
if (des.TITLES & des.AffectedRunwayCoverage):
indent3.set('xlink:title', title)
#
# Depth of deposits
indent3 = ET.Element('iwxxm:depthOfDeposit')
depth = token['state'][2:4]
if depth.isdigit():
if depth != '99':
indent3.set('uom', 'mm')
indent3.text = self._RunwayDepositDepths.get(depth, depth)
else:
indent3.set('uom', 'N/A')
indent3.set('xsi:nil', 'true')
indent3.set('nilReason', self.codes[des.NIL][des.UNKNWN][0])

indent2.append(indent3)

elif depth == '//':
indent3.set('uom', 'N/A')
indent3.set('xsi:nil', 'true')
indent3.set('nilReason', self.codes[des.NIL][des.NOOPRSIG][0])
indent2.append(indent3)
#
# Runway friction
friction = token['state'][4:6]
if friction.isdigit():
#
# Remove leading zeros
friction = str(int(friction))
indent3 = ET.SubElement(indent2, 'iwxxm:estimatedSurfaceFrictionOrBrakingAction')
uri, ignored = self.codes[des.RWYFRCTN][friction]
indent3.set('xlink:href', uri)
if (des.TITLES & des.RunwayFriction):
title = des.RunwayFrictionValues.get(friction, 'Friction coefficient: %.2f' %
(int(friction) * 0.01))
indent3.set('xlink:title', title)

def runwayDirection(self, parent, rwy):

uuid = self.runwayDirectionCache.get(rwy, deu.getUUID())
Expand Down
10 changes: 5 additions & 5 deletions gifts/tcaDecoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Decoder(tpg.Parser):
START/d -> TCA $ d=self.finish() $ ;
TCA -> 'TC ADVISORY' (Test|Exercise)? Body ;
Body -> DTG Centre CName AdvNum CLoc (CBNIL|CB2|CB3)* (CMov1|CMov2) IChng? CPres CMaxWnd (CFPsn CFWnd){4,} Rmk NextDTG? '.*' ;
Body -> DTG Centre CName AdvNum CLoc (CBNIL|CB2|CB3)* (CMov1|CMov2) IChng CPres CMaxWnd (CFPsn CFWnd){4,} Rmk NextDTG? '.*' ;
CB2 -> 'CB:\s*WI\s+' CBCircle CBTop ;
CB3 -> 'CB:\s*WI\s+' (LatLon|'-'){5,} CBTop ;
Expand Down Expand Up @@ -77,11 +77,11 @@ class Decoder(tpg.Parser):

def __init__(self):

self._tokenInEnglish = {'_tok_1': 'TC ADVISORY line', 'dtg': 'Date/Time Group', 'centre': 'Issuing Centre',
self._tokenInEnglish = {'_tok_1': 'TC ADVISORY line', 'dtg': 'Date/Time Group', 'centre': 'Issuing RSMC',
'cname': 'Name of Cyclone', 'vloc': 'Location of Cyclone', 'advnum': 'Advisory Number',
'cloc': 'Cyclone Position', 'cmov': 'Cyclone Movement', 'cpres': 'Central Pressure',
'cmaxwnd': 'Cyclone Maximum Wind Speed', 'cfpsn': 'Forecast Position',
'cfwnd': 'Forecast Maximum Wind Speed', 'rmk': 'Remarks',
'cloc': 'Cyclone Position', 'cmov': 'Cyclone Movement', 'ichng': 'Intensity Change',
'cpres': 'Central Pressure', 'cmaxwnd': 'Cyclone Maximum Wind Speed',
'cfpsn': 'Forecast Position', 'cfwnd': 'Forecast Maximum Wind Speed', 'rmk': 'Remarks',
'nextdtg': 'Next advisory issuance time or NO MSG EXP'}

self.header = re.compile(r'.*(?=TC ADVISORY)', re.DOTALL)
Expand Down
92 changes: 3 additions & 89 deletions tests/test_metar_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import gifts.common.xmlConfig as des
import gifts.common.xmlUtilities as deu

reqCodes = [des.WEATHER, des.SEACNDS, des.RWYFRCTN, des.RWYCNTMS, des.RWYDEPST, des.RECENTWX, des.CVCTNCLDS,
des.CLDAMTS]
reqCodes = [des.WEATHER, des.SEACNDS, des.RECENTWX, des.CVCTNCLDS, des.CLDAMTS]

codes = deu.parseCodeRegistryTables(des.CodesFilePath, reqCodes)

Expand Down Expand Up @@ -1107,90 +1106,6 @@ def test_seastates():
assert wh.get('nilReason') == notObservable[0]


def test_runwaystates():
#
# Runway states depreciated, discontinued Nov 2020. Tests are not exhaustive.
#
test = """SAXX99 XXXX 151200
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R/SNOCLO=
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R/CLRD//=
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R01///////=
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R02/999491=
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R88/CLRD//=
METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R99/CLRD//=
"""
bulletin = encoder.encode(test)
assert len(bulletin) == test.count('\n') - 1

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R/SNOCLO=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
assert tree.find('%srunwayState' % iwxxm).get('nilReason') == des.NIL_SNOCLO_URL

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R/CLRD//=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
rs = tree.find('%srunwayState' % iwxxm)
assert rs[0].get('allRunways') == 'true'
assert rs[0].get('cleared') == 'true'

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R01///////=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
rs = tree.find('%srunwayState' % iwxxm)
assert rs[0].get('allRunways') == 'false'
assert rs.find('%sdesignator' % aixm).text == '01'
assert rs.find('%sdepositType' % iwxxm) is None
assert rs.find('%scontamination' % iwxxm) is None
assert rs.find('%sdepthOfDeposit' % iwxxm).get('nilReason') == nothingOfOperationalSignificance[0]
assert rs.find('%sestimatedSurfaceFrictionOrBrakingAction' % iwxxm) is None

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R02/999491=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
rs = tree.find('%srunwayState' % iwxxm)
assert rs[0].get('allRunways') == 'false'
assert rs.find('%sdesignator' % aixm).text == '02'
assert rs.find('%sdepositType' % iwxxm).get(xhref) == codes[des.RWYDEPST]['9'][0]
assert rs.find('%scontamination' % iwxxm).get(xhref) == codes[des.RWYCNTMS]['9'][0]
assert rs.find('%sdepthOfDeposit' % iwxxm).text == '200'
assert rs.find('%sdepthOfDeposit' % iwxxm).get('uom') == 'mm'
friction = rs.find('%sestimatedSurfaceFrictionOrBrakingAction' % iwxxm)
assert friction.get(xhref) == codes[des.RWYFRCTN]['91'][0]

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R88/CLRD//=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
rs = tree.find('%srunwayState' % iwxxm)
assert rs[0].get('allRunways') == 'true'
assert rs[0].get('cleared') == 'true'

# METAR BIAR 290000Z /////KT //// // ////// ///// Q//// R99/CLRD//=

result = bulletin.pop()
assert result.get('translationFailedTAC') is None

tree = ET.XML(ET.tostring(result))
rs = tree.find('%srunwayState' % iwxxm)
assert rs[0].get('fromPreviousReport') == 'true'
assert rs[0].get('cleared') == 'true'


def test_trendTiming():

test = """SAXX99 XXXX 151200
Expand Down Expand Up @@ -1333,14 +1248,14 @@ def test_trendTiming():
def test_commonRunway():

test = """SAXX99 KXXX 151200
METAR BIAR 290000Z /////MPS //// R01C/2000 ////// ///// Q//// WS R01C R01C/999491=
METAR BIAR 290000Z /////MPS //// R01C/2000 ////// ///// Q//// WS R01C=
"""
bulletin = encoder.encode(test)
assert len(bulletin) == test.count('\n') - 1

tree = ET.XML(ET.tostring(bulletin.pop()))
runways = tree.findall('%srunway' % iwxxm)
assert len(runways) == 3
assert len(runways) == 2
#
# First runway shall have the id that is shared with the rest
runwayID = None
Expand Down Expand Up @@ -1395,7 +1310,6 @@ def test_misc():
test_sky_conditions()
test_windshears()
test_seastates()
test_runwaystates()
test_trendTiming()
test_commonRunway()
test_misc()
43 changes: 5 additions & 38 deletions tests/test_tca_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def test_tcaExercise():
OBS PSN: 11/1800Z N1400 E13725
CB: WI 180NM OF TC CENTRE TOP ABV FL450
MOV: W 12KT
INTST CHANGE: INTSF
C: 905HPA
MAX WIND: 110KT
FCST PSN +6 HR: 12/0000Z N1405 E13620
Expand Down Expand Up @@ -229,6 +230,7 @@ def test_tcaNormal():
CB: WI N3140 W03525-N3061 W03611-N3030 W03449-
N3140 W03525 TOP FL350
MOV: STNR
INTST CHANGE: NC
C: 0988HPA
MAX WIND: 060KT
FCST PSN +6 HR: 01/2100Z N3438 W03546
Expand Down Expand Up @@ -270,6 +272,7 @@ def test_tcaMetric():
OBS PSN: 11/1800Z N1400 E13725
CB: WI 180KM OF TC CENTRE TOP ABV FL450
MOV: W 20KMH
INTST CHANGE: NC
C: 905HPA
MAX WIND: 150MPS
FCST PSN +6 HR: 12/0000Z N14 E13620
Expand Down Expand Up @@ -381,6 +384,7 @@ def test_developing():
OBS PSN: 09/0000Z S1212 E13406
CB: WI 60NM OF TC CENTRE TOP FL600
MOV: SW 06KT
INTST CHANGE: WKN
C: 999HPA
MAX WIND: 30KT
FCST PSN +6 HR: 09/0600Z S1224 E13342
Expand Down Expand Up @@ -495,6 +499,7 @@ def test_dissipation():
ADVISORY NR: 2019/004
OBS PSN: 23/1500Z N29 W080
MOV: NNE 15KT
INTST CHANGE: WKN
C: 1014HPA
MAX WIND: 030KT
FCST PSN +6 HR: 23/2100Z N2955 W07839
Expand Down Expand Up @@ -587,43 +592,6 @@ def test_dissipation():
assert element.get('nilReason') == codes[des.NIL][des.NA][0]


def test_amd79():
import gifts.tcaDecoder as tD

test = """FKNT23 KNHC 011501
TCANT3
TROPICAL STORM HELENE ICAO ADVISORY NUMBER 01
NWS NATIONAL HURRICANE CENTER MIAMI FL AL012018
1501 UTC FRI MAY 01 2018
TC ADVISORY
DTG: 20180501/1501Z
TCAC: KNHC
TC: HELENE
ADVISORY NR: 2018/01
OBS PSN: 01/1430Z N3254 W03618
CB: NIL
MOV: STNR
INTST CHANGE: NC
C: 0988HPA
MAX WIND: 060KT
FCST PSN +6 HR: 01/2100Z N3438 W03546
FCST MAX WIND +6 HR: 060KT
FCST PSN +12 HR: 02/0300Z N3613 W03458
FCST MAX WIND +12 HR: 060KT
FCST PSN +18 HR: 02/0900Z N3740 W03355
FCST MAX WIND +18 HR: 055KT
FCST PSN +24 HR: 02/1500Z N3858 W03233
FCST MAX WIND +24 HR: 055KT
RMK: NIL
NXT MSG: 20180912/0000Z="""

decoder = tD.Decoder()
result = decoder(test)
assert 'err_msg' not in result


if __name__ == '__main__':

test_tcaFailureModes()
Expand All @@ -633,4 +601,3 @@ def test_amd79():
test_tcaMetric()
test_developing()
test_dissipation()
test_amd79()
Loading

0 comments on commit d402d82

Please sign in to comment.