awips2/edexOsgi/com.raytheon.uf.edex.aviation/utility/common_static/base/aviation/python/XmlTafEncoder.py
ucar-tmeyer 64073296dd Merge remote-tracking branch 'me/swit_20.3.2-orphan2' into swit_20.3.2-orphan
Conflicts:
	.gitignore
	build/deploy.edex.awips2/esb/conf/logback-ingest.xml
	build/deploy.edex.awips2/esb/conf/spring/edex-datastore.xml
	cave/com.raytheon.uf.viz.feature.alertviz/feature.xml
	cave/com.raytheon.uf.viz.thinclient/src/com/raytheon/uf/viz/thinclient/ui/ThinClientConnectivityDialog.java
	cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/ForecastTable.py
	cave/com.raytheon.viz.grid/src/com/raytheon/viz/grid/inv/GridUpdater.java
	edexOsgi/build.edex/build.xml
	edexOsgi/com.raytheon.edex.feature.uframe/feature.xml
	edexOsgi/com.raytheon.edex.plugin.binlightning/src/com/raytheon/edex/plugin/binlightning/dao/BinLightningDao.java
	edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/db/dao/GFEDao.java
	edexOsgi/com.raytheon.edex.plugin.gfe/utility/common_static/base/gfe/python/isc/ifpnetCDF.py
	edexOsgi/com.raytheon.edex.plugin.gfe/utility/common_static/base/gfe/python/isc/sendWFOMessage.py
	edexOsgi/com.raytheon.edex.plugin.gfe/utility/common_static/base/gfe/textproducts/templates/product/TAF.py
	edexOsgi/com.raytheon.edex.plugin.obs/src/com/raytheon/edex/plugin/obs/ObsDao.java
	edexOsgi/com.raytheon.edex.plugin.radar/src/com/raytheon/edex/plugin/radar/dao/RadarDao.java
	edexOsgi/com.raytheon.edex.plugin.satellite/src/com/raytheon/edex/plugin/satellite/dao/SatelliteDao.java
	edexOsgi/com.raytheon.uf.common.aviation/utility/common_static/base/aviation/python/MonitorP.py
	edexOsgi/com.raytheon.uf.common.dataplugin.grid.derivparam/src/com/raytheon/uf/common/dataplugin/grid/derivparam/daf/DerivedGridDataAccessFactory.java
	edexOsgi/com.raytheon.uf.edex.aviation/.pydevproject
	edexOsgi/com.raytheon.uf.edex.aviation/utility/common_static/base/aviation/python/XmlTafEncoder.py
	edexOsgi/com.raytheon.uf.edex.grid.staticdata/src/com/raytheon/uf/edex/grid/staticdata/StaticDataGenerator.java
	edexOsgi/com.raytheon.uf.edex.plugin.bufrmos/src/com/raytheon/uf/edex/plugin/bufrmos/dao/BufrMOSDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.cwat/src/com/raytheon/uf/edex/plugin/cwat/CWATDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.ffmp/src/com/raytheon/uf/edex/plugin/ffmp/FFMPDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.fog/src/com/raytheon/uf/edex/plugin/fog/FogDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.grid/src/com/raytheon/uf/edex/plugin/grid/dao/GridDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.mpe/src/com/raytheon/uf/edex/plugin/mpe/dao/metadata/impl/PrecipDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.npp.viirs/src/com/raytheon/uf/edex/plugin/npp/viirs/dao/VIIRSDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.nswrc/src/com/raytheon/uf/edex/plugin/nswrc/NSWRCDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.pointset/src/com/raytheon/uf/edex/plugin/pointset/PointSetDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.preciprate/src/com/raytheon/uf/edex/plugin/preciprate/PrecipRateDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.qpf/src/com/raytheon/uf/edex/plugin/qpf/QPFDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/dao/RedbookDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.scan/src/com/raytheon/uf/edex/plugin/scan/ScanDao.java
	edexOsgi/com.raytheon.uf.edex.plugin.vil/src/com/raytheon/uf/edex/plugin/vil/VILDao.java
	edexOsgi/com.raytheon.uf.tools.cli/impl/src/msg/sendNotificationMsg.py
	edexOsgi/com.raytheon.uf.tools.gfesuite/cli/src/ifpservertext/ifpServerText.py
	edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh
	pythonPackages/pypies/pypies/impl/DataStoreFactory.py
	pythonPackages/ufpy/test/dafTests/baseDafTestCase.py
	pythonPackages/ufpy/test/dafTests/params.py
	pythonPackages/ufpy/test/dafTests/testGrid.py
	rpms/awips2.cave/deploy.builder/build.sh
	rpms/build/x86_64/build.sh
2022-08-05 15:28:28 -05:00

829 lines
31 KiB
Python

#
# xmlTafEncoder.py
#
# Purpose: Encodes a python dictionary consisting of TAF components into a XML
# document according to the IWXXM 3.0 TAF schema.
#
# Author: Mark Oberfield MDL/OSTI/NWS/NOAA
#
# Date: 5 April 2019
#
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------- -------- --------- --------------------------------------------
# May 15, 2019 20693 mgamazaychikov Initial Creation
# Aug 27, 2019 21545 mgamazaychikov Minor bugs in XmlTafEncoder.encode()
# Sep 30, 2019 21615 mgamazaychikov Fixed incorrect dictionary key in
# XmlTafEncoder.write()
# Jan 29, 2020 21611 mgamazaychikov Upgrade to version 3.4;
# change write method to return command
# line option-argument pairs for
# msg_send command
# Apr 11, 2022 8846 randerso Fix Python 3 issue and correct bug in
# XmlTafEncoder.write()
# Apr 14, 2022 8846 randerso Additional changes to ensure the xml
# declaration is written to the file.
#
import io
import logging
import os
import re
import time
import uuid
import TafDecoder as TD
import UFStatusHandler
import XmlTafConfig as des
import xml.etree.ElementTree as ET
logHandler = UFStatusHandler.UFStatusHandler("com.raytheon.uf.edex.aviation", "EDEX")
_Logger = logging.getLogger("XmlTafEncoder")
_Logger.addHandler(logHandler)
class Encoder:
"""
Encodes a python dictionary consisting of TAF components into a XML document
according to the IWXXM/IWXXM-US 3.0 TAF schemas.
"""
def __init__(self, codesFile=des.CodesFilePath):
self._program_name = 'IWXXM TAF Encoder'
self._description = 'To encode Terminal Aerodrome Forecast information in IWXXM %s format.' % des._iwxxm
self._version = '3.4' # Software version, not IWXXM schema version.
self._annex3_amd = '78'
#
self.NameSpaces = {'aixm': 'http://www.aixm.aero/schema/5.1.1',
'gml': 'http://www.opengis.net/gml/3.2',
'': des.IWXXM_URI,
'xlink': 'http://www.w3.org/1999/xlink',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
#
# For US extension blocks
self.usTAFAmendmentParameters = {'None': {'href': '%s/NONE' % des.US_TAF_CODE_REGISTRY_URL,
'title': 'No amendments will be issued'},
'CLD': {'href': '%s/CEILING' % des.US_TAF_CODE_REGISTRY_URL,
'title': 'Amendments based on cloud ceilings will be issued'},
'VIS': {'href': '%s/VISIBILITY' % des.US_TAF_CODE_REGISTRY_URL,
'title': 'Amendments based on horizontal visibility will be issued'},
'WIND': {'href': '%s/WIND' % des.US_TAF_CODE_REGISTRY_URL,
'title': 'Amendments based on wind will be issued'},
'WX': {'href': '%s/WEATHER' % des.US_TAF_CODE_REGISTRY_URL,
'title': 'Amendments based on weather phenomenon will be issued'}}
#
self._re_cloudLyr = re.compile(r'(?P<AMT>VV|SKC|CLR|FEW|SCT|BKN|OVC)(?P<HGT>\d{3})?')
#
self._bbbCodes = {'A': 'AMENDMENT', 'C': 'CORRECTION'}
self.ForecastResults = ['vsby', 'wind', 'pcp', 'vcnty', 'obv', 'nsw', 'sky', 'llws']
#
self.parseCodeRegistryTables(codesFile, des.PreferredLanguageForTitles)
#
# map several token ids to a single function
setattr(self, 'obv', self.pcp)
setattr(self, 'vcnty', self.pcp)
def __call__(self, decodedTaf, tacString):
"""
A decoded TAF is re-assembled into an IWXXM XML document.
decodedTaf - TAF in python dictionary form which is obtained from AWIPS II TafDecoder.py
tacString - Original alphanumeric code form of TAF obtained from AvnFPS TAF Editor
"""
self.tacString = tacString
#
self.decodedTaf = decodedTaf
self.decodingFailure = False
self.iwxxmUSPrefix = False
#
# Root element
self.XMLDocument = ET.Element('TAF')
for prefix, uri in list(self.NameSpaces.items()):
if prefix == '':
self.XMLDocument.set('xmlns', uri)
else:
self.XMLDocument.set('xmlns:%s' % prefix, uri)
#
# Count how many non-Annex 3 elements found.
if self.nonAnnexElementsCount() > 0:
self.iwxxmUSPrefix = True
self.XMLDocument.set('xmlns:%s' % 'iwxxm-us', des.IWXXM_US_URI)
if self.iwxxmUSPrefix:
self.XMLDocument.set('xsi:schemaLocation', '%s %s %s %s' %
(des.IWXXM_URI, des.IWXXM_URL, des.IWXXM_US_URI, des.IWXXM_US_URL))
else:
self.XMLDocument.set('xsi:schemaLocation', '%s %s' % (des.IWXXM_URI, des.IWXXM_URL))
self.XMLDocument.set('reportStatus', self._bbbCodes.get(self.decodedTaf['bbb'][0], 'NORMAL'))
self.XMLDocument.set('permissibleUsage', 'OPERATIONAL')
#
# If there was a decoding problem, set the attributes to indicate this. However, this should
# never happen in AWIPS provided the TAF Syntax Check works and is not overridden by forecaster.
#
if 'err_msg' in self.decodedTaf:
self.XMLDocument.set('translationFailedTAC', self.tacString)
self.XMLDocument.set('permissibleUsageSupplementary', self.decodedMetar.get('err_msg'))
self.decodingFailure = True
self.doIt()
return self.XMLDocument
def doIt(self):
#
# Issuance time and Aerodrome identifier should always be available
self.itime(self.XMLDocument, self.decodedTaf['itime'])
self.aerodrome(self.XMLDocument, self.decodedTaf['ident'])
#
try:
self.vtime(ET.SubElement(self.XMLDocument, 'validPeriod'),
self.decodedTaf['vtime'])
if self.decodingFailure:
return
#
# No valid time for NIL TAF
except KeyError:
self.XMLDocument._children.pop()
#
# Set the "base" forecast, which is the initial prevailing condition of the TAF
try:
base = self.decodedTaf['group'].pop(0)
try:
self.baseFcst(self.XMLDocument, base['prev'])
self.changeGroup(self.XMLDocument, base['ocnl'])
except KeyError:
pass
#
# There is no initial forecast if TAF NIL'd
except IndexError:
indent = ET.SubElement(self.XMLDocument, 'baseForecast')
indent.set('nilReason', des.NIL_MSSG_URL)
#
# Now the rest of the forecast "evolves" from the initial condition
for group in self.decodedTaf['group']:
self.changeGroup(self.XMLDocument, group['prev'])
try:
self.changeGroup(self.XMLDocument, group['ocnl'])
except KeyError:
pass
#
# Find limits to amendments, if any
try:
extension = ET.Element('extension')
self.amd(extension, self.decodedTaf['amd'])
self.XMLDocument.append(extension)
except KeyError:
pass
#
# Option to provide URL to NWS Instruction on TAFs
if des.NWSI10813_INSERTION:
extension = ET.SubElement(self.XMLDocument, 'extension')
if self.iwxxmUSPrefix:
metaData = ET.SubElement(extension, 'iwxxm-us:USMetadata')
procedure = ET.SubElement(metaData, 'iwxxm-us:procedure')
else:
metaData = ET.SubElement(extension, 'USMetadata')
metaData.set('xmlns', des.IWXXM_US_URI)
metaData.set('xsi:schemaLocation', '%s %s' % (des.IWXXM_US_URI, des.IWXXM_US_URL))
procedure = ET.SubElement(metaData, 'procedure')
procedure.set('xlink:href', des.NWSI10813_URL)
procedure.set('xlink:title', des.NWSI10813_TITLE)
def itime(self, parent, token):
value = token['value']
parent.set('gml:id', 'uuid.%s' % uuid.uuid4())
indent1 = ET.SubElement(parent, 'issueTime')
indent2 = ET.SubElement(indent1, 'gml:TimeInstant')
indent2.set('gml:id', 'uuid.%s' % uuid.uuid4())
indent3 = ET.SubElement(indent2, 'gml:timePosition')
indent3.text = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(value))
def aerodrome(self, parent, token):
indent = ET.SubElement(parent, 'aerodrome')
indent1 = ET.SubElement(indent, 'aixm:AirportHeliport')
indent1.set('gml:id', 'uuid.%s' % uuid.uuid4())
indent2 = ET.SubElement(indent1, 'aixm:timeSlice')
indent3 = ET.SubElement(indent2, 'aixm:AirportHeliportTimeSlice')
indent3.set('gml:id', 'uuid.%s' % uuid.uuid4())
indent4 = ET.SubElement(indent3, 'gml:validTime')
indent4 = ET.SubElement(indent3, 'aixm:interpretation')
indent4.text = 'SNAPSHOT'
indent4 = ET.SubElement(indent3, 'aixm:designator')
indent4.text = token['str']
try:
indent4 = ET.Element('aixm:name')
indent4.text = token['name'].strip().upper()
if len(indent4.text):
indent3.append(indent4)
except KeyError:
pass
#
indent4 = ET.SubElement(indent3, 'aixm:locationIndicatorICAO')
indent4.text = token['str']
try:
indent4 = ET.Element('aixm:ARP')
indent5 = ET.SubElement(indent4, 'aixm:ElevatedPoint')
indent6 = ET.SubElement(indent5, 'gml:pos')
indent6.text = ' '.join(token['location'].split(' ')[:2])
indent5.set('srsDimension', des.srsDimension)
indent5.set('srsName', des.srsName)
indent5.set('axisLabels', 'Lat Long')
indent5.set('gml:id', 'uuid.%s' % uuid.uuid4())
#
# If vertical datum information is known, then use it.
if des.srsDimension == '3':
try:
indent6 = ET.Element('aixm:elevation')
indent6.text = token['location'].split(' ')[2]
indent6.set('uom', des.elevationUOM)
indent5.append(indent6)
indent6 = ET.SubElement(indent5, 'aixm:verticalDatum')
indent6.text = des.verticalDatum
except IndexError:
pass
indent3.append(indent4)
except KeyError:
pass
def vtime(self, parent, token):
indent = ET.SubElement(parent, 'gml:TimePeriod')
indent.set('gml:id', 'uuid.%s' % uuid.uuid4())
indent1 = ET.SubElement(indent, 'gml:beginPosition')
indent1.text = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(token['from']))
indent1 = ET.SubElement(indent, 'gml:endPosition')
indent1.text = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(token['to']))
def baseFcst(self, parent, token):
indent = ET.SubElement(parent, 'baseForecast')
indent1 = ET.SubElement(indent, 'MeteorologicalAerodromeForecast')
self.vtime(ET.SubElement(indent1, 'phenomenonTime'), token['time'])
#
# Finally the "base" forecast
self.result(indent1, token, True)
def changeGroup(self, parent, fcsts):
if type(fcsts) == type({}):
fcsts = [fcsts]
for token in fcsts:
indent = ET.SubElement(parent, 'changeForecast')
indent1 = ET.SubElement(indent, 'MeteorologicalAerodromeForecast')
self.vtime(ET.SubElement(indent1, 'phenomenonTime'), token['time'])
self.result(indent1, token)
def result(self, parent, token, baseFcst=False):
parent.set('cloudAndVisibilityOK', 'false')
if not baseFcst:
if token['type'] == 'PROB':
parent.set('changeIndicator', 'PROBABILITY_30')
elif token['type'] == 'TEMPO':
parent.set('changeIndicator', 'TEMPORARY_FLUCTUATIONS')
else:
parent.set('changeIndicator', 'FROM')
parent.set('gml:id', 'uuid.%s' % uuid.uuid4())
#
for element in self.ForecastResults:
function = getattr(self, element)
try:
function(parent, token[element])
except KeyError:
pass
def wind(self, parent, token):
indent = ET.SubElement(parent, 'surfaceWind')
indent1 = ET.Element('AerodromeSurfaceWindForecast')
if token['str'].startswith('VRB'):
indent1.set('variableWindDirection', 'true')
else:
try:
indent1.set('variableWindDirection', 'false')
indent2 = ET.Element('meanWindDirection')
indent2.text = str(token['dd'])
indent2.set('uom', 'deg')
except KeyError:
pass
indent1.append(indent2)
try:
indent2 = ET.Element('meanWindSpeed')
indent2.text = str(token['ff'])
indent2.set('uom', '[kn_i]')
indent1.append(indent2)
except KeyError:
pass
try:
indent2 = ET.Element('windGustSpeed')
indent2.text = str(token['gg'])
indent2.set('uom', '[kn_i]')
indent1.append(indent2)
except KeyError:
pass
if len(indent1):
indent.append(indent1)
def vsby(self, parent, token):
indent = ET.SubElement(parent, 'prevailingVisibility')
indent.set('uom', 'm')
indent.text = str(self.checkVisibility(token['value'], '[mi_i]'))
#
# Visbility above 6SM (P6SM)
if token['value'] > 7:
indent = ET.SubElement(parent, 'prevailingVisibilityOperator')
indent.text = 'ABOVE'
def pcp(self, parent, token):
for ww in token['str'].split():
#
# Search BUFR table
try:
codes = self.codes[ww]
indent = ET.SubElement(parent, 'weather')
indent.set('xlink:href', codes[0])
if (des.TITLES & des.Weather):
indent.set('xlink:title', codes[1])
#
# Initial weather phenomenon token not matched
except KeyError:
self.wxrPhenomenonSearch(parent, ww)
def wxrPhenomenonSearch(self, parent, ww):
#
# Split the weather string into two; both pieces must be found
pos = -2
ww1 = ww[:pos]
ww2 = ww[pos:]
while len(ww1) > 1:
try:
codes1 = self.codes[ww1]
codes2 = self.codes[ww2]
indent = ET.SubElement(parent, 'weather')
indent.set('xlink:href', codes1[0])
if (des.TITLES & des.Weather):
indent.set('xlink:title', codes1[1])
indent = ET.SubElement(parent, 'weather')
indent.set('xlink:href', codes2[0])
if (des.TITLES & des.Weather):
indent.set('xlink:title', codes2[1])
break
except KeyError:
pos -= 2
ww1 = ww[:pos]
ww2 = ww[pos:]
def nsw(self, parent, ignored):
indent = ET.SubElement(parent, 'weather')
indent.set('nilReason', des.NIL_NOOPRSIG_URL)
def sky(self, parent, token):
indent = ET.SubElement(parent, 'cloud')
for numberLyr, layer in enumerate(token['str'].split()):
if layer[:2] == 'VV':
try:
indent1 = ET.SubElement(indent, 'AerodromeCloudForecast')
indent1.set('gml:id', 'uuid.%s' % uuid.uuid4())
height = int(layer[2:]) * 100
indent2 = ET.Element('verticalVisibility')
indent2.text = str(height)
indent2.set('uom', '[ft_i]')
indent1.append(indent2)
except ValueError:
parent.remove(indent)
elif layer == 'NSC':
indent.set('nilReason', des.NIL_NOOPRSIG_URL)
else:
if numberLyr == 0:
indent1 = ET.SubElement(indent, 'AerodromeCloudForecast')
indent1.set('gml:id', 'uuid.%s' % uuid.uuid4())
self.doCloudLayer(indent1, layer)
def doCloudLayer(self, parent, layer):
indent = ET.SubElement(parent, 'layer')
indent1 = ET.SubElement(indent, 'CloudLayer')
desc = self._re_cloudLyr.match(layer)
try:
amount = desc.group('AMT')
indent2 = ET.Element('amount')
indent2.set('xlink:href', '%s%s' % (des.CLDCVR_URL, amount))
if (des.TITLES & des.CloudAmt):
indent2.set('xlink:title', des.CldCvr[amount])
indent1.append(indent2)
except TypeError:
return
indent2 = ET.SubElement(indent1, 'base')
indent2.set('uom', '[ft_i]')
try:
height = int(desc.group('HGT')) * 100
indent2.text = str(height)
except TypeError:
if amount in ['CLR', 'SKC']:
indent2.set('uom', 'N/A')
indent2.set('xsi:nil', 'true')
indent2.set('nilReason', des.NIL_NA_URL)
if layer.endswith('CB'):
indent2 = ET.SubElement(indent1, 'cloudType')
indent2.set('xlink:href', des.CUMULONIMBUS)
if layer.endswith('TCU'):
indent2 = ET.SubElement(indent1, 'cloudType')
indent2.set('xlink:href', des.TWRNGCUMULUS)
def llws(self, parent, token):
child = ET.SubElement(parent, 'extension')
NonConvectiveLLWS = ET.SubElement(child, 'iwxxm-us:NonConvectiveLowLevelWindShear')
LLWSDir = ET.SubElement(NonConvectiveLLWS, 'iwxxm-us:windDirection')
LLWSSpd = ET.SubElement(NonConvectiveLLWS, 'iwxxm-us:windSpeed')
self.layerAboveAerodrome(NonConvectiveLLWS, str(token['hgt'] * 100), '0', '[ft_i]')
LLWSDir.set('uom', 'deg')
LLWSSpd.set('uom', '[kn_i]')
LLWSDir.text = str(token['dd'])
LLWSSpd.text = str(token['ff'])
def layerAboveAerodrome(self, parent, upper, lower, uom):
child = ET.SubElement(parent, 'iwxxm-us:layerAboveAerodrome')
lowerLimit = ET.SubElement(child, 'iwxxm-us:lowerLimit')
upperLimit = ET.SubElement(child, 'iwxxm-us:upperLimit')
lowerLimit.set('uom', uom)
lowerLimit.text = lower
upperLimit.set('uom', uom)
upperLimit.text = upper
def amd(self, parent, limits):
#
# Get references to time
alist = []
s = limits['str']
_TimePhrase = '(AFT|TIL)\s+(\d{6})|(\d{4}/\d{4})'
_AmdPat = re.compile(r'AMD\s+NOT\s+SKED(\s+(%s))?|AMD\s+LTD\s+TO(\s+(CLD|VIS|WX|AND|WIND)){1,5}(\s+(%s))?' % (_TimePhrase, _TimePhrase))
tms = list(time.gmtime(self.decodedTaf['vtime']['from']))
m = _AmdPat.match(s)
if m:
#
# If reference to time is found in the AMD clause one of these
# groups will have it.
#
timestr = m.group(4) or m.group(5) or m.group(11) or m.group(12)
#
# (AFT|TIL) DDHHMM clause
if m.group(4) or m.group(11):
tms[2:6] = int(timestr[:2]), int(timestr[2:4]), int(timestr[-2:]), 0
self.fix_date(tms)
if (m.group(3) or m.group(10)) == 'TIL':
limits['time'] = {'from': self.decodedTaf['itime']['value'], 'to': time.mktime(tuple(tms))}
elif (m.group(3) or m.group(10)) == 'AFT':
limits['time'] = {'from': time.mktime(
tuple(tms)), 'to': self.decodedTaf['vtime']['to']}
#
# The D1H1/D2H2 case
elif m.group(5) or m.group(12):
for key, timestr in zip(['from', 'to'], timestr.split('/')):
tms[2:6] = int(timestr[0:2]), int(timestr[2:4]), 0, 0
self.fix_date(tms)
alist.append((key, time.mktime(tuple(tms))))
limits['time'] = dict(alist)
#
# If no reference to time, then its the entire time period of the TAF
else:
limits['time'] = self.decodedTaf['vtime'].copy()
limits['time']['from'] = self.decodedTaf['itime']['value']
TAFAmendmentLimitations = ET.SubElement(parent, 'iwxxm-us:TAFAmendmentLimitations')
if limits['str'].find('AMD NOT SKED') == 0:
amdTAFParameter = ET.SubElement(TAFAmendmentLimitations, 'iwxxm-us:amendableTAFParameter')
amdTAFParameter.set('xlink:href', self.usTAFAmendmentParameters['None']['href'])
else:
for parameter in ['CLD', 'VIS', 'WIND', 'WX']:
if limits['str'].find(parameter) > 0:
amdTAFParameter = ET.SubElement(TAFAmendmentLimitations, 'iwxxm-us:amendableTAFParameter')
amdTAFParameter.set('xlink:href', self.usTAFAmendmentParameters[parameter]['href'])
periodOfLimitation = ET.SubElement(TAFAmendmentLimitations, 'iwxxm-us:periodOfLimitation')
periodOfLimitation.set('gml:id', 'uuid.%s' % uuid.uuid4())
beginPosition = ET.SubElement(periodOfLimitation, 'gml:beginPosition')
beginPosition.text = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(limits['time']['from']))
endPosition = ET.SubElement(periodOfLimitation, 'gml:endPosition')
endPosition.text = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(limits['time']['to']))
def nonAnnexElementsCount(self):
count = 0
if 'amd' in self.decodedTaf:
count = 1
for g in self.decodedTaf['group']:
if 'llws' in g['prev']:
count += 1
return count
def parseCodeRegistryTables(self, fname, preferredLanguage='en'):
events = 'start', 'start-ns'
top = None
nameSpaces = {'xml':'http://www.w3.org/XML/1998/namespace'}
self.codes = {}
neededNS = ['ldp', 'skos', 'rdf', 'rdfs']
#
for event, elem in ET.iterparse(fname, events):
if event == 'start' and top == None:
top = elem
elif neededNS and event == 'start-ns':
if elem[0] in neededNS:
nameSpaces[elem[0]] = elem[1]
neededNS.remove(elem[0])
#
# Now that we have the required namespaces for searches
Containers = '{%s}Container' % nameSpaces.get('ldp')
Concept = '{%s}Concept' % nameSpaces.get('skos')
about = '{%s}about' % nameSpaces.get('rdf')
label = '{%s}label[@{%s}lang="%s"]' % (nameSpaces.get('rdfs'), nameSpaces.get('xml'), preferredLanguage)
enlabel = '{%s}label[@{%s}lang="%s"]' % (nameSpaces.get('rdfs'), nameSpaces.get('xml'), 'en')
nolang = '{%s}label' % nameSpaces.get('rdfs')
root = ET.ElementTree(top)
for container in root.iter(Containers):
uri = container.get(about)
for concept in container.iter(Concept):
try:
uri = concept.get(about)
key = uri[uri.rfind('/') + 1:]
text = ''
try:
text = concept.find(label).text
except AttributeError:
if preferredLanguage != 'en':
text = concept.find(enlabel).text
else:
text = concept.find(nolang).text
finally:
self.codes[key] = (uri, text)
except AttributeError:
pass
#
# Returns values (in meters) according to Annex 3 Amd 77
def checkVisibility(self, value, uom='m'):
if type(value) == type(''):
def function(x):
return str(x)
value = float(value)
else:
def function(x):
return int(x)
if uom == '[mi_i]':
value *= 1609.34
elif uom == '[ft_i]':
value *= 0.3048
mod = 1
value = int(value)
if value < 800:
mod = 50
elif 800 <= value < 5000:
mod = 100
elif value < 9999:
mod = 1000
else:
value = 10000
return function(value - (value % mod))
def fix_date(self, tms):
now = time.time()
t = time.mktime(tuple(tms))
if t > now + 86400.0: # previous month
if tms[1] > 1:
tms[1] -= 1
else:
tms[1] = 12
tms[0] -= 1
elif t < now - 25 * 86400.0: # next month
if tms[1] < 12:
tms[1] += 1
else:
tms[1] = 1
tms[0] += 1
class XmlTafEncoder():
"""
Constructs MeteorologicalBulletins consisting of one or more IWXXM TAF documents
having the same WMO Abbreviated Header Line (AHL)
"""
def __init__(self):
#
# Initialize the TAF decoder and encoder
self.encoder = Encoder()
self.decoder = TD.Decoder()
#
# Cache of IWXXM documents grouped together by AHL
self.docs = {}
#
# Regular expression to determine type of issuance:
# routine, amended, corrected, or routinely delayed
self.prefix = re.compile(r'TAF(\s+([ACR][A-Z]{2}))?\s+[KPT]\w{3}\s+\d{6}Z')
#
# Convert TAF TAC to IWXXM XML
def encode(self, tac, ahl, geolocation):
"""
The traditional alphanumeric (TAC) text is converted into IWXXM form and cached for
later retrieval
tac = TAF text
ahl = Abbreviated Header Line, consisting of 'LTAAii CCCC YYGG00 BBB'
geolocation = string with the latitude, longitude and elevation of the airport
reference point (ARP) separated by spaces.
latitude and longitude are floating point numbers, e.g 30' (minutes) == 0.5
elevation shall be specified in meters w.r.t a known vertical datum (see
xmlConfig.py) for allowed values.
"""
#
# The TAF decoder needs to know the BBB code
try:
bbb = ahl.split(' ')[3]
if bbb[0] == '_':
bbb = ' '
#
# Remove the underscore BBB code from the key
ahl = ' '.join(ahl.split(' ')[:3])
except IndexError:
bbb = ' '
re_start = self.prefix.search(tac)
#
# Call AWIPS II AvnFPS TAF Decoder
try:
tafDictionary = self.decoder(tac[re_start.start():], bbb)
tafDictionary['ident']['location'] = geolocation
#
# If decoding the TAF failed (should never happen)
except KeyError:
_Logger.exception('Unable to decode TAF:\n%s', tac)
#
# Build the XML TAF document and place into the cache.
self.docs.setdefault(ahl, []).append(self.encoder(tafDictionary, tac))
def write(self):
"""
Return <MeteorologicalBulletin> documents until cache of unique 'AHL'
codes is exhausted.
"""
import JUtil
#
# Check to see if there's anything to write to the file. If not, log and return
#
if not self.docs:
_Logger.info('No more MeteorologicalBulletin messages to send.')
return None
#
# The IWXXM TAF product(s) needs to be wrapped up in a Meteorological Bulletin "envelope".
#
# Construct the root element
bulletin = ET.Element('MeteorologicalBulletin')
bulletin.set('xmlns', 'http://def.wmo.int/collect/2014')
bulletin.set('xmlns:gml', 'http://www.opengis.net/gml/3.2')
bulletin.set('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
bulletin.set('xsi:schemaLocation',
'http://def.wmo.int/collect/2014 http://schemas.wmo.int/collect/1.2/collect.xsd')
bulletin.set('gml:id', 'uuid.%s' % uuid.uuid4())
#
# For each write() call, a unique AHL IWXXM XML bulletin is made
keys = list(self.docs.keys())
try:
self.ahl = keys.pop()
except IndexError:
return None
#
# Construct the WMO AHL Line
try:
ttaaii, cccc, yygg, bbb = self.ahl.split(' ')
except ValueError:
ttaaii, cccc, yygg = self.ahl.strip().split(' ')
bbb = ''
if bbb == '':
_Logger.info('Sending %d routinely issued TAFs in a MeteorologicalBulletin.', len(self.docs[self.ahl]))
else:
_Logger.info('Sending %d %s (%s) TAFs in a MeteorologicalBulletin',
len(self.docs[self.ahl]),
{ 'A': 'amended',
'C': 'corrected',
'R': 'routinely delayed'
}.get(bbb[0]),
bbb
)
#
while True:
try:
iwxxm = self.docs[self.ahl].pop()
child = ET.SubElement(bulletin, 'meteorologicalInformation')
child.append(iwxxm)
except IndexError:
del self.docs[self.ahl]
break
#
# Construct the full day/time stamp for the product
child = ET.SubElement(bulletin, 'bulletinIdentifier')
#
# Construct the name of the bulletin
child.text = 'A_%s%s%s%s_C_%s_%s.xml' % (ttaaii, cccc, yygg, bbb, cccc, time.strftime('%Y%m%d%H%M%S'))
#
# Serialize
tree = ET.ElementTree(element=bulletin)
xmlBytes = io.BytesIO()
tree.write(xmlBytes, encoding="UTF-8", xml_declaration=True, method="xml", short_empty_elements=True)
xmlBytes = xmlBytes.getvalue().replace(' />'.encode("UTF-8"), '/>'.encode("UTF-8"))
#
# Write XML document to AWIPS outgoing directory
filename = os.path.join(des.MHS_OUTPUT_FULL_PATH_DIR, child.text)
try:
with open(filename, 'wb') as _fh:
_fh.write(f"{self.ahl}\n".encode("UTF-8"))
_fh.write(xmlBytes)
except OSError:
_Logger.exception("Error writing XML TAF document to %s", filename)
return None
mhsI = "-i%s" % (self.ahl)
mhsE = "-e%s" % (filename)
mhsArgs = [des.MHS_CODE, des.MHS_SUBJECT, des.MHS_ADDRESSEE, des.MHS_PRIORITY, mhsI, mhsE]
return JUtil.pyValToJavaObj(mhsArgs)
def getAHL(self):
"""Return the unique AHL line"""
return self.ahl