Skip to content

Commit

Permalink
Restored runway state encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
mgoberfield committed Jan 12, 2021
1 parent d8bea35 commit 3b30b2e
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 5 deletions.
3 changes: 3 additions & 0 deletions gifts/common/xmlConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
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: 90 additions & 2 deletions gifts/metarEncoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ 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]
neededCodes = [des.CLDAMTS, des.WEATHER, des.RECENTWX, des.CVCTNCLDS, des.SEACNDS, des.RWYDEPST, des.RWYCNTMS,
des.RWYDEPST, des.RWYFRCTN]
try:
self.codes = deu.parseCodeRegistryTables(des.CodesFilePath, neededCodes, des.PreferredLanguageForTitles)
except AssertionError as msg: # pragma: no cover
Expand All @@ -38,13 +39,15 @@ def __init__(self):
setattr(self, 'vcnty', self.pcp)

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

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 @@ -727,6 +730,91 @@ 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
92 changes: 89 additions & 3 deletions tests/test_metar_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import gifts.common.xmlConfig as des
import gifts.common.xmlUtilities as deu

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

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

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


def test_runwaystates():
#
# Runway states depreciated, discontinued Nov 2021. 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 @@ -1248,14 +1333,14 @@ def test_trendTiming():
def test_commonRunway():

test = """SAXX99 KXXX 151200
METAR BIAR 290000Z /////MPS //// R01C/2000 ////// ///// Q//// WS R01C=
METAR BIAR 290000Z /////MPS //// R01C/2000 ////// ///// Q//// WS R01C R01C/999491=
"""
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) == 2
assert len(runways) == 3
#
# First runway shall have the id that is shared with the rest
runwayID = None
Expand Down Expand Up @@ -1310,6 +1395,7 @@ def test_misc():
test_sky_conditions()
test_windshears()
test_seastates()
test_runwaystates()
test_trendTiming()
test_commonRunway()
test_misc()

0 comments on commit 3b30b2e

Please sign in to comment.