Skip to content

Commit

Permalink
Additional text added to README.md; Added new flag to bulletin.write(…
Browse files Browse the repository at this point in the history
…) method. Added new tests for bulletin object.
  • Loading branch information
mgoberfield committed Oct 8, 2021
1 parent 3dc4370 commit 7e7b30b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 9 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,17 @@ This means that for the METAR, SPECI and TAF, the product starts with one of tho
The WMO AHL line in the TAC file is crucial in forming the proper filename for the IWXXM Meteorological Bulletin, which is shown in the 'IWXXM XML file" text field. The format of the filename follows the specifications outlined for Aviation XML products in WMO No. 368 Manual on the Global Telecommunication System.

## Bulletins
Every encoder, after processing a TAC message successfully, returns an object of the class [Bulletin](https://github.com/NOAA-MDL/GIFTs/blob/master/gifts/common/bulletin.py). The Bulletin object has similarities to a python list object: it has a "length" (the number of IWXXM XML reports); can be indexed; can be iterated; and [ElementTree](https://docs.python.org/3/library/xml.etree.elementtree.html) reports added and removed with the usual python list operators. In addition to the built-in list operations, python's [print()](https://docs.python.org/3/library/functions.html#print) function will nicely format (for human eyes) the bulletin object and write out the complete XML document.
Every encoder, after processing a TAC message successfully, returns an object of the class [Bulletin](https://github.com/NOAA-MDL/GIFTs/blob/master/gifts/common/bulletin.py). The Bulletin object has similarities to a python list object: it has a "length" (the number of IWXXM XML reports); can be indexed; can be iterated; and [ElementTree](https://docs.python.org/3/library/xml.etree.elementtree.html) reports added and removed with the usual python list operations. In addition to the built-in list operations, python's [print()](https://docs.python.org/3/library/functions.html#print) function will nicely format (for human eyes) the bulletin object and write out the complete XML document to a file (default is sys.stdout).

For international distribution, IWXXM reports, due to their increased character length and expanded character set, shall be set over the Extended ATS Message Handling System (AMHS) as a File Transfer Body Part.<sup>2</sup> The Bulletin class provides a convenient [write()](https://github.com/NOAA-MDL/GIFTs/blob/master/gifts/common/bulletin.py#L177) method to generate the `<MeterologicalBulletin>`<sup>3</sup> XML document for transmission over the AMHS.
For international distribution, IWXXM reports, due to their increased character length and expanded character set, shall be sent over the Extended ATS Message Handling System (AMHS) as a File Transfer Body Part.<sup>2</sup> The Bulletin class provides a convenient [write()](https://github.com/NOAA-MDL/GIFTs/blob/master/gifts/common/bulletin.py#L177) method to generate the `<MeterologicalBulletin>`<sup>3</sup> XML document for transmission over the AMHS.

Because of the character length of the `<MeteorologicalBulletin>`, the File Transfer Body Part shall be a compressed file using the gzip protocol. By default, the `.encode()` method of the [Encoder](https://github.com/NOAA-MDL/GIFTs/blob/master/gifts/common/Encoder.py#L15) class is to generate an uncompressed file when the bulletin.write() method is invoked. To generate a compressed `<MeteorologicalBulletin>` file for transmission over the AMHS, supply an additional argument to the `.encode()` method, like so:

Encoder.encode(tacString, xml='xml.gz')
An alternative method is to set the `compress` flag to True in the Bulletin object's write() method, like so:

bulletin.write(compress=True)
Either method will generate a gzip file containing the `<MeteorologicalBulletin>` suitable for transmission over the AMHS.

## Caveats
The decoders were written to follow Annex 3 specifications for the TAC forms. If your observations or forecast products deviate significantly from Annex 3, then this software will likely refuse to encode the data into IWXXM. Fortunately, solutions can be readily found, ranging from trivial to challenging (see United States METAR/SPECI [reports](https://nws.weather.gov/schemas/iwxxm-us/3.0/examples/metars)).
Expand Down
16 changes: 11 additions & 5 deletions gifts/common/bulletin.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _write(self, obj, header):
else:
obj(result)

def write(self, obj=None, header=False):
def write(self, obj=None, header=False, compress=False):
"""ElementTree to a file or stream.
obj - if none provided, XML is written to current working directory, or
Expand All @@ -187,15 +187,21 @@ def write(self, obj=None, header=False):
the file is no longer valid XML.
File extension indicated on <bulletinIdentifer> element's value determines
whether compression is done. (Only gzip is permitted at this time)"""
whether compression is done OR the compress flag is set to True. (Only gzip is permitted at this time)"""

if self._bulletinID[-2:] == 'gz':
self._canBeCompressed = False
if compress or self._bulletinID[-2:] == 'gz':
if 'gzip' in globals().keys():
self._canBeCompressed = True
else:
raise SystemError('No capability to compress files using gzip()')
else:
self._canBeCompressed = False
#
# Do not include WMO AHL line in compressed files
if self._canBeCompressed:

header = False
if self._bulletinID[-2:] != 'gz':
self._bulletinID = '{}.gz'.format(self._bulletinID)
#
# Generate the bulletin for export to file or stream
self._export()
Expand Down
49 changes: 47 additions & 2 deletions tests/test_bulletin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import print_function
import os
import pytest
import tempfile
import gzip

import gifts.common.bulletin as bulletin
from gifts.TAF import Encoder as TE
Expand Down Expand Up @@ -218,11 +218,56 @@ def test_operations():
assert len(collective3) == 2


def test_compression():

taf_test = """FTUS43 KBOU 081800 CCA
TAF SBAF 101800Z NIL=
"""
collective = tafEncoder.encode(taf_test, xml='xml.gz')
#
id = collective.get_bulletinIdentifier()
assert id[-2:] == 'gz'
#
# Verify that compressed file is written and without WMO AHL
# (header is ignored)
#
collective.write(header=True)
try:
_fh = gzip.open(id)
except Exception as exc:
assert False, f"Attempt to open gzip file failed {exc}"

first_line = _fh.readline().decode('utf-8')
assert first_line != 'LTUS43 KBOU 081800 CCA\n'
_fh.close()
os.unlink(id)

taf_test = """FTUS41 KCAE 101200
TAF SBAF 101800Z NIL=
"""
collective = tafEncoder.encode(taf_test)
id = collective.get_bulletinIdentifier()
assert id[-3:] == 'xml'

collective.write(compress=True)
nid = collective.get_bulletinIdentifier()
assert nid[-2:] == 'gz'

try:
_fh = gzip.open(nid)
except Exception as exc:
assert False, f"Attempt to open gzip file failed {exc}"

_fh.close()
os.unlink(nid)


if __name__ == '__main__':

test_empty()
test_unlike()
test_realize()
test_operations()
test_writes()
test_header_option()
test_operations()
test_compression()

0 comments on commit 7e7b30b

Please sign in to comment.