1731 lines
59 KiB
Python
1731 lines
59 KiB
Python
##
|
|
# This software was developed and / or modified by Raytheon Company,
|
|
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
|
|
#
|
|
# U.S. EXPORT CONTROLLED TECHNICAL DATA
|
|
# This software product contains export-restricted data whose
|
|
# export/transfer/disclosure is restricted by U.S. law. Dissemination
|
|
# to non-U.S. persons whether in the United States or abroad requires
|
|
# an export license or other authorization.
|
|
#
|
|
# Contractor Name: Raytheon Company
|
|
# Contractor Address: 6825 Pine Street, Suite 340
|
|
# Mail Stop B8
|
|
# Omaha, NE 68106
|
|
# 402.291.0100
|
|
#
|
|
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
|
|
# further licensing information.
|
|
##
|
|
#
|
|
# Name:
|
|
# TafDecoder.py
|
|
# GFS1-NHD:A7822.0000-SCRIPT;1.65
|
|
#
|
|
# Status:
|
|
# DELIVERED
|
|
#
|
|
# History:
|
|
# Revision 1.65 (DELIVERED)
|
|
# Created: 21-OCT-2009 21:53:54 OBERFIEL
|
|
# VC events not handled correctly for VRB winds.
|
|
#
|
|
# Revision 1.64 (DELIVERED)
|
|
# Created: 21-SEP-2009 07:14:57 OBERFIEL
|
|
# Fixed monitoring of TEMPO groups at start of forecast.
|
|
# Monitoring corrected TAFs also fixed.
|
|
# Showers are considered convective and so VRB with speeds >
|
|
# 6kts is allowed.
|
|
#
|
|
# Revision 1.63 (DELIVERED)
|
|
# Created: 16-JUN-2009 14:41:14 OBERFIEL
|
|
# Added checks on misuse of LLWS in conditional groups.
|
|
#
|
|
# Revision 1.62 (DELIVERED)
|
|
# Created: 15-MAY-2009 15:09:08 OBERFIEL
|
|
# Fixed crash when bad wind group in prevailing fcst is
|
|
# inherited by the occasional group
|
|
# TEMPO or PROB30.
|
|
#
|
|
# Revision 1.61 (REVIEW)
|
|
# Created: 14-APR-2009 10:36:34 OBERFIEL
|
|
# Added new checks for AMD NOT SKED and AMD LTD TO clauses,
|
|
# proper use of NSW and VRB winds.
|
|
#
|
|
# Revision 1.60 (REVIEW)
|
|
# Created: 30-MAR-2009 23:01:09 OBERFIEL
|
|
# Fixed typo in check_wind() routine.
|
|
#
|
|
# Revision 1.59 (REVIEW)
|
|
# Created: 30-MAR-2009 18:31:54 OBERFIEL
|
|
# Removed obsolete TAF format code. Fixed VRB winds with
|
|
# convection. Fixed NSW handling.
|
|
# Added checks for times in AMD clauses.
|
|
#
|
|
# Revision 1.58 (DELIVERED)
|
|
# Created: 10-MAR-2009 08:49:20 OBERFIEL
|
|
# Added new check for end of month, e.g. 31st April. Fixed
|
|
# typos and added overlooked check for wind shear in
|
|
# TEMPO/PROB groups.
|
|
#
|
|
# Revision 1.57 (REVIEW)
|
|
# Created: 10-DEC-2008 12:46:59 OBERFIEL
|
|
# Documented patched OB9 TafDecoder.py
|
|
#
|
|
# Revision 1.56 (DELIVERED)
|
|
# Created: 10-DEC-2008 12:40:20 OBERFIEL
|
|
# Documented patched OB8.3.1 TafDecoder.py
|
|
#
|
|
# Revision 1.55 (DELIVERED)
|
|
# Created: 02-DEC-2008 10:11:20 OBERFIEL
|
|
# Used fix_date routine to handle month-to-month and
|
|
# year-to-year transitions.
|
|
#
|
|
# Revision 1.54 (DELIVERED)
|
|
# Created: 19-NOV-2008 14:08:03 OBERFIEL
|
|
# Fixed swapped dates/hours in TEMPO and PROB30 groups
|
|
#
|
|
# Revision 1.53 (DELIVERED)
|
|
# Created: 31-OCT-2008 11:40:08 OBERFIEL
|
|
# Fixed beginning of month problem with TEMPO and PROB30
|
|
# groups
|
|
#
|
|
# Revision 1.52 (DELIVERED)
|
|
# Created: 29-OCT-2008 12:57:54 OBERFIEL
|
|
# Fixed end-of-month/beginning-of-next-month bug
|
|
#
|
|
# Revision 1.51 (DELIVERED)
|
|
# Created: 28-AUG-2008 15:28:43 OBERFIEL
|
|
# NIL AMD is now flagged. TAF COR is now allowed.
|
|
#
|
|
# Revision 1.50 (DELIVERED)
|
|
# Created: 01-AUG-2008 15:44:46 OBERFIEL
|
|
# Synch'd up with changes in OB8.3.1
|
|
#
|
|
# Revision 1.49 (DELIVERED)
|
|
# Created: 30-JUN-2008 14:42:35 OBERFIEL
|
|
# Initial guess for part-time TAF, pending consensus from
|
|
#
|
|
# Revision 1.48 (REVIEW)
|
|
# Created: 19-JUN-2008 14:19:35 OBERFIEL
|
|
# Allowed different TAF formats without raising fatal error.
|
|
#
|
|
# Revision 1.47 (DELIVERED)
|
|
# Created: 31-MAR-2008 12:48:21 OBERFIEL
|
|
# Corrected valid_day for OB8.3 release
|
|
#
|
|
# Revision 1.46 (DELIVERED)
|
|
# Created: 03-MAR-2008 10:08:20 OBERFIEL
|
|
# Fixed Leap-Day bug.
|
|
#
|
|
# Revision 1.45 (APPROVED)
|
|
# Created: 29-FEB-2008 09:55:46 OBERFIEL
|
|
# Fixed bug with leap-day. Made the logic a bit more clear.
|
|
#
|
|
# Revision 1.44 (DELIVERED)
|
|
# Created: 26-FEB-2008 14:25:36 OBERFIEL
|
|
# Fixed _talk notification (unimplemented) and DTG for Nov
|
|
# 2008
|
|
#
|
|
# Revision 1.43 (DELIVERED)
|
|
# Created: 27-JUN-2007 13:14:16 OBERFIEL
|
|
# MetarMonitorP.py revamped logic and created new rule for
|
|
# Alaska region.
|
|
# TafDecoder.py changed to allow variable AMD LTD element
|
|
# list
|
|
#
|
|
# Revision 1.42 (DELIVERED)
|
|
# Created: 14-MAY-2007 10:04:50 OBERFIEL
|
|
# Removed references to the obsolete prototype XTF product.
|
|
# Allow decoder and encoder to format TAF in two different
|
|
# ways. New format will be triggered by day and time to be
|
|
# specified at a later date.
|
|
#
|
|
# Revision 1.41 (DELIVERED)
|
|
# Created: 21-JUN-2006 08:57:36 TROJAN
|
|
# spr 7162: fixed start time of the first TAF group
|
|
#
|
|
# Revision 1.40 (DELIVERED)
|
|
# Created: 20-JUN-2006 08:28:29 TROJAN
|
|
# spr 7163: fixed start time of the first TAF group
|
|
#
|
|
# Revision 1.39 (DELIVERED)
|
|
# Created: 02-JUN-2006 10:28:09 TROJAN
|
|
# spr 7160: changed regular expression for visibility
|
|
#
|
|
# Revision 1.38 (DELIVERED)
|
|
# Created: 02-JUN-2006 09:04:22 TROJAN
|
|
# spr 7159: changed regular expression for visibility
|
|
#
|
|
# Revision 1.37 (DELIVERED)
|
|
# Created: 03-MAY-2006 13:49:15 TROJAN
|
|
# SPR 7139: fixed issue/valid time checks
|
|
#
|
|
# Revision 1.36 (DELIVERED)
|
|
# Created: 03-MAY-2006 13:28:39 TROJAN
|
|
# SPR 7140: fixed issue/valid time checks
|
|
#
|
|
# Revision 1.35 (DELIVERED)
|
|
# Created: 14-APR-2006 13:17:50 TROJAN
|
|
# spr 7118
|
|
#
|
|
# Revision 1.34 (DELIVERED)
|
|
# Created: 14-APR-2006 08:42:21 TROJAN
|
|
# spr 7117
|
|
#
|
|
# Revision 1.33 (APPROVED)
|
|
# Created: 14-APR-2006 08:24:42 TROJAN
|
|
# spr 7117
|
|
#
|
|
# Revision 1.32 (DELIVERED)
|
|
# Created: 27-MAR-2006 08:30:43 TROJAN
|
|
# spr 7112: added check for valid time of FM group for
|
|
# regular issuance TAFs
|
|
#
|
|
# Revision 1.31 (DELIVERED)
|
|
# Created: 19-JAN-2006 11:11:08 OBERFIEL
|
|
# Numerous changes to sync up platforms
|
|
#
|
|
# Revision 1.30 (APPROVED)
|
|
# Created: 03-NOV-2005 13:16:33 TROJAN
|
|
# spr 7051
|
|
#
|
|
# Revision 1.29 (APPROVED)
|
|
# Created: 12-OCT-2005 18:29:27 TROJAN
|
|
# spr 7038
|
|
#
|
|
# Revision 1.28 (DELIVERED)
|
|
# Created: 19-SEP-2005 13:47:39 TROJAN
|
|
# spr 7011
|
|
#
|
|
# Revision 1.27 (APPROVED)
|
|
# Created: 08-SEP-2005 14:40:39 TROJAN
|
|
# spr 7011
|
|
#
|
|
# Revision 1.26 (DELIVERED)
|
|
# Created: 19-AUG-2005 19:50:32 TROJAN
|
|
# spr 6999
|
|
#
|
|
# Revision 1.25 (APPROVED)
|
|
# Created: 17-AUG-2005 19:50:31 TROJAN
|
|
# spr 6992
|
|
#
|
|
# Revision 1.24 (APPROVED)
|
|
# Created: 16-AUG-2005 13:23:17 TROJAN
|
|
# spr 6987
|
|
#
|
|
# Revision 1.23 (APPROVED)
|
|
# Created: 12-AUG-2005 12:39:09 OBERFIEL
|
|
# Volcanic Ash can be forecasted regardless of visibility.
|
|
#
|
|
# Revision 1.22 (APPROVED)
|
|
# Created: 09-AUG-2005 15:02:46 TROJAN
|
|
# spr 6975
|
|
#
|
|
# Revision 1.21 (DELIVERED)
|
|
# Created: 26-JUL-2005 18:27:56 TROJAN
|
|
# spr 6946
|
|
#
|
|
# Revision 1.20 (APPROVED)
|
|
# Created: 06-JUL-2005 18:16:41 TROJAN
|
|
# spr 6548
|
|
#
|
|
# Revision 1.19 (DELIVERED)
|
|
# Created: 13-MAY-2005 18:56:28 TROJAN
|
|
# spr 6834
|
|
#
|
|
# Revision 1.18 (REVIEW)
|
|
# Created: 12-MAY-2005 14:20:56 TROJAN
|
|
# spr 6834
|
|
#
|
|
# Revision 1.17 (REVIEW)
|
|
# Created: 07-MAY-2005 11:38:28 OBERFIEL
|
|
# Added Item Header Block
|
|
#
|
|
# Revision 1.16 (DELIVERED)
|
|
# Created: 26-APR-2005 15:07:40 TROJAN
|
|
# spr 6812
|
|
#
|
|
# Revision 1.15 (REVIEW)
|
|
# Created: 26-APR-2005 14:12:30 TROJAN
|
|
# spr 6812
|
|
#
|
|
# Revision 1.14 (APPROVED)
|
|
# Created: 26-APR-2005 13:56:52 TROJAN
|
|
# spr 6812
|
|
#
|
|
# Revision 1.13 (DELIVERED)
|
|
# Created: 06-APR-2005 11:46:47 TROJAN
|
|
# spr 6776
|
|
#
|
|
# Revision 1.12 (APPROVED)
|
|
# Created: 04-APR-2005 15:12:25 TROJAN
|
|
# spr 6778
|
|
#
|
|
# Revision 1.11 (DELIVERED)
|
|
# Created: 01-MAR-2005 21:07:14 TROJAN
|
|
# spr 6689
|
|
#
|
|
# Revision 1.10 (DELIVERED)
|
|
# Created: 14-FEB-2005 20:54:51 TROJAN
|
|
# spr 6649
|
|
#
|
|
# Revision 1.9 (APPROVED)
|
|
# Created: 24-JAN-2005 21:18:47 TROJAN
|
|
# spr 6612
|
|
#
|
|
# Revision 1.8 (APPROVED)
|
|
# Created: 24-JAN-2005 17:49:56 TROJAN
|
|
# spr 6608
|
|
#
|
|
# Revision 1.7 (APPROVED)
|
|
# Created: 19-JAN-2005 15:13:59 TROJAN
|
|
# spr 6565
|
|
#
|
|
# Revision 1.6 (APPROVED)
|
|
# Created: 07-DEC-2004 19:18:35 TROJAN
|
|
# spr 6527
|
|
#
|
|
# Revision 1.5 (UNDER WORK)
|
|
# Created: 21-OCT-2004 19:33:49 TROJAN
|
|
# spr 6398
|
|
#
|
|
# Revision 1.4 (APPROVED)
|
|
# Created: 01-OCT-2004 13:38:14 TROJAN
|
|
# spr 6399
|
|
#
|
|
# Revision 1.3 (APPROVED)
|
|
# Created: 19-AUG-2004 20:49:16 OBERFIEL
|
|
# Code chage
|
|
#
|
|
# Revision 1.2 (APPROVED)
|
|
# Created: 09-JUL-2004 18:07:07 OBERFIEL
|
|
# Updated to fix problem with NSW
|
|
#
|
|
# Revision 1.1 (APPROVED)
|
|
# Created: 01-JUL-2004 14:45:02 OBERFIEL
|
|
# date and time created -2147483647/-2147483648/-2147481748
|
|
# -2147483648:-2147483648:-2147483648 by oberfiel
|
|
#
|
|
# Change Document History:
|
|
# 1:
|
|
# Change Document: GFS1-NHD_SPR_7431
|
|
# Action Date: 22-OCT-2009 20:05:48
|
|
# Relationship Type: In Response to
|
|
# Status: TEST
|
|
# Title: AvnFPS: OB9.2 installation breaks mtrs.cfg file
|
|
#
|
|
# Date Ticket# Engineer Description
|
|
# ------------ ---------- ----------- --------------------------
|
|
# 02APR2014 17211 zhao (code obtained from the listserver via Virgil that implements a new rule regarding CB, TS etc)
|
|
# May 12, 2014 16928 zhao Modified check_prev_time()
|
|
# Sep 17, 2014 16928 zhao Added a line break "\n" to message 25 (since it appears together with message 49)
|
|
# Jul 07, 2015 16973 zhao Added 'DRSN' as valid value of sig weather
|
|
# Mar 03, 2016 18664 zhao Fixed an indentation error in check_vsby_wx()
|
|
# Jan 15, 2018 7119 tgurney check_tempo_group() correctly handle invalid vsby in the "FM" Part
|
|
#
|
|
#
|
|
|
|
##
|
|
# This is a base file that is not intended to be overridden.
|
|
##
|
|
|
|
|
|
import re
|
|
import time
|
|
import traceback
|
|
import tpg
|
|
import Avn
|
|
import AvnLib
|
|
import AvnParser
|
|
|
|
###############################################################################
|
|
# local exceptions
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
class Warning1(Exception):
|
|
pass # avoiding name clash
|
|
|
|
###############################################################################
|
|
# dictionary of errors and warnings
|
|
_Errors = {
|
|
11: """Thunderstorm forecast requires CB in the cloud
|
|
group (NWSI 10-813, Appendix C, 1.2.7.3)""",
|
|
12: """Visibility <= 6SM requires forecast of significant
|
|
weather (NWSI 10-813, Appendix C, 1.2.5)""",
|
|
13: """Volcanic ash requires visibility forecast
|
|
(NWSI 10-813, Appendix C, 1.2.6)""",
|
|
14: """FG or FZFG forecast requires visibility < 5/8SM,
|
|
MIFG requires visibility >= 5/8SM
|
|
(NWSI 10-813, Appendix C, 1.2.6)""",
|
|
15: """BR forecast requires visibility between 5/8SM
|
|
and 6SM (NWSI 10-813, Appendix C, 1.2.6)""",
|
|
16: """PROB group must include forecast of a thunderstorm
|
|
or precipitation event (NWSI 10-813, Appendix C, 1.2.9.4)""",
|
|
17: """Invalid sky cover sequence
|
|
(NWSI 10-813, Appendix C, 1.2.7.1)""",
|
|
18: """Invalid weather with visibility >= 6SM
|
|
(NWSI 10-813, Appendix C, 1.2.5)""",
|
|
19: """Missing terminating blank""",
|
|
20: """Invalid datetime format""",
|
|
21: """Invalid end hour""",
|
|
22: """Invalid start hour""",
|
|
23: """Invalid day""",
|
|
24: """Issue and valid times do not match""",
|
|
25: """Group time period not within TAF forecast period\n""",
|
|
26: """Only PROB30 is allowed""",
|
|
27: """The PROB group shall not be used in the first
|
|
9 hours of the valid TAF forecast
|
|
(NWSI 10-813, Appendix C, 1.2.9.4)""",
|
|
28: """Weather in the vicinity shall not be used in TEMPO
|
|
or PROB groups (NWSI 10-813, Appendix C, 1.2.6.1)""",
|
|
29: """Wrong day of the month""",
|
|
30: """Invalid value of wind direction""",
|
|
31: """Invalid value of wind speed""",
|
|
32: """Wind gust <= wind speed""",
|
|
33: """Forecast of non-convective low-level wind shear
|
|
shall not be included in TEMPO or PROB groups
|
|
(NWSI 10-813, Appendix C, 1.2.8)""",
|
|
34: """Missing weather in this group""",
|
|
35: """Invalid value of wind shear direction""",
|
|
36: """Invalid value of wind shear height""",
|
|
37: """Invalid value of visibility
|
|
(NWSI 10-813, Appendix C, 1.2.5)""",
|
|
38: """Invalid value of sig weather
|
|
(NWSI 10-813, Appendix B, 4)""",
|
|
39: """Unnecessary leading zero for wind shear speed""",
|
|
40: """Invalid cloud amount
|
|
(NWSI 10-813, Appendix C, 1.2.7.1)""",
|
|
41: """Invalid cloud base
|
|
(NWSI 10-813, Appendix C, 1.2.7.1)""",
|
|
42: """Cannot forecast partial obscuration
|
|
(NWSI 10-813, Appendix C, 1.2.7.2)""",
|
|
43: """Invalid VC weather. Allowed entries:
|
|
VCFG, VCSH, VCTS.
|
|
(NWSI 10-813, Appendix C, 1.2.6.1)""",
|
|
44: """CLR shall not be used in TAF
|
|
(NWSI 10-813, Appendix C, 1.2.7.1)""",
|
|
45: """Invalid precipitation string
|
|
(NWSI 10-813, Appendix C, 1.2.6)""",
|
|
46: """P6SM needed with NSW in this group
|
|
(NWSI 10-813, Appendix C, 1.2.6)""",
|
|
47: """Consecutive NSW groups not permitted
|
|
(NWSI 10-813, Appendix C, 1.2.6)""",
|
|
48: """When reduction in visibility is forecast
|
|
to change in TEMPO group, the significant weather
|
|
causing the deterioration shall be included
|
|
(NWSI 10-813, Appendix C, 1.2.9.2)""",
|
|
49: """Valid time of FM group must be > valid time for
|
|
the entire TAF or a previous FM group""",
|
|
50: """Valid time must be >= valid time of previous
|
|
TEMPO or PROB group""",
|
|
51: """Bad TEMPO/PROB group duration""",
|
|
52: """Valid time of TEMPO/PROB must be >= valid time
|
|
of previous FM group""",
|
|
53: """+PL requires visibility < 3SM
|
|
PL requires visibility <= 6SM (FMH#1, p 8-3)""",
|
|
54: """+SN or +DZ requires visibility <= 1/4SM
|
|
SN or DZ requires visibility <= 1/2SM
|
|
(FMH#1, p 8-3)""",
|
|
55: """+SS or +DS requires visibility <= 1/4SM
|
|
SS or DS requires visibility <= 1/2SM
|
|
(NWSI 10-813, B-4)""",
|
|
56: """The period covered by a TEMPO group will not
|
|
exceed 4 hours (NWSI 10-813, 4.3.4)""",
|
|
57: """Repeated occurence of weather elements""",
|
|
59: """Invalid AMD phrase. Valid phrases are:
|
|
AMD NOT SKED
|
|
AMD NOT SKED AFT ddHHmm
|
|
AMD NOT SKED TIL ddHHmm
|
|
AMD NOT SKED ddHH/ddHH
|
|
AMD LTD TO (element list) (AFT ddHHmm, or TIL ddHHmm, or
|
|
ddHH/ddHH)""",
|
|
60: """NSW not needed""",
|
|
61: """The period covered by a TAF shall not exceed 30
|
|
hours""",
|
|
81: """CB may only be mentioned when TS or VCTS mentioned
|
|
(NWSI 10-813, Appendix B, 1.2.7.3)""",
|
|
}
|
|
|
|
_Warnings = { \
|
|
#11: """Forecast weather in the vicinity should be
|
|
#the last entry in the weather group
|
|
#(NWSI 10-813, 1.2.6.1)""",
|
|
12: """Number of cloud groups should not exceed three
|
|
(NWSI 10-813, Appendix C, 1.2.7.1)""",
|
|
13: """The period of time covered by a PROB should be
|
|
6 hours or less (NWSI 10-813, Appendix C, 1.2.9.4)""",
|
|
14: """Tornadoes or Waterspouts should not be
|
|
forecast in terminal forecasts
|
|
(NWSI 10-813, Appendix B, 4)""",
|
|
15: """Suspicious value of wind speed""",
|
|
16: """Suspicious value of wind gust""",
|
|
17: """Suspicious value of llws speed""",
|
|
19: """AMD restriction not within TAF forecast period""",
|
|
31: """Variable wind speed must be between 1 and 6KT
|
|
without convective activity.
|
|
(NWSI 10-813, Appendix C, 1.2.4)""",
|
|
}
|
|
|
|
_Messages = {'error': _Errors, 'warning': _Warnings}
|
|
|
|
###############################################################################
|
|
# valid wx elements
|
|
# NWSI 10-813, 1.2.5
|
|
_ValidVsby = {
|
|
'0': 0.0,
|
|
'M1/4': 0.0,
|
|
'1/4': 0.25,
|
|
'1/2': 0.5,
|
|
'3/4': 0.75,
|
|
'1': 1.0,
|
|
'1 1/2': 1.5,
|
|
'2': 2.0,
|
|
'3': 3.0,
|
|
'4': 4.0,
|
|
'5': 5.0,
|
|
'6': 6.0,
|
|
'P6': 99.0
|
|
}
|
|
|
|
# Appendix B, NWSI 10-813
|
|
_ValidObvis = dict.fromkeys(['BR', 'FG', 'FZFG', 'MIFG', 'PRFG', 'BCFG',
|
|
'FU', 'VA', 'HZ', 'BLPY',
|
|
'DU', 'DRDU', 'BLDU',
|
|
'SA', 'DRSA', 'BLSA', 'DRSN',
|
|
'BLSN', 'BLSA', 'BLDU',
|
|
'PO', 'SQ', 'FC', '+FC', 'SS', '+SS', 'DS', '+DS'])
|
|
|
|
_ValidPcp = dict.fromkeys(['-DZ', 'DZ', '+DZ', '-FZDZ', 'FZDZ', '+FZDZ',
|
|
'-RA', 'RA', '+RA', '-SHRA', 'SHRA', '+SHRA',
|
|
'-TSRA', 'TSRA', '+TSRA', '-FZRA', 'FZRA', '+FZRA',
|
|
'-SN', 'SN', '+SN', '-SHSN', 'SHSN', '+SHSN', 'DRSN',
|
|
'-TSSN', 'TSSN', '+TSSN',
|
|
'-PL', 'PL', '+PL', '+SHPL', 'SHPL', '+SHPL',
|
|
'-TSPL', 'TSPL', '+TSPL',
|
|
'-SG', 'SG', '+SG',
|
|
'IC',
|
|
'GR', 'SHGR', 'TSGR',
|
|
'GS', 'SHGS', 'TSGS',
|
|
'TS'])
|
|
|
|
_ValidVcnty = dict.fromkeys(['TS', 'SH', 'FG'])
|
|
|
|
# NWSI 10-813, 1.2.7
|
|
_ValidCover = {'FEW': 1, 'SCT': 2, 'BKN': 3, 'OVC': 4}
|
|
|
|
# NWSI 10-813, 1.2.6
|
|
_UnltdVsbyWx = dict.fromkeys(['VA', 'DRDU', 'DRSA', 'DRSN', 'MIFG',
|
|
'PRFG', 'BCFG'])
|
|
|
|
##############################################################################
|
|
# parser stuff
|
|
_Cld = '(FEW|SCT|BKN|OVC|VV)\d{3}(CB)?'
|
|
_Fract = '(1\s*)?[13]/[24]'
|
|
_Obv = 'BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|\+?(FC|SS|DS)|SN'
|
|
_ObvQ = 'MI|PR|BC|DR|BL|FZ'
|
|
_Pcp = 'DZ|RA|SN|SG|IC|PE|GR|GS|UP|PL'
|
|
_PcpQ = 'SH|TS|FZ'
|
|
_TimePhrase = '(AFT|TIL)\s+(\d{6})|(\d{4}/\d{4})'
|
|
_Options = r"""
|
|
set lexer = ContextSensitiveLexer
|
|
set lexer_dotall = True
|
|
"""
|
|
|
|
_Separator = r"separator spaces: '\s+' ;"
|
|
|
|
_pcptok = '[+-]?(%s)?(%s)+' % (_PcpQ, _Pcp)
|
|
_obvtok = '(%s)?(%s)' % (_ObvQ, _Obv)
|
|
|
|
_TokList = [
|
|
('prefix', r'TAF(\s+(AMD|COR))?'),
|
|
('ident', r'[A-Z][A-Z0-9]{3}'),
|
|
('itime', r'\d{6}Z'),
|
|
('nil', r'NIL'),
|
|
('vtime', r'\d{4}/\d{4}'),
|
|
('ftime', r'FM\d{6}'),
|
|
('ttime', r'TEMPO \d{4}/\d{4}'),
|
|
('ptime', r'PROB\d{2}\s+\d{4}/\d{4}'),
|
|
('wind', r'(VRB|\d{3})\d{2,3}(G\d{2,3})?KT'),
|
|
('vsby', r'(%s|\d|P6)SM' % _Fract),
|
|
('pcp', r'%s|TS(\s+%s)?' % (_pcptok, _pcptok)),
|
|
('obv', r'%s(\s+%s)*' % (_obvtok, _obvtok)),
|
|
('vcnty', r'VC\w+'),
|
|
('nsw', r'NSW'),
|
|
('sky', r'SKC|CLR|(%s(\s+%s)*)' % (_Cld, _Cld)),
|
|
('llws', r'WS\d{3}/\d{5,6}KT'),
|
|
('amd', r'AMD\s+(NOT\s+|LTD\s+)[^=]+'),
|
|
]
|
|
|
|
_Tokens = '\n'.join([r"token %s: '%s' ;" % tok for tok in _TokList])
|
|
|
|
_Rules = r"""
|
|
START/e -> TAF/e $ e=self.taf() $ ;
|
|
TAF -> Prefix? Main (TGroup | PGroup)? (FGroup (TGroup | PGroup)?)* Amd? '(=.*|$)' ;
|
|
Main -> Ident ITime VTime/t (Nil | FWeather) $ self.add_group('FM') $ ;
|
|
FGroup -> FTime FWeather $ self.add_group('FM') $ ;
|
|
TGroup -> TTime/t TWeather $ self.add_group('TEMPO') $ ;
|
|
PGroup -> PTime/t PWeather $ self.add_group('PROB') $ ;
|
|
FWeather -> Wind Vsby Pcp? Obv? Vcnty? Sky Shear? ;
|
|
TWeather -> Wind? Vsby? Pcp? Obv? Vcnty? Nsw? Sky? Shear? ;
|
|
PWeather -> Wind? Vsby? Pcp? Obv? Vcnty? Sky? Shear? ;
|
|
|
|
Prefix -> prefix/x $ self.prefix(x) $ ;
|
|
Ident -> ident/x $ self.ident(x) $ ;
|
|
ITime -> itime/x $ self.itime(x) $ ;
|
|
VTime -> vtime/x $ self.vtime(x) $ ;
|
|
FTime -> ftime/x $ self.ftime(x) $ ;
|
|
TTime -> ttime/x $ self.ttime(x) $ ;
|
|
PTime -> ptime/x $ self.ptime(x) $ ;
|
|
Nil -> nil $ self._nil = 1 $ ;
|
|
Wind -> wind/x $ self.wind(x) $ ;
|
|
Vsby -> vsby/x $ self.vsby(x) $ ;
|
|
Pcp -> pcp/x $ self.pcp(x) $ ;
|
|
Obv -> obv/x $ self.obv(x) $ ;
|
|
Nsw -> nsw/x $ self.nsw(x) $ ;
|
|
Vcnty -> vcnty/x $ self.vcnty(x) $ ;
|
|
Sky -> sky/x $ self.sky(x) $ ;
|
|
Shear -> llws/x $ self.llws(x) $ ;
|
|
Amd -> amd/x $ self.amd(x) $ ;
|
|
"""
|
|
|
|
##############################################################################
|
|
# to produce a meaningful error messsage
|
|
_TokDict = {
|
|
'prefix': 'TAF or TAF AMD or TAF COR',
|
|
'ident': 'site id',
|
|
'itime': 'issue time',
|
|
'nil': 'NIL',
|
|
'vtime': 'valid time',
|
|
'ftime': 'FMddHHMM',
|
|
'ttime': 'TEMPO ddHH/ddhh',
|
|
'ptime': 'PROB30 ddHH/ddhh',
|
|
'wind': 'wind',
|
|
'vsby': 'visibility',
|
|
'pcp': 'precipitation or thunder',
|
|
'obv': 'obstruction to vision',
|
|
'vcnty': 'weather in vicinity',
|
|
'nsw': 'no significant wx',
|
|
'sky': 'sky conditions',
|
|
'llws': 'low level wind shear',
|
|
'amd': 'AMD ... phrase',
|
|
}
|
|
|
|
alist = [r'AMD\s+NOT\s+SKED(\s+(%s))?$' % _TimePhrase,
|
|
r'AMD\s+LTD\s+TO(\s+(CLD|VIS|WX|AND|WIND)){1,5}(\s+(%s))?$' % _TimePhrase]
|
|
_AmdPat = re.compile('|'.join(alist))
|
|
_ConvectionPat = re.compile(r'TS|SH')
|
|
|
|
##############################################################################
|
|
# local functions
|
|
def valid_base(base):
|
|
"""Checks if cloud base is valid"""
|
|
if base <= 30:
|
|
return True
|
|
elif base <= 50:
|
|
return base % 5 == 0
|
|
else:
|
|
return base % 10 == 0
|
|
|
|
def invalid_pl_vsby(i, v):
|
|
"""Checks if visibility is inconsistent with PL"""
|
|
if i == '+' and v >= 3.0:
|
|
return True
|
|
elif i == '' and v > 6.0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def invalid_sn_vsby(i, v):
|
|
"""Checks if visibility is inconsistent with SN or DZ
|
|
Returns 0 if consistent, -1 if too high, +1 if too low"""
|
|
if i == '+':
|
|
if v > 0.25:
|
|
return -1
|
|
else:
|
|
return 0
|
|
elif i == '':
|
|
if v > 0.5:
|
|
return -1
|
|
elif v <= 0.25:
|
|
return 1
|
|
else:
|
|
return 0
|
|
elif i == '-':
|
|
if v <= 0.5:
|
|
return 1
|
|
else:
|
|
return 0
|
|
else:
|
|
return 0
|
|
|
|
def invalid_fg_vsby(s, v):
|
|
"""Checks if visibility is inconsistent with FG"""
|
|
# NWSI 10-813, 1.2.6
|
|
if len(s) == 2 or s.startswith('FZ'):
|
|
if v > 0.6:
|
|
return True
|
|
elif s.startswith('MI'):
|
|
if v < 0.6:
|
|
return True
|
|
return False
|
|
|
|
def invalid_br_vsby(v):
|
|
"""Checks if visibility is inconsistent with BR"""
|
|
return not 0.6 < v < 6.1
|
|
|
|
def invalid_ds_vsby(i, v):
|
|
"""Checks if visibility is inconsistent with DS or SS"""
|
|
if i == '+' and v >= 0.3:
|
|
return True
|
|
elif i == '' and not 0.3 < v < 0.6:
|
|
return True
|
|
return False
|
|
|
|
def check_sky(tok, cover, base, cig):
|
|
"""Verifies validity of cloud layer.
|
|
Calculates summary cover, base, cig. Raises exception on error.
|
|
Arguments: (cover, base, cig) evaluated from lower layers."""
|
|
if tok.startswith('VV'):
|
|
if cover != 0:
|
|
raise Error(_Errors[40])
|
|
base = cig = int(tok[2:5])
|
|
elif tok == 'CLR':
|
|
cig = Avn.CLEAR
|
|
elif tok == 'SKC':
|
|
cig = Avn.UNLIMITED
|
|
else:
|
|
tcover, tbase = _ValidCover.get(tok[:3], None), int(tok[3:6])
|
|
if tcover is None or tcover < cover or cover == 4:
|
|
raise Error(_Errors[17])
|
|
if not valid_base(tbase) or tbase <= base:
|
|
raise Error(_Errors[41])
|
|
if tbase == 0 and tcover != 4:
|
|
raise Error(_Errors[42])
|
|
if tcover in [3, 4]:
|
|
cig = min(cig, tbase)
|
|
cover, base = tcover, tbase
|
|
|
|
return cover, base, cig
|
|
|
|
def valid_day(tms):
|
|
"""Checks if day of month is valid"""
|
|
year, month, day = tms[:3]
|
|
if day > 31:
|
|
return False
|
|
if month in [4, 6, 9, 11] and day > 30:
|
|
return False
|
|
if month == 2:
|
|
if (year%4 and day > 28) or day > 29:
|
|
return False
|
|
return True
|
|
|
|
def fix_date(tms):
|
|
"""Tries to determine month and year from report timestamp"""
|
|
now = time.time()
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
# tms contains day, hour, min of the report, current year and month
|
|
if t > now + 3*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
|
|
|
|
def get_prev_pcp(s):
|
|
"""Parses precipitation string to extract prevailing precipitation.
|
|
Return tuple: (prev, other). Skips stand-alone TS"""
|
|
if s.startswith('TS'):
|
|
s = s.split()[-1]
|
|
if s[0] in '-+':
|
|
n = 1
|
|
else:
|
|
n = 0
|
|
if s[n:n+2] in _PcpQ:
|
|
n += 2
|
|
return s[:n+2], s[n+2:]
|
|
|
|
def add_msg(d, key, msg):
|
|
"""Adds text error message to dictionary d. msg is either a text or
|
|
message number from _Errors or _Warnings"""
|
|
if type(msg) is int:
|
|
msg = _Messages[key].get(msg, 'Unknown %s %d' % (key, msg))
|
|
else:
|
|
msg = str(msg)
|
|
if key in d:
|
|
d[key].append(msg)
|
|
else:
|
|
d[key] = [msg]
|
|
|
|
##############################################################################
|
|
# decoder class
|
|
class Decoder(tpg.VerboseParser):
|
|
"""TAF decoder class"""
|
|
verbose = 0
|
|
__doc__ = '\n'.join([_Options, _Separator, _Tokens, _Rules])
|
|
|
|
def __call__(self, taf, bbb=None, firstline=0, strict=False):
|
|
"""Decodes TAF. taf starts with CCCC ddhhmmZ"""
|
|
if type(taf) is list:
|
|
taf = '\n'.join(taf)
|
|
if not bbb:
|
|
bbb = ' '
|
|
self._taf = {'bbb': bbb, 'group': []}
|
|
self._strict = strict
|
|
self._group = {}
|
|
self._first = firstline
|
|
self._cutoff = 0
|
|
self.expected = []
|
|
self._nil = 0
|
|
self.bad = {}
|
|
# strip trailing '=' and add a terminating blank
|
|
i = taf.find('=')
|
|
if i > 0:
|
|
taf = taf[:i]
|
|
self._text = taf + ' '
|
|
try:
|
|
return super(Decoder, self).__call__(self._text)
|
|
except tpg.SyntacticError:
|
|
if self.expected:
|
|
return {'index': self.bad['index'], \
|
|
'fatal': ['Invalid word %s. Expecting one of:\n%s' % \
|
|
(self.bad['text'], '\n'.join(self.expected))]}
|
|
else:
|
|
return {'index': self.bad['index'], \
|
|
'fatal': ['Unexpected end after %s.' % self.bad['text']]}
|
|
except Exception as e:
|
|
# highlight only site id
|
|
row = self._first+2
|
|
index = ('%d.%d' % (row, 0), '%d.%d' % (row, 4))
|
|
return {'index': index, 'fatal': ['TAF decoder bug:\n', traceback.format_exc()]}
|
|
|
|
def __index(self, pos, token):
|
|
tmp = self.lexer.input[:pos]
|
|
line = tmp.count('\n') + self._first + 1
|
|
row = pos - tmp.rfind('\n') - 1
|
|
return ('%d.%d' % (line, row), '%d.%d' % (line, row+len(token)))
|
|
|
|
def index(self):
|
|
token = self.lexer.token()
|
|
return self.__index(token.start, token.text)
|
|
|
|
def tokenOK(self, pos=0):
|
|
"""Checks whether token ends with a blank"""
|
|
return self._text[self.lexer.token().stop+pos] in ' \t\n'
|
|
|
|
def taf(self):
|
|
"""Called by the parser at the end of work"""
|
|
if self._strict:
|
|
self.check_issue_time()
|
|
return self._taf
|
|
|
|
def eatCSL(self, name):
|
|
"""Overrides super definition"""
|
|
try:
|
|
value = super(Decoder, self).eatCSL(name)
|
|
self.bad = {}
|
|
self.expected = []
|
|
return value
|
|
except tpg.WrongToken:
|
|
if self.lexer.input[self.lexer.pos:]:
|
|
bad = self.lexer.input[self.lexer.pos:].split(None, 1)[0]
|
|
if self.bad:
|
|
if bad == self.bad['text']:
|
|
self.expected.append(_TokDict.get(name, ''))
|
|
else:
|
|
self.bad['text'] = bad
|
|
self.bad['index'] = self.__index(self.lexer.pos, bad)
|
|
self.expected.append(_TokDict.get(name, ''))
|
|
else:
|
|
bad = self.lexer.input.split()[-1]
|
|
self.bad['text'] = bad
|
|
self.bad['index'] = self.__index(self.lexer.pos-len(bad)-1, bad)
|
|
raise
|
|
|
|
def add_group(self, type):
|
|
"""Checks for valid syntax between elements in a group and
|
|
and valid times"""
|
|
if self._nil or not self._group:
|
|
return
|
|
if type == 'PROB':
|
|
self.check_prob_group()
|
|
if type == 'TEMPO':
|
|
self.check_tempo_group()
|
|
|
|
self._group['type'] = type
|
|
|
|
# TEMPO and PROB groups modify conditions reported in FM group
|
|
if self._group['type'] in ['PROB', 'TEMPO']:
|
|
tmpgroup = self._taf['group'][-1]['prev'].copy()
|
|
# pcp, obv and vcnty go together, wx in TEMPO overrides FM
|
|
if 'nsw' in self._group or 'pcp' in self._group or \
|
|
'obv' in self._group: # no VC in TEMPO
|
|
rmvditem = False
|
|
for item in ['pcp', 'obv', 'vcnty']:
|
|
if item in tmpgroup:
|
|
del tmpgroup[item]
|
|
rmvditem = True
|
|
|
|
if not rmvditem and 'nsw' in self._group:
|
|
add_msg(self._group['nsw'],'error',60)
|
|
|
|
tmpgroup.update(self._group)
|
|
else:
|
|
tmpgroup = self._group
|
|
|
|
try:
|
|
self.check_wind(tmpgroup)
|
|
except Warning1 as e:
|
|
# Wind 'inherited' from prevailing group if not
|
|
# in occasional group
|
|
try:
|
|
add_msg(self._group['wind'], 'warning', e)
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
self.check_pcp(tmpgroup)
|
|
except Error as e:
|
|
try:
|
|
add_msg(self._group['pcp'], 'error', e)
|
|
except KeyError:
|
|
pass
|
|
|
|
self.check_obv()
|
|
|
|
try:
|
|
self.check_vsby_wx(tmpgroup)
|
|
except Error as e:
|
|
for k in ['pcp', 'obv', 'vsby']:
|
|
if k in self._group:
|
|
add_msg(self._group[k], 'error', e)
|
|
break
|
|
try:
|
|
self.check_ts_cb(tmpgroup)
|
|
except Error as e:
|
|
for k in ['vcnty', 'pcp', 'sky']:
|
|
if k in self._group:
|
|
add_msg(self._group[k], 'error', e)
|
|
break
|
|
|
|
if self._group['type'] == 'FM':
|
|
self.check_prev_time()
|
|
self._taf['group'].append({'prev': self._group})
|
|
else:
|
|
period = self._taf['group'][-1]
|
|
self.check_ocnl_time()
|
|
period['ocnl'] = self._group
|
|
self._group = {}
|
|
|
|
###################################################################
|
|
# Element checks
|
|
def check_issue_time(self):
|
|
try:
|
|
itime = self._taf['itime']['value']
|
|
vtime = self._taf['vtime']['from']
|
|
bbb = self._taf['bbb'][0]
|
|
if bbb == ' ':
|
|
if -2401.0 <= itime - vtime < 0:
|
|
return
|
|
elif bbb == 'R': # to allow incoming TAFs
|
|
if -2401.0 < itime - vtime < 1800.0:
|
|
return
|
|
elif bbb == 'C':
|
|
if -2401.0 <= itime - vtime < 21600.0:
|
|
return
|
|
else:
|
|
if -1801.0 < itime - vtime < 1801.0:
|
|
return
|
|
add_msg(self._taf['itime'], 'error', 24)
|
|
except KeyError:
|
|
pass
|
|
|
|
def check_prev_time(self):
|
|
if not self._taf['group']: # main group
|
|
return
|
|
period = self._taf['group'][-1]
|
|
t = self._group['time']
|
|
try:
|
|
if t['from'] <= max(period['prev']['time']['from'], self._cutoff):
|
|
add_msg(t, 'error', 49)
|
|
elif t['from'] <= self._taf['vtime']['from']:
|
|
add_msg(t, 'error', 49)
|
|
if 'ocnl' in period and t['from'] < period['ocnl']['time']['to']:
|
|
add_msg(t, 'error', 50)
|
|
period['prev']['time']['to'] = t['from']
|
|
except KeyError:
|
|
pass
|
|
|
|
def check_ocnl_time(self):
|
|
period = self._taf['group'][-1]
|
|
t = self._group['time']
|
|
# adjust start of valid time for the first conditional group
|
|
if len(self._taf['group']) == 1:
|
|
itime = self._taf['itime']['value']
|
|
vtime = self._taf['vtime']['from']
|
|
|
|
if itime - 1800.0 < t['from'] < itime:
|
|
t['from'] = itime
|
|
elif t['from'] == vtime and vtime % 21600 < 60.0:
|
|
t['from'] = itime
|
|
try:
|
|
if t['from'] < period['prev']['time']['from']:
|
|
add_msg(t, 'error', 52)
|
|
except KeyError:
|
|
pass
|
|
|
|
def check_prob_group(self):
|
|
if 'pcp' not in self._group:
|
|
add_msg(self._group['time'], 'error', 16)
|
|
if 'llws' in self._group:
|
|
add_msg(self._group['llws'], 'error', 33)
|
|
if 'vcnty' in self._group:
|
|
add_msg(self._group['vcnty'], 'error', 28)
|
|
|
|
def check_tempo_group(self):
|
|
if len(self._group) < 2:
|
|
add_msg(self._group['time'], 'error', 34)
|
|
if 'llws' in self._group:
|
|
add_msg(self._group['llws'], 'error', 33)
|
|
if 'vcnty' in self._group:
|
|
add_msg(self._group['vcnty'], 'error', 28)
|
|
|
|
if 'nsw' in self._group:
|
|
try:
|
|
if 'nsw' in self._taf['group'][-2]['ocnl']:
|
|
add_msg(self._group['nsw'], 'error', 47)
|
|
return
|
|
except (IndexError,KeyError):
|
|
pass
|
|
|
|
if 'vsby' in self._group:
|
|
if self._group['vsby']['value'] < 7.0:
|
|
add_msg(self._group['vsby'], 'error', 46)
|
|
else:
|
|
add_msg(self._group['nsw'], 'error', 46)
|
|
|
|
if 'obv' in self._group or 'pcp' in self._group:
|
|
add_msg(self._group['nsw'], 'error', 60)
|
|
|
|
else:
|
|
# if 'vsby' in self._group:
|
|
# When invalid 'value' can be missing.
|
|
if 'vsby' in self._group and 'value' in self._group['vsby']:
|
|
#
|
|
# If reduction in visibility is forecasted in TEMPO and there's
|
|
# no mention of obv or wx...
|
|
prev = self._taf['group'][-1]['prev']
|
|
if 'value' in prev['vsby'] and \
|
|
prev['vsby']['value'] > self._group['vsby']['value'] and \
|
|
('pcp' in prev or 'obv' in prev or 'vcnty' in prev) and \
|
|
not ('pcp' in self._group or 'obv' in self._group):
|
|
add_msg(self._group['vsby'], 'error', 48)
|
|
|
|
def check_wind(self,g):
|
|
# variable wind speed > 6kt needs convective weather
|
|
try:
|
|
wind = g['wind']
|
|
except KeyError:
|
|
return
|
|
|
|
dd, ff = wind.get('dd', None), wind.get('ff', None)
|
|
if dd == 'VRB' and ff > 6:
|
|
wx = g.get('pcp', None)
|
|
if wx:
|
|
for tok in wx['str'].split():
|
|
if _ConvectionPat.search(tok):
|
|
return
|
|
|
|
wx = g.get('vcnty', None)
|
|
if wx:
|
|
if wx['str'] in ['VCTS','VCSH']:
|
|
return
|
|
|
|
sky = g.get('sky', None)
|
|
if sky:
|
|
for lyr in sky['str'].split():
|
|
if lyr.endswith('CB'):
|
|
return
|
|
|
|
raise Warning1(_Warnings[31])
|
|
|
|
def check_pcp(self,g):
|
|
# NWSI 10-813, 1.2.6
|
|
if 'pcp' not in g:
|
|
return
|
|
try:
|
|
plist = g['pcp']['str'].split()
|
|
if plist.index('TS') != 0 or 'FZ' not in plist[1]:
|
|
raise Error(_Errors[45])
|
|
except (KeyError, IndexError, ValueError):
|
|
pass
|
|
|
|
prev, other = get_prev_pcp(g['pcp']['str'])
|
|
tok = [prev[-2:]] + [other[n:n+2] for n in range(0, len(other)-2, 2)]
|
|
if len(tok) != len(dict.fromkeys(tok)):
|
|
raise Error(_Errors[57])
|
|
|
|
def check_ts_cb(self, g):
|
|
# NWSI 10-813, 1.2.6
|
|
if 'pcp' in g and 'TS' in g['pcp']['str'] or 'vcnty' in g and \
|
|
'TS' in g['vcnty']['str']:
|
|
if 'sky' not in g or 'CB' not in g['sky']['str']:
|
|
raise Error(_Errors[11])
|
|
if 'sky' in g and 'CB' in g['sky']['str']:
|
|
if ('pcp' not in g or 'TS' not in g['pcp']['str']) and \
|
|
('vcnty' not in g or 'TS' not in g['vcnty']['str']):
|
|
raise Error(_Errors[81])
|
|
|
|
def check_obv(self):
|
|
# NWSI 10-813, 1.2.6
|
|
if 'obv' in self._group and 'VA' in self._group['obv']['str'] \
|
|
and not 'vsby' in self._group:
|
|
add_msg(self._group['obv'], 'error', 13)
|
|
|
|
def check_vsby_wx(self, g):
|
|
if 'vsby' in g:
|
|
try:
|
|
vsby = g['vsby']['value']
|
|
except KeyError:
|
|
return
|
|
# visibility consistent with precipitation
|
|
snow = 0
|
|
if 'pcp' in g:
|
|
prev = get_prev_pcp(g['pcp']['str'])[0]
|
|
try:
|
|
i, ptype = prev[0], prev[-2:]
|
|
if i not in '+-':
|
|
i = ''
|
|
if ptype == 'PL':
|
|
if invalid_pl_vsby(i, vsby):
|
|
raise Error(_Errors[53])
|
|
elif ptype in ('SN', 'DZ'):
|
|
snow = invalid_sn_vsby(i, vsby)
|
|
except TypeError: # TS
|
|
pass
|
|
if g['vsby']['str'] == 'P6SM':
|
|
if 'obv' in g and 'nsw' not in g:
|
|
for wx in g['obv']['str'].split():
|
|
if wx not in _UnltdVsbyWx:
|
|
raise Error(_Errors[18])
|
|
if snow == -1:
|
|
raise Error(_Errors[54])
|
|
# NWSI 10-813, 1.2.6
|
|
else:
|
|
if not ('pcp' in g or 'obv' in g or 'nsw' in g):
|
|
add_msg(g['vsby'], 'error', 12)
|
|
if 'obv' in g:
|
|
for tok in g['obv']['str'].split():
|
|
wx = tok[-2:]
|
|
if wx == 'FG' and \
|
|
invalid_fg_vsby(tok, vsby):
|
|
raise Error(_Errors[14])
|
|
if wx == 'BR' and invalid_br_vsby(vsby):
|
|
raise Error(_Errors[15])
|
|
if wx in ('DS', 'SS'):
|
|
if tok[0] in '+-':
|
|
i = tok[0]
|
|
else:
|
|
i = ''
|
|
if invalid_ds_vsby(i, vsby):
|
|
raise Error(_Errors[55])
|
|
if snow == -1:
|
|
raise Error(_Errors[54])
|
|
else:
|
|
if snow:
|
|
raise Error(_Errors[54])
|
|
|
|
########################################################################
|
|
# Methods called by the parser
|
|
def prefix(self, s):
|
|
pass
|
|
|
|
def ident(self, s):
|
|
self._taf['ident'] = {'str': s, 'index': self.index()}
|
|
if not self.tokenOK():
|
|
add_msg(self._taf['ident'], 'error', 19)
|
|
|
|
def itime(self, s):
|
|
self._group = {'type': 'FM'}
|
|
d = self._taf['itime'] = {'str': s, 'index': self.index()}
|
|
mday, hour, minute = int(s[:2]), int(s[2:4]), int(s[4:6])
|
|
try:
|
|
tms = list(time.gmtime())
|
|
tms[2:6] = mday, hour, minute, 0
|
|
fix_date(tms)
|
|
d['value'] = time.mktime(tuple(tms)) - time.timezone
|
|
if mday > 31 or hour > 23 or minute > 59:
|
|
raise Error(_Errors[20])
|
|
if not valid_day(tms):
|
|
raise Error(_Errors[29])
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def vtime(self, s):
|
|
d = self._group['time'] = {'str': s, 'index': self.index()}
|
|
|
|
tms = list(time.gmtime())
|
|
tms[2:6] = int(s[0:2]),int(s[2:4]),0,0
|
|
fix_date(tms)
|
|
|
|
mday, shour, eday, ehour = int(s[:2]), int(s[2:4]), int(s[5:7]), int(s[7:9])
|
|
|
|
try:
|
|
tms[2:6] = mday, shour, 0, 0
|
|
fix_date(tms)
|
|
d['from'] = time.mktime(tuple(tms)) - time.timezone
|
|
|
|
if len(s) == 6:
|
|
period = ehour - shour
|
|
if period <= 0:
|
|
period += 24
|
|
d['to'] = d['from'] + 3600*period
|
|
else:
|
|
tms[2:6] = eday, ehour, 0, 0
|
|
fix_date(tms)
|
|
d['to'] = time.mktime(tuple(tms)) - time.timezone
|
|
|
|
period = abs((d['to'] - d['from'])/3600)
|
|
|
|
if period > 30.01:
|
|
add_msg(d, 'error', 61)
|
|
|
|
if mday > 31:
|
|
raise Error(_Errors[23])
|
|
if shour > 23:
|
|
raise Error(_Errors[22])
|
|
if not valid_day(tms):
|
|
raise Error(_Errors[29])
|
|
if not 0 < ehour <= 24:
|
|
raise Error(_Errors[21])
|
|
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
|
|
self._taf['vtime'] = self._group['time'].copy()
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
# to determine the earliest time of the first FM group
|
|
if self._strict:
|
|
# make start of valid time to be issue time and
|
|
# determine the earliest time of the first FM group
|
|
if self._taf['bbb'][0] == 'C':
|
|
if shour in [0, 6, 12, 18]:
|
|
d['from'] = self._taf['itime']['value']
|
|
else:
|
|
self._cutoff = d['from'] = self._taf['vtime']['from']-1800
|
|
else:
|
|
self._cutoff = d['from'] = self._taf['itime']['value']
|
|
|
|
if shour in [0, 6, 12, 18] and self._cutoff < self._taf['vtime']['from']:
|
|
self._cutoff = self._taf['vtime']['from']
|
|
else:
|
|
# it should not matter, for monitoring
|
|
d['from'] = min(self._taf['vtime']['from'],
|
|
self._taf['itime']['value'])
|
|
|
|
def ftime(self, s):
|
|
d = self._group['time'] = {'str': s, 'index': self.index()}
|
|
|
|
mday, hour, minute = int(s[2:4]), int(s[4:6]), int(s[6:8])
|
|
if not (0 <= hour <= 23 and 0 <= minute <= 59):
|
|
add_msg(d, 'error', 22)
|
|
try:
|
|
tms = list(time.gmtime(self._taf['vtime']['from']))
|
|
tms[2:5] = mday, hour, minute
|
|
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
if t <= self._taf['vtime']['from']-1800:
|
|
fix_date(tms)
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
|
|
if not (self._taf['vtime']['from']-1800 <= t < self._taf['vtime']['to']):
|
|
add_msg(d, 'error', 25)
|
|
|
|
if 'error' not in d and mday != time.gmtime(t)[2]:
|
|
add_msg(d, 'error', 29)
|
|
|
|
d.update({'from': t, 'to': self._taf['vtime']['to']})
|
|
|
|
except KeyError:
|
|
pass
|
|
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def ttime(self, s):
|
|
d = self._group['time'] = {'str': s, 'index': self.index()}
|
|
try:
|
|
tmp = s.split()[1]
|
|
sday, shour, eday, ehour = int(tmp[:2]), int(tmp[2:4]),\
|
|
int(tmp[5:7]),int(tmp[7:9])
|
|
|
|
tms = list(time.gmtime(self._taf['vtime']['from']))
|
|
tms[2:4] = sday,shour
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
if t < self._taf['vtime']['from']:
|
|
fix_date(tms)
|
|
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
if t < self._taf['vtime']['from']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
tms[2:4] = eday, ehour
|
|
if eday < sday:
|
|
tms[1] += 1
|
|
|
|
d.update({'from': t, 'to': time.mktime(tuple(tms)) - time.timezone})
|
|
|
|
if not 0 <= shour < 24:
|
|
add_msg(d, 'error', 22)
|
|
if not 0 < ehour <= 24:
|
|
add_msg(d, 'error', 21)
|
|
|
|
if t >= self._taf['vtime']['to']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
if d['to'] > self._taf['vtime']['to']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
if d['to'] - d['from'] > 14400.0:
|
|
add_msg(d, 'error', 56)
|
|
|
|
if d['to'] <= d['from']:
|
|
add_msg(d, 'error', 51)
|
|
|
|
if 'error' not in d:
|
|
if sday != time.gmtime(t)[2]:
|
|
add_msg(d, 'error', 29)
|
|
else:
|
|
if ehour != 24 and eday != time.gmtime(d['to'])[2]:
|
|
add_msg(d, 'error', 29)
|
|
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def ptime(self, s):
|
|
d = self._group['time'] = {'str': s, 'index': self.index()}
|
|
try:
|
|
if not s.startswith('PROB30'):
|
|
raise Error(_Errors[26])
|
|
|
|
tmp = s.split()[1]
|
|
sday = int(tmp[:2])
|
|
shour = int(tmp[2:4])
|
|
eday = int(tmp[5:7])
|
|
ehour = int(tmp[7:9])
|
|
|
|
tms = list(time.gmtime(self._taf['vtime']['from']))
|
|
tms[2:4] = sday, shour
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
if t < self._taf['vtime']['from']:
|
|
fix_date(tms)
|
|
|
|
t = time.mktime(tuple(tms)) - time.timezone
|
|
if t <= self._taf['vtime']['from']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
tms[2:4] = eday,ehour
|
|
if eday < sday:
|
|
tms[1] += 1
|
|
|
|
d.update({'from': t, 'to': time.mktime(tuple(tms)) - time.timezone})
|
|
|
|
if not 0 <= shour < 24:
|
|
add_msg(d, 'error', 22)
|
|
if not 0 < ehour <= 24:
|
|
add_msg(d, 'error', 21)
|
|
|
|
if t >= self._taf['vtime']['to']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
if d['to'] > self._taf['vtime']['to']:
|
|
add_msg(d, 'error', 25)
|
|
|
|
if d['from'] < 9*3600.0+self._taf['vtime']['from']:
|
|
add_msg(d, 'error', 27)
|
|
|
|
if d['to'] - d['from'] > 21600.0:
|
|
add_msg(d, 'warning', 13)
|
|
|
|
if d['to'] <= d['from']:
|
|
add_msg(d, 'error', 51)
|
|
|
|
if 'error' not in d:
|
|
if sday != time.gmtime(t)[2]:
|
|
add_msg(d, 'error', 29)
|
|
else:
|
|
if ehour != 24 and eday != time.gmtime(d['to'])[2]:
|
|
add_msg(d, 'error', 29)
|
|
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def vsby(self, s):
|
|
d = self._group['vsby'] = {'str': s, 'index': self.index()}
|
|
tok = ' '.join(s[:-2].split())
|
|
v = _ValidVsby.get(tok, None)
|
|
if v is None:
|
|
add_msg(d, 'error', 37)
|
|
else:
|
|
d['value'] = v
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def wind(self, s):
|
|
d = self._group['wind'] = {'str': s, 'index': self.index()}
|
|
try:
|
|
if s.startswith('VRB'):
|
|
dd = d['dd'] = 'VRB'
|
|
else:
|
|
dd = d['dd'] = int(s[:3])
|
|
tok = s[3:-2].split('G', 1)
|
|
ff = d['ff'] = int(tok[0])
|
|
if len(tok[0]) > 2 and tok[0][0] != '1':
|
|
raise Error(_Errors[31])
|
|
if len(tok) > 1:
|
|
gg = d['gg'] = int(tok[1])
|
|
if gg <= ff:
|
|
raise Error(_Errors[32])
|
|
else:
|
|
gg = None
|
|
if dd == 'VRB':
|
|
if ff == 0:
|
|
raise Error(_Errors[30])
|
|
else:
|
|
if (dd % 10 != 0 or dd > 360 or ff == 0 and dd != 0 or
|
|
ff > 0 and dd == 0):
|
|
raise Error(_Errors[30])
|
|
if ff > 99:
|
|
raise Warning1(_Warnings[15])
|
|
if gg and gg - ff > 30:
|
|
raise Warning1(_Warnings[16])
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def obv(self, s):
|
|
d = self._group['obv'] = {'str': s, 'index': self.index()}
|
|
try:
|
|
tmp = s.split()
|
|
for tok in tmp:
|
|
if tok not in _ValidObvis:
|
|
raise Error(_Errors[38])
|
|
if len(tmp) != len(dict.fromkeys(tmp)):
|
|
raise Error(_Errors[57])
|
|
if 'FC' in s:
|
|
raise Warning1(_Warnings[14])
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def pcp(self, s):
|
|
d = self._group['pcp'] = {'str': s, 'index': self.index()}
|
|
prev, other = get_prev_pcp(s)
|
|
if prev is None: # TS
|
|
return
|
|
try:
|
|
if prev not in _ValidPcp:
|
|
raise Error(_Errors[38])
|
|
tmp = [other[i:i+2] for i in range(0, len(other), 2)]
|
|
for tok in tmp:
|
|
if tok not in _ValidPcp:
|
|
raise Error(_Errors[38])
|
|
tmp.append(prev[-2:]) # also has to check prevailing precipitation
|
|
if len(tmp) != len(dict.fromkeys(tmp)):
|
|
raise Error(_Errors[57])
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def nsw(self, s):
|
|
d = self._group['nsw'] = {'str': s, 'index': self.index()}
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def vcnty(self, s):
|
|
d = self._group['vcnty'] = {'str': s, 'index': self.index()}
|
|
if not s[2:] in _ValidVcnty:
|
|
add_msg(d, 'error', 43)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def sky(self, s):
|
|
d = self._group['sky'] = {'str': s, 'index': self.index()}
|
|
cover, base, cig = 0, -1, Avn.UNLIMITED
|
|
try:
|
|
clds = s.split()
|
|
for tok in clds:
|
|
cover, base, cig = check_sky(tok, cover, base, cig)
|
|
if cig == Avn.CLEAR:
|
|
add_msg(d, 'error', 44)
|
|
elif cig < Avn.CLEAR:
|
|
cig *= 100
|
|
if len(clds) > 3:
|
|
raise Warning1(_Warnings[12])
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
d.update({'cover': cover, 'cig': cig})
|
|
|
|
def llws(self, s):
|
|
d = self._group['llws'] = {'str': s, 'index': self.index()}
|
|
try:
|
|
h = int(s[2:5])
|
|
dd = int(s[6:9])
|
|
ff = int(s[9:-2])
|
|
d.update({'hgt': h, 'dd': dd, 'ff': ff})
|
|
if ff > 99:
|
|
raise Warning1(_Warnings[17])
|
|
elif len(s) >= 14:
|
|
raise Error(_Errors[39])
|
|
if not 0 < h <= 20:
|
|
raise Error(_Errors[36])
|
|
if dd % 10 != 0 or dd > 360 or ff == 0 and dd != 0:
|
|
raise Error(_Errors[35])
|
|
except Warning1 as e:
|
|
add_msg(d, 'warning', e)
|
|
except Error as e:
|
|
add_msg(d, 'error', e)
|
|
if not self.tokenOK():
|
|
add_msg(d, 'error', 19)
|
|
|
|
def amd(self, s):
|
|
# s does not contain the whole phrase
|
|
phrase = (s+self.lexer.input[self.lexer.pos:]).rstrip()
|
|
m = _AmdPat.match(phrase)
|
|
|
|
if m:
|
|
s = m.group()
|
|
ix0 = self.index()[0]
|
|
row, col = ix0.split('.')
|
|
col = int(col)+len(s)
|
|
ix = (ix0, '%s.%d' % (row, col))
|
|
self._taf['amd'] = {'str': s, 'index': ix}
|
|
|
|
# If reference to time is found in the clause one of these
|
|
# groups will have it.
|
|
strng = m.group(4) or m.group(5) or m.group(11) or m.group(12)
|
|
if strng:
|
|
tms = list(time.gmtime(self._taf['vtime']['from']))
|
|
mins = 0
|
|
if len(strng) == 6:
|
|
mins = int(strng[-2:])
|
|
|
|
for ddmm in strng.split('/'):
|
|
tms[2:6] = int(ddmm[0:2]),int(ddmm[2:4]),mins,0
|
|
fix_date(tms)
|
|
if not (self._taf['vtime']['from'] <= (time.mktime(tuple(tms)) - time.timezone) <= self._taf['vtime']['to']):
|
|
self._taf['amd'] = {'str': s, 'index': self.index()}
|
|
add_msg(self._taf['amd'], 'warning', 19)
|
|
break
|
|
else:
|
|
self._taf['amd'] = {'str': s, 'index': self.index()}
|
|
add_msg(self._taf['amd'], 'error', 59)
|
|
|
|
def __updateIssueValidTimes(self, bbb, fcst):
|
|
TafIdent = re.compile(r'(?P<ident>[KTPN]\w{3})\s+\d{6}Z\s+\d{4}/(?P<evtime>\d{4})\s+')
|
|
# Updates issuance and valid times in a forecast
|
|
t=time.time()
|
|
itime = AvnLib.getFmtIssueTime(bbb, t)
|
|
|
|
if bbb and bbb[0] == 'C':
|
|
# corrected forecast has the same timestamp
|
|
return re.sub(' (DD|\d{2})\d{4}Z ', ' %s ' % itime, fcst, 1)
|
|
else:
|
|
result = TafIdent.search(fcst)
|
|
if result:
|
|
ident = result.group('ident')
|
|
tafDuration=int(AvnParser.getTafSiteCfg(ident)['thresholds']['tafduration'])
|
|
else:
|
|
tafDuration = 24
|
|
|
|
if result:
|
|
vtime = AvnLib.getFmtValidTime(bbb,None,tafDuration=tafDuration,
|
|
evtime=result.group('evtime'))[4:]
|
|
else:
|
|
vtime = AvnLib.getFmtValidTime(bbb,None,tafDuration)[4:]
|
|
|
|
return re.sub(' (DD|\d{2})\d{4}Z [/D\d]{6,9} ',
|
|
' %s %s ' % (itime, vtime), fcst, 1)
|
|
|
|
def splitBulletin(self, text):
|
|
SplitReg = re.compile(r'=+[\s\n]*|\n{2,}|\n$')
|
|
# Splits bulletin into forecasts. Assumes that a forcast is
|
|
# terminated with '=' or forecasts are separated by a blank
|
|
# line
|
|
forecasts = [x.strip() for x in \
|
|
SplitReg.split(text)]
|
|
return ['%s=\n' % x for x in forecasts if x]
|
|
|
|
##############################################################################
|
|
# java interface part ... added to support calling python from java
|
|
# Like the TafEditDialog this formats then parses the TAF.
|
|
def parseFromJava(self, text, bbb):
|
|
|
|
import JUtil
|
|
fcsts = self.splitBulletin(text)
|
|
|
|
tmpText = []
|
|
for fcst in map(Avn.curry(_format, bbb), fcsts) :
|
|
tmpText.extend(fcst+[''])
|
|
|
|
text = '\n'.join(tmpText)
|
|
|
|
result = self(text, bbb)
|
|
text = '\n'.join([self.__updateIssueValidTimes(bbb, f)
|
|
for f in self.splitBulletin(text)])
|
|
headerTime = AvnLib.getFmtHeaderTime(bbb)
|
|
|
|
return JUtil.pyDictToJavaMap({'result': result, 'text': text, 'headerTime': headerTime})
|
|
|
|
##############################################################################
|
|
# java interface part ... added to support calling python from java
|
|
# updated java interface method to work with TAF editor QC
|
|
def updateTime(self, text, bbb):
|
|
import JUtil
|
|
text = '\n'.join([self.__updateIssueValidTimes(bbb, f)
|
|
for f in self.splitBulletin(text)])
|
|
headerTime = AvnLib.getFmtHeaderTime(bbb)
|
|
|
|
return JUtil.pyDictToJavaMap({'text': text, 'headerTime': headerTime})
|
|
|
|
##############################################################################
|
|
# public part
|
|
def errors(decoded):
|
|
"""Returns list of elements that have 'error' or 'warning' key"""
|
|
d = {'error': [], 'warning': []}
|
|
def walk(item, k=None):
|
|
if type(item) is list:
|
|
for i in item:
|
|
walk(i)
|
|
elif type(item) is dict:
|
|
if 'error' in item:
|
|
d['error'].append((k, item))
|
|
elif 'warning' in item:
|
|
d['warning'].append((k, item))
|
|
else:
|
|
for k, v in item.items():
|
|
walk(v, k)
|
|
if 'fatal' in decoded:
|
|
return d
|
|
walk(decoded)
|
|
return d
|
|
|
|
###############################################################################
|
|
# These methods were grabbed from TafEditDialog, OB9.2.X_source
|
|
# Revision 1.76. Placed here so all the TafEditDialg GUI is not imported.
|
|
###############################################################################
|
|
import itertools
|
|
def _preamble(kind, bbb):
|
|
if bbb[:2] == 'AA':
|
|
return kind + ' AMD'
|
|
elif bbb[:2] == 'CC':
|
|
return kind + ' COR'
|
|
else:
|
|
return kind
|
|
|
|
_format_pat = re.compile('|'.join([r'(FM\d+)', r'(TEMPO)', r'(AMD\s+[LN])',
|
|
r'(NIL\s+AMD)', r'(TAF\s+AMD)', r'(TAF\s+COR)',
|
|
r'(TAF)']))
|
|
|
|
def _format(bbb, fcst):
|
|
# split each forecast into groups
|
|
tmplist = [_f for _f in _format_pat.split(' '.join(fcst.split())) if _f]
|
|
# fix first line
|
|
kind = tmplist[0][:3]
|
|
if kind == 'TAF':
|
|
preamble = _preamble(kind, bbb)
|
|
tmplist[0] = preamble
|
|
elif re.match('\s*[A-Z]{4}\s', tmplist[0]): # missing TAF
|
|
preamble = _preamble('TAF', bbb)
|
|
tmplist.insert(0, preamble)
|
|
else: # unknown string, leave alone
|
|
return tmplist
|
|
newlist = tmplist[:2] + [''.join(x) for x in
|
|
zip(itertools.islice(tmplist, 2, None, 2),
|
|
itertools.islice(tmplist, 3, None, 2))]
|
|
return AvnLib.indentTaf(newlist)
|
|
|
|
####################################################################################################################
|
|
# End of TafEditDialog code
|
|
####################################################################################################################
|
|
# test
|
|
def main(report):
|
|
_startTaf = re.compile(r'[A-Z][A-Z0-9]{3}\s+\d{6}Z|TAF(\s+(AMD|COR))?')
|
|
def splitTaf(report):
|
|
# Splits report into header and TAF
|
|
if type(report) is str:
|
|
report = report.rstrip().split('\n')
|
|
# skip header
|
|
for n, line in enumerate(report):
|
|
if _startTaf.match(line):
|
|
return report[:n], '\n'.join(report[n:])+'\n'
|
|
raise Avn.AvnError('Cannot find start of forecast')
|
|
header, taf = splitTaf(report)
|
|
tmp = header[0].split()
|
|
try:
|
|
bbb = tmp[3]
|
|
except IndexError:
|
|
bbb = ' '
|
|
decoder = Decoder()
|
|
decoded = decoder(''.join(taf), bbb, strict=True)
|
|
if 'fatal' in decoded:
|
|
print('Fatal error at ' + str(decoded['index']) + ' ' + str(decoded['fatal']))
|
|
return
|
|
for key in decoded:
|
|
if key == 'group':
|
|
for g in decoded['group']:
|
|
print()
|
|
for key2 in g:
|
|
print(str(time.ctime(g[key2]['time']['from'])) + ' ' +
|
|
str(time.ctime(g[key2]['time']['to'])))
|
|
print('\t' + ' ' + str(key2) + ' ' + str(g[key2]))
|
|
elif key == 'bbb':
|
|
print('%s \'%s\'' % (str(key), str(decoded[key])))
|
|
else:
|
|
print(str(key) + ' ' + str(decoded[key]))
|
|
|
|
errlist = errors(decoded)
|
|
print('====== Errors =======')
|
|
for k, d in errlist['error']:
|
|
print(str(k) + ' ' + str(d['index']) + ' ' + str(d['error']))
|
|
print('====== Warnings =====')
|
|
for k, d in errlist['warning']:
|
|
print(str(k) + ' ' + str(d['index']) + ' ' + str(d['warning']))
|
|
|
|
|
|
###############################################################################
|
|
# commented-out the standalone program part to support calling python from java
|
|
#if __name__ == '__main__':
|
|
# import sys
|
|
# main(sys.stdin.read())
|