script= decodeMETAR_USA_Rmk.php, © author= SPAWS, version= 0.4.1, '; // Full version history at end /* ================================================================================= SPAWS Script for decoding METeorological Aviation Routine (METAR) Weather Reports ================================================================================= A METAR weather report consists of abbreviations, contractions, numbers, plain language, and symbols to provide a uniform means of disseminating surface weather reports using traditional alphanumerical characters (TAC) that can be understood by a trained person. Because I have not been trained to interpret METAR, nor checked my work with anybody who issues them, I issue a disclaimer here that my decoding is to the best of my ability and intended for those with an amateur interest, it may not be accurate enough for pilots and anyone else with safety-critical needs. Indeed, as some of the content decoded here is not defined in any manual I have seen, it is possible that either the labelling or the units I quote are totally wrong, although my interpretation appears to make sense. --------------------------------------------------------------------------------- A lot of work has gone into development and (even more work involving finding over a thousand METAR reports) for testing this suite of scripts, consequently the intellectual ownership of this script resides with the operator of a Simple Personal Automatic Weather Station (SPAWS), who does not give away any rights for anybody else to gain financially, in any way, directly from use of this totally novel code developed from scratch by SPAWS. This is important as my script includes functionality not available from any other decoder. Permission will be given for others to use this suite on amateur weather sites, or to incorporate parts in their own freely distributed scripts for use by others on such amateur sites, provided due acknowledgement is made to the work put in by SPAWS. No guarantee is offered that this script is fit for purpose, and you should quote my disclaimer above. It is possible that this particular script might crash your browser if it finds content in a METAR that it was not specifically designed to handle, because there is no source available that defines everything that this script must handle, there are a few rarely used codes that I do not even attempt to process, and I cannot anticipate everything this script might find. © Script Editor SPAWS - this particular script developed July 2017 to March 2018. --------------------------------------------------------------------------------- */ # An optional code snippet (for PHP source displaying), remove if you want! #--------------------------------------------------------------------------# # Just list the PHP source? Start of common SPAWS snippet. # # Modify URL of calling web page by adding a query-string such as # # ?viewSource='xxxxx'" or "?src='xxxxx'" or "?sce='xxxxx'" # # to see source for any file xxxxx.php whether web page or included file # #--------------------------------------------------------------------------# global $home; $home = substr(__FILE__ , 0,14); if(file_exists('sourceViewC.php')) include_once 'sourceViewC.php'; elseif(file_exists($home . 'forbidden\sourceView.php')) include_once $home . 'forbidden\sourceView.php'; /* 'require' does a fatal error if the requested file is not found, rest of code ignored; 'include' issues a warning if the requested file is not found, rest of HTML obeyed; 'require_once' and 'include_once' have same file not found action, but the once clause ensures that only one attempt is made to load the procedure. The once option is used here because some variables declared in the procedure may be used in this script, and such variables cannot have multiple declarations of them. */ # IMPORTANT - This included script provides functions 'calculateBeaufort', 'calculateTime($timeStamp)', 'add_source' and 'display_source' # but this script tests for their existence and will not fail if they are not present. if(function_exists('add_source')) add_source(__FILE__, basename(__FILE__, '.php')); # NB use of this magic constant represents the file in which it appears if( // $_GET only returns parameters as part of a GET REQUEST, this is the one where (when you start (re-)loading the file), you read information from the query-string // $_POST only returns parameters as part of a POST request, this is when a form has been completed its contents are kept in HTTP for when page reloaded // $_REQUEST will return parameters found in COOKIE, GET or POST isset($_GET['viewSource']) && $_GET['viewSource'] == basename(__FILE__, ".php") or isset($_GET['sce']) && $_REQUEST['sce'] == basename(__FILE__, '.php') or isset($_REQUEST['src']) && $_REQUEST['src'] == basename(__FILE__, '.php') ){ if(function_exists('display_source')) display_source (basename(__FILE__, '.php')); } #-----------------------------------------------------------# # End of common snippet to list source call # #-----------------------------------------------------------# // ========================================================================================================================================================== # UUUUUU UUUUUU SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS AAAAAAAA # UUUUUU UUUUUU SSSSSSSSS AAAAA AAAAA # UUUUUU UUUUUU SSSSSSSSS AAAAAA AAAAAA # UUUUUU UUUUUU SSSSSSSSS AAAAAAAAAAAAAAAAAAAAAAAAA # UUUUU UUUUU SSSSSSSSS AAAAAA AAAAAA # UUUUUUUUUUUUUUUUUUUUUUUUU SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS AAAAAA AAAAAA #====================================== # UNITED STATES OF AMERICA - Remarks #====================================== ################################################################################################################################################################################################# # The following sub-function, to decode what the USA put in the Remarks Group of a METAR report, was originally written because USAF airfields in the UK follow American Guidance for their # # METAR, not UK guidance, hence this script by SPAWS had to decode what those USAF might use, so it would work for all METAR issued in UK. USAF guidance in Surface Weather Observations: # # Air Force Manual 15-111 [U.S. Air Force] 27 February 2013 edition available at 'http://static.e-publishing.af.mil/production/1/af_a3/publication/afman15-111/afman15-111.pdf' and was not # # found when I first developed this script. There is a supplement at 'http://static.e-publishing.af.mil/production/1/pacaf/publication/afman15-111_pacafsup/afman15-111_pacafsup.pdf'. # # Instead I first relied on obsolete information in Wiki (see accompanying PDF paper). A redesign was then made based on Federal Meteorological Handbook No. 1 (September 2005 edition) # # (from http://www.ofcm.gov/publications/fmh/allfmh2.htm). The sub-function was updated for the latest edition (FCM-H1-2017) effective 30 November 2017 with 45 specifications for Remarks # # found at 'http://www.ofcm.gov/publications/fmh/FMH1/FMH1_2017.pdf'. That handbook is 98 pages long and it devotes more than 17 pages to Remarks Group specifications, so it is not # # surprising that it took an awful lot of development/testing to modify this script suite to cope with the METAR actually experienced, not just in UK but also in aerodromes with USA connections throughout the world. # # In the other scripts making up this SPAWS decoder there are cross-references to the paragraphs in the handbook specifying the format of what needs to be decoded. # # If a segment cannot be decoded, it is reported as transmitted, but the whole METAR is also added to a log, so that this sub-function can be amended to cope with that segment # # when it is next used. Such amendments are written to cope with not just what could not be decoded before, but also similar possibilities in the relevant specification, and # # thanks to the log the revised script can be retested against that same METAR. # ########################################################################################################################################################################################## function decode_USA_remarks() { global $metar_toDecode, $decodeInfo, $decodeGroupCount, $decodeGroupProcessed, $pressureHPA, $temp, $dp, $usaf2013, $fmh1_2017, $dM_Rmk; // shared with other scripts global $show_diagnostics, $show_diagnosticsR, $descriptorArray, $precipitationTypes, $cloudAmountBandCode, $compass, $compass_expanded, $hoursOffset, $file, $lastMatchUSA; // shared with other sub-functions global $descLocal, $precipLocal, $outputLocal, $restLocal, $remarks_toDecode, $passCountUSA, $specification, $startUSA, $stopUSA; // shared with other sub-functions $lastMatchUSA = -1; if(count($metar_toDecode) < 1) goto exit_point; $timeArray = array (); // Initialise array used for converting UTC times to local times $returnArray = array(); /////////////////////////////////////////////////// // USAF Remarks Group - Group specifications // /////////////////////////////////////////////////// // 2018/01/13 17:54 KBUF 131754Z 33010KT 9SM SN BKN023 M11/M16 A3027 RMK AO2 SNB06 SLP265 PCPN VRY LGT 4/006 933007 P0000 60000 T11111156 11106 21117 51015 $ if(!array_key_exists('USAF', $decodeGroupCount)) $decodeGroupCount['USA_walk'] = $decodeGroupCount['USAF'] = 0; $usaf2013 = array( 'U. S. Air Force Manual 15-111: Surface Weather Observations - Attachment 3 (Remarks Groups) - 27 February 2013 edition', 'Remark number 1 - Volcanic eruption', 'Remark number 2 - Tornadic activity - Tornadic activity_B/E(hh)mm_ LOC/DIR_(MOV)', 'Remark number 3 - Augmented or Automated Systems - AO1, AO2 or AO2A', 'Remark number 4 - Peak wind - PK_WND_dddff(f)/(hh)mm', 'Remark number 5 - Wind shift - WSHFT_(hh)mm', 'Remark number 6 - Tower visibility - TWR_VIS_vvvvv', 'Remark number 7 - Variable prevailing visibility - VIS_vnvnvnvnvnVvxvxvxvxvx', 'Remark number 8 - Sector visibility - VIS_[DIR]_vvvvv_[Plain Language]', 'Remark number 9 - Visibility at second location - VIS_vvvvv_[LOC]', 'Remark number 10 - Lightning - LTG DSNT followed by the direction', 'Remark number 11 - Beginning and Ending of Precipitation - w’w’B(hh)mmE(hh)mm', 'Remark number 12 - Beginning and Ending of thunderstorms - TSB(hh)mmE(hh)mm', 'Remark number 13 - Thunderstorm location - TS_LOC_(MOV_DIR)', 'Remark number 14 - Hailstone Size - GR_[size]_[Plain Language]', 'Remark number 15 - Variable ceiling height - CIG_hnhnhnVhxhxhx', "Remark number 16 - Partial Obscurations - w'w'_[NsNsNs]hshshs_[Plain Language]", 'Remark number 17 - Variable sky condition - NsNsNs(hshshs) _V_NsNsNs_[Plain Language]', array( 'Remark number 18 - Significant cloud types', '1)CB or CBMAM_LOC_(MOV_DIR)_[Plain Language]', '2) TCU_[DIR]_[Plain Language]', '3) ACC_[DIR]_[Plain Language]', '4) CLD_[DIR]_[Plain Language]', ), 'Remark number 19 - Ceiling height at second location - CIG_hhh_[LOC]', 'Remark number 20 - Pressure rising or falling rapidly - PRESRR or PRESFR', 'Remark number 21 - Sea level pressure - SLPppp or SLPNO', 'Remark number 22 - Aircraft mishap - Indicate non transmission by enclosing the remark (ACFT_MSHP) in parentheses', 'Remark number 23 - Snow increasing rapidly - SNINCR_[inches-hour/inches on ground]', array( 'Remark number 24 - Other Significant Information - [Plain Language Remarks]', '(1) Unofficial Weather Reports', '(2) Estimated Wind and Pressure', '(3) Significant Atmospheric Phenomena not Reported Elsewhere', '(4) Aurora observed in the past hour', '(5) Condensation Trails', '(6) Location Unique Information', ), 'Remark number 25 - Hourly precipitation amount - Prrrr', 'Remark number 26 - 3- and 6-Hourly precipitation amount - 6RRRR', 'Remark number 27 - 24-Hourly precipitation amount (reporting time variant each side of international date line (IDL)) - 7R24R24R24R24', 'Remark number 28 - Snow depth on ground - 4/sss', 'Remark number 29 - Hourly temperature and dew point - TsnT’T’T’snT’dT’dT’d or TsnT’T’T’', 'Remark number 30 - 6-hourly maximum temperature - 1snTxTxTx', 'Remark number 31 - 6-hourly minimum temperature - 2snTnTnTn', 'Remark number 32 - 24-hour maximum temperature and the 24-hour minimum temperature - 4snTxTxTxsnTnTnTn', 'Remark number 33 - 3-hourly pressure tendency - 5appp', 'Remark number 34 - Sensor status indicators - RVRNO, PWINO, PNO, FZRANO, TSNO, VISNO (LOC), CHINO (LOC)', 'Remark number 35 - maintenance indicator sign - $', 'Remark number 36 - LAST (e.g., TCU SE LAST), FIRST, CORR mm', ); $decodeGroupProcessed['USAF'] = $usaf2013[0]; /////////////////////////////////////////////////// // USA Remarks Group - Group specifications // // not in either USAF or FAA manuals // /////////////////////////////////////////////////// if(!array_key_exists('USA_extras', $decodeGroupCount)) $decodeGroupCount['USA_extras'] = 0; $usa_extras = array( 'Specifications not in either USAF or FMH manuals', 'Observer reported: Plain Language, PAST HR guess delay to Barometer Reading', # 1 'Observer reported: Snow on Ground SOG', # 2 'Observer reported: Prevailing Visibility (4 digits)', # 3 'Higher or lower Visibility in particular compass direction', # 4 'Higher or lower Ceiling in particular compass direction', # 5 'Very Light Precipitation', #6 ); $decodeGroupProcessed['USA_extras'] = $usa_extras[0]; ///////////////////////////////////////////////////////////////////////////////// // Federal Meteorological Handbook - Remarks Group - Group specifications // ///////////////////////////////////////////////////////////////////////////////// if(!array_key_exists('FMH1', $decodeGroupCount)) $decodeGroupCount['FMH1'] = 0; $fmh1_2017 = array('Federal Meteorological Handbook No.1 - November 2017 edition (FCM-H1-2017): Surface Weather Observations and Reports', // Multi-segment specifications # METAR/SPECI remarks fall into 2 categories: (1) Automated, Manual, and Plain Language (see paragraph 12.7.1) '12.7.1 a. Volcanic eruptions', # 1 '12.7.1 b. Funnel Cloud and Tornado activity', # 2 '12.7.1 c. Type of automated station', # 3 '12.7.1 d. Peak wind (PK_WND_dddff(f)/(hh)mm)', # 4 '12.7.1 e. Wind shift (WSHFT_(hh)mm)', # 5 '12.7.1 f. Tower and surface visibility (TWR_VIS_vvvvv or SFC_VIS_vvvvv)', # 6 '12.7.1 g. Variable prevailing visibility (VIS_vvvvvVvvvvv)', # 7 '12.7.1 h. Sector visibility (VIS_[DIR]_vvvvv)', # 8 '12.7.1 i. 2nd visibility (VIS_vvvvv_[LOC])', # 9 '12.7.1 j. Lightning (Frequency_LTG(type)_[LOC])', # 10 # METAR/SPECI remarks fall into 2 categories: (1) Automated, Manual, and Plain Language (see paragraph 12.7.1) // Single segment codes - decode using array walk technique // ===========================================================================================// // The following specifications are handled within an array_walk function ===================// '12.7.1 k. Precipitation timings (wwB[hh]mmE[hh]mm...)', # 11 // just type of rain/drizzle until 2017, from 2017 adds type of snow, and permits descriptors of freezing and of showers '12.7.1 l. Thunderstorm timings (TSB(hh)mmE(hh)mm)', # 12 // Multi-segment specifications '12.7.1 m. Thunderstorm location (TS_LOC_(MOV_DIR))', # 13 '12.7.1 n. Hailstone size (GR_[size])', # 14 # From here on all paragraph references changed (for November 2017 changes in USA handbook replacing September 2005 edition) with hailstone/snow separation # - change was made in this script at version 0.1.4 - September 2017 '12.7.1 o) Snow pellets (GS_[intensity])', # 15 '12.7.1 p) Wisps of precipitation evaporating before reaching the ground (VIRGA_(DIR))', # 16 '12.7.1 q) Variable ceiling height (CIG_hhhVhhh)', # 17 '12.7.1 r) Obscurations (w w _[NNN]hhh)', # 18 '12.7.1 s) Variable sky (NNN(hhh)_V_NNN)', # 19 '12.7.1 t) Significant cloud type', # 20 '12.7.1 u) 2nd ceiling height (CIG_hhh_[LOC])', # 21 # METAR/SPECI remarks fall into 2 categories: (1) Automated, Manual, and Plain Language (see paragraph 12.7.1) // Single segment codes - decode using array walk technique // ===========================================================================================// // The following specifications are handled within an array_walk function ===================// '12.7.1 v) Pressure trend (PRESRR/PRESFR)', # 22 '12.7.1 w) Sea-level pressure (SLPppp)', # 23 // Multi-segment specifications '12.7.1 x) Mishap (ACFT_MSHP)', # 24 '12.7.1 y) Manual station not reporting changes (NOSPECI)', # 25 - sequence in handbook, content here may be FIRST in real METAR? '12.7.1 z) Snow (SNINCR_[inches-hour/inches on ground])', # 26 'paragraph 12.7.1 aa) Other significant', # 27 # METAR/SPECI remarks fall into 2 categories: (2) Additive and Maintenance Data (see paragraph 12.7.2). // ===========================================================================================// // The following specifications are handled within an array_walk function ===================// // Single segment codes - decode using array walk technique '12.7.2 e. 6 hour max temp - 5 digits with first digit being "1" (1snTxTxTx)', #28 '12.7.2 f. 6 hour min temp - 5 digits with first digit being "2" (2snTnTnTn)', #29 '12.7.2 h. 3 hour pressure trend - 5 digits with first digit being "5" (5appp)', #30 '12.7.2 a. (3) (b) 3 and 6 hourly precipitation - 5 digits with first digit being "6" (6RRRR)', #31 '12.7.2 a. (3) (c) 24 hour precipitation - 5 digits with first digit being "7" (7RRRR)', #32 '12.7.2 a. (3) (a) Hourly precipitation - letter "P" then 4 digits (Prrrr)', #33 '12.7.2 a. (3) (e) Water equivalent - 6 digits with first digit being "9" and second "3" (933RRR)', #34 '12.7.2 g. 24 hour temp extremes - 9 digits with first digit being "4" (4snTxTxTxsnTnTnTn)', #35 '12.7.2 c. Sunshine - 6 digits with first digit being "9" and second "8" (98mmm)', #36 '12.7.2 a. (3) (b) 3 and 6 hourly precipitation not available (6////)', #37 '12.7.2 a. (3) (d) Snow depth (4/sss)', #38 '12.7.2 b. 3 and 6 hour Cloud Types (8/ClCmCh)', #39 "12.7.2 d. Temperature and dew-point (more precise) (TsnT'T'T'snT'dT'dT'd)", #40 '12.7.2 i. ice (I1nnn), (I3nnn and I6nnn)', #41 // Also Tested in the array walk: (but any malformed versions processed into correct format outside this array walk in main USA sub-function) '12.7.2 k. Maintenance Indicator. ($)', #42 // ===========================================================================================// // Multi-segment specifications # METAR/SPECI remarks fall into 2 categories: (2) Additive and Maintenance Data (see paragraph 12.7.2). '12.7.2 j. sensor status', #43 ); $decodeGroupProcessed['FMH1'] = $fmh1_2017[0]; // 12.7.1 Automated, Manual, and Plain Language Remarks. a. Volcanic Eruptions (Plain Language). // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 1 #if(in_array('VOLCANO', $metar_toDecode)) if(array_key_exists('VOLCANO', $remarks_toDecode)) { $specification = $usaf2013[1]; $processed = '(' . $decodeGroupCount['REMARK'] . ') → RMK_'; // contains the processed segments from the raw METAR $output = ''; // contains the output from the decoder // Since don't know how many segments are being used to convey the plain English message, can only output the whole lot up to dayTime in first output message. $j = 0; $k = count($metar_toDecode); for($iCount=0;$iCount<$k;$iCount++) { if(strlen($metar_toDecode[$iCount + $j]) == 6 and is_numeric($metar_toDecode[$iCount + $j])) { $day = substr($metar_toDecode[$iCount + $j], 0, 2); $timeArray = adjustTime(substr($metar_toDecode[$iCount + $j], 2, 4)); $output .= 'at ' . $timeArray['hour'] . ':' . $timeArray['minute'] . ' (local time)'; do_output($decodeGroupCount['REMARK'], $iCount, $output, $fmh1_2017[1] . ' and ' . $usaf2013[1]); $j = $iCount; $output = ''; // contains the output from the decoder // Since don't know how many segments are being used to convey the plain English message, can only output all those found after dayTime in second message. }else $output .= $metar_toDecode[$iCount + $j]; if($iCount < $k and $j != $iCount) { $processed .= '_'; $output .= ' '; } } do_output($decodeGroupCount['REMARK'], $iCount - $j, $output, $fmh1_2017[1] . ' and ' . $usaf2013[1]); $decodeGroupCount['FMH1']++; $specification = 60; // nothing left to decode $lastMatchUSA = 1; goto exit_point; } $passCountUSA++; // track passes through this function optionalDiagnosticOutput( $fmh1_2017[0] . ' and ' . $usaf2013[0] . ' - USA pass ' .$passCountUSA . ' starts', $file); /* A number of different design approaches have been tried for decoding the content of the Remarks Group issued by United States Air Force establishments in USA and other countries, and Civilian aerodromes in the United States of America. One problem is that the USAF use a subset of the codes in the Federal Meteorological Handbook, and in slightly different sequence. The only way to identify whether USAF or FMH1 specifications apply, to determine the correct sequence would be to maintain a list of all relevant ICAO codes and their respective Group list. Also despite both documents specifying the desired order that the Groups they specify should be used, an Observer at that aerodrome could choose to use a different order or a different format. Some of the Group Specifications have a format of a single segment with the first one or two characters of that segment being the key that identifies the specification. These can be handled by running all the segments in the Remarks Group through an array walk that runs through all the tests in the function USA_walk() for each segment. That slows down the more segments it has to analyse although use of switch and case (where possible) has streamlined the checking of all the relevant specifications. The design question posed is when to do this walk, the apparent answer is as early as possible to reduce the number of segments that have to be tested against group specifications that involve multiple segments. */ // Single segment codes - decode using array walk technique$beforeThis = count($metar_toDecode); /* USAF Remark number 11, in the Federal Meteorological Handbook as '12.7.1 Automated, Manual, and Plain Language Remarks. k. Beginning and Ending of Precipitation' and the equivalent for thunderstorms allows multiple observations to be included in the same segment, in these two cases this script will separate off each part as it decodes it, creating multiple processed single specifications, for ease of consistent processing. */ $beforeThis = count($metar_toDecode); if($passCountUSA < 2) { optionalDiagnosticOutput('Remarks Group new_get_remarks() - USA section: STARTING array walk for single segments', $file); if($show_diagnostics or $show_diagnosticsR) print_array($metar_toDecode, 'calling array walk USA for'); /* // The following specifications are handled within this array_walk function '12.7.1 k. Precipitation timings (wwB[hh]mmE[hh]mm...)', // just type of rain/drizzle until 2017, from 2017 adds type of snow, and permits descriptors of freezing and of showers '12.7.1 l. Thunderstorm timings (TSB(hh)mmE(hh)mm)', '12.7.2 e. 6 hour max temp - 5 digits with first digit being '1' (1snTxTxTx)', '12.7.2 f. 6 hour min temp - 5 digits with first digit being '2' (2snTnTnTn)', '12.7.2 h. 3 hour pressure trend - 5 digits with first digit being '5' (5appp)', '12.7.2 a. (3) (b) 3 and 6 hourly precipitation - 5 digits with first digit being '6' (6RRRR)', '12.7.2 a. (3) (c) 24 hour precipitation - 5 digits with first digit being '7' (7RRRR)', '12.7.2 a. (3) (a) Hourly precipitation - letter 'P' then 4 digits (Prrrr)', '12.7.2 a. (3) (e) Water equivalent - 6 digits with first digit being '9' and second '3' (933RRR)', '12.7.2 g. 24 hour temp extremes - 9 digits with first digit being '4' (4snTxTxTxsnTnTnTn)', '12.7.2 c. Sunshine - 6 digits with first digit being '9' and second '8' (98mmm)', '12.7.2 a. (3) (b) 3 and 6 hourly precipitation not available (6////)', '12.7.2 a. (3) (d) Snow depth (4/sss)', '12.7.2 b. 3 and 6 hour Cloud Types (8/ClCmCh)', '12.7.2 d. Temperature and dew-point (more precise) (TsnT'T'T'snT'dT'dT'd)', '12.7.2 i. ice (I1nnn), (I3nnn and I6nnn)', // Also Tested in the array walk: (but any malformed versions processed into correct format outside this array walk in main USA sub-function) '12.7.2 k. Maintenance Indicator. ($)', // ===========================================================================================// */ array_walk($remarks_toDecode, 'USA_walk' ); $decodeGroupCount['USA_walk'] = $beforeThis - count($metar_toDecode); if(count($metar_toDecode) == $beforeThis) { # nothing decoded by array walk, place to specify action, but none identified yet if($show_diagnosticsR) echo '
Nothing decoded by USA array walk'; }else{ if($show_diagnosticsR) echo '
' . $decodeGroupCount['USA_walk'] . ' segments decoded by USA array walk'; if(count($metar_toDecode) < 1) goto exit_point; // if all segments decoded by array_walk } optionalDiagnosticOutput('Remarks Group new_get_remarks() - USA section: FINISHED array walk for single segments, continuing with remaining segments ' . $decodeGroupCount['USA_walk'] . ' segments decoded by USA array walk', $file); } while(count($metar_toDecode) > 0) { /* The remaining Group Specifications, from both documents, involve multiple segments, basically the standard defines that a space should be added between the code defining the observation type, and the observation detail. In some cases, further spaces are specified in the standard between the various parts of the details. In all these, this function will need to identify the segment containing the observation type, and then decide which of the subsequent segments are linked to that recognised segment. There are therefore two problems, first how to recognise the segment that determines the observation type (as in some cases the same first segment is shared between multiple specifications and sometimes it is the second or third segment that actually uniquely identifies the specification), and the second problem is to determine how many segments are consumed by that specification (as some segments are optional and some METAR are malformed with spaces in wrong positions). There are therefore a lot of different design approaches to try, and whatever is done, there are two outputs (one gives an explanation of the meaning of the multiple segments in British English, and the other using '_' as separator will combine the processed segments making up a single specification so the final part of the function sees it as a single processed specification for ease of consistent processing). Initially this script picked the sequence in the FMH1, and worked through each Group Specification in turn, looking just at the segments appearing first in the still to be decoded array. If a match was found, then the matched segments were discarded from the still to be decoded array, and the first remaining segment was tested first against only those Group Specifications after the one that had been matched, although a second pass of earlier ones followed. If the first unmatched segments could not be matched to one Group specification, then the next Group Specification was tried, and when the end of the list was reached, a second pass was made, if that too failed just the very first not yet decoded segment was discarded as being impossible to match and the whole loop restarted with what had been the second segment. This had two significant disadvantages, first that it is slow and resource demanding to do so many tests, generally multiple times, and could result in a crash because of using too much memory or too much processing time. Second, it relied on making a second pass to deal with any specifications that were out of sequence, but that just added more delay and more resource loading. The number of failures to decode Remarks Group content continued to increase, partly because it was discovered that codes not in FMH1 were frequently being use, and partly because some malformed Groups were seen. Also a revision of FMH1 taking effect from November 2017 increased the complexity of several of the conditions that were being tested. In the final count there are around sixty different Specification Groups that can be used for the content of the Remarks Group, so working through all sixty for each segment found is very slow and resource wasting. The only way to speed up the processing of these multiple segments is to look at the first still to be decoded segment and try to quickly identify a sub-set of the 60 specifications to try. This led into several attempts to find the best design for this script to do that approach. In one attempt, either a unique first character or a unique whole first segment of the multi-segment specification was used to make a jump, otherwise all the specifications were still tried in sequence. This achieved some savings in time, but unfortunately in many cases, a large number of steps were still occurring because the codes were not recognised by those jumps. Now, recognising that using the first two characters of the first segment of multi-segment specifications was a reasonably good way to quickly reduce the number of possibilities, another design is being tried totally ignoring any sequence constraints. Instead the design works by using switch and case (based on this possibility reduction) as the most efficient condition selection in the PHP language. */ $passCountUSA ++; $beforeThis = count($metar_toDecode); optionalDiagnosticOutput( $fmh1_2017[0] . '
and ' . $usaf2013[0] . ' - USA pass ' .$passCountUSA . ' starts', $file); //==================================================================================================// // First deal with complicated specifications where lots of different first segments possible... // //==================================================================================================// // USA FMH1 - 12.7.2 Additive and Automated Maintenance Data. j. sensor status Indicators. // Air Force Manual 15-111 [U.S. Air Force] Surface Weather Observations: Remark Number 34 // Not tested in the array walk: '12.7.2 j. sensor status', $iCount = 1; $lastMatchUSA = $usaf2013[34]; switch($metar_toDecode[0]){ case 'RVRNO': $output = 'Runway Visual Range sensor faulty'; break; case 'PWINO': $output = 'Present Weather sensor faulty'; break; case 'PNO': $output = 'Precipitation (tipping bucket rain gauge) sensor faulty'; break; case 'FZRANO': $output = 'Precipitation (freezing rain) sensor faulty'; break; case 'TSNO': $output = 'Precipitation (lightning) sensor faulty'; break; case 'VISNO': $output = 'Visibility at ' . $metar_toDecode[1] . ' not available'; $iCount = 2; break; case 'CHINO': $output = 'Cloud Height Indicator at ' . $metar_toDecode[1] . ' not available'; $iCount = 2; break; default: if($show_diagnosticsR) echo '
Not matched to ' . $fmh1_2017[43] . ' (' . $lastMatchUSA . ')'; goto skip0; } do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[43]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; skip0: // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. t) (previously s.) Significant Cloud Types // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 18 /* Encode significant cloud types as follows. Identify cumulonimbus (CB) of any kind and towering cumulus (TCU) in the body of the report in the sky condition group. Include distance if known. (1) Cumulonimbus or Cumulonimbus Mammatus as appropriate, (when no thunderstorm is being reported) in format (CB or CBMAM_LOC_(MOV_DIR)_[Plain Language] where CB or CBMAM is the cloud type, LOC is the direction from the observing location, and MOV_DIR is the movement with direction (if known). Separate the cloud type entries from each other with a space. For example, a CB 21 nautical miles west of the observing location moving toward the east would be encoded 'CB 21W MOV E.' If a CB is more than 10 nautical miles to the west and distance cannot be determined, encode as 'CB DSNT W.' (2) Towering cumulus in format TCU_[DIR]_[Plain Language], where TCU is cloud type and DIR is direction from the observing location. Separate the cloud type entries by a space. For example, a towering cumulus cloud up to 10 nautical miles west of the observing location would be encoded 'TCU W.' (3) Altocumulus Castellanus in format, ACC_[DIR]_[Plain Language], where ACC is cloud type and DIR is direction from the observing location. Separate the cloud type entries by a space. For example, an ACC cloud 5 to 10 nautical miles north-west of the observing location would be encoded 'ACC NW.' (4) Stratocumulus(SCSL), altocumulus (ACSL), or cirrocumulus (CCSL), or rotor clouds in format CLD_[DIR]_[Plain Language], where CLD is cloud type and DIR is direction from the observing location. Separate the cloud type entries by a space. For example, ACSL clouds observed southwest through west of the observing location would be encoded ACSL SW-W ; an apparent rotor cloud northeast of the observing location would be encoded APRNT ROTOR CLD NE ; and CCSL clouds south of the observing location would be encoded CCSL S. */ $iCount = 1; switch($metar_toDecode[0]) { case 'TCU': $output = 'Towering cumulus cloud seen'; $lastMatchUSA = $usaf2013[18][2]; break; case 'ACC': $output = 'Altocumulus Castellanus cloud seen up to 10 miles away'; $lastMatchUSA = $usaf2013[18][3]; break; case 'SCSL': $output = 'Stratocumulus cloud seen'; $lastMatchUSA = $usaf2013[18][4]; break; case 'ACSL': $output = 'Altocumulus cloud seen'; $lastMatchUSA = $usaf2013[18][4]; break; case 'CCSL': $output = 'Cirrocumulus cloud seen'; $lastMatchUSA = $usaf2013[18][4]; break; case 'APRNT': $output = 'Apparent ' . $metar_toDecode[1]; $iCount++; if($metar_toDecode[1] == 'CLD') { $iCount ++; $output .= ' cloud seen'; } $lastMatchUSA = $usaf2013[18][4]; break; case 'CB': $output = 'Cumulonimbus '; if(is_numeric(substr($metar_toDecode[1], 0, 2)) && in_array(substr($metar_toDecode[1], 2), $compass)) { $iCount++; $distance = is_numeric(substr($metar_toDecode[1], 0, 2)) ? (1 * substr($metar_toDecode[1], 0, 2)) : (1 * substr($metar_toDecode[1], 0, 1)); $distance .= ' nautical miles to the '; $direction =substr($metar_toDecode[1], 0, 2) ? substr($metar_toDecode[1], 2) : substr($metar_toDecode[1], 1); $direction = $compass_expanded[$direction]; $output .= 'seen in a location ' . $distance . $direction . ' of the observation point'; } if($metar_toDecode[$iCount] == 'MOV') { $iCount ++; $output .= ', the weather system is moving '; } $lastMatchUSA = $usaf2013[18][1]; break; case 'CBMAM': $output = 'Cumulonimbus Mammatus '; if(is_numeric(substr($metar_toDecode[1], 0, 2)) && in_array(substr($metar_toDecode[1], 2), $compass)) { $iCount++; $distance = is_numeric(substr($metar_toDecode[1], 0, 2)) ? 1 * substr($metar_toDecode[1], 0, 2) : 1 * substr($metar_toDecode[1], 0, 1); $distance .= ' nautical miles (' . number_format(1.852 * $distance) . ' km) to the '; $direction =substr($metar_toDecode[1], 0, 2) ? substr($metar_toDecode[1], 2) : substr($metar_toDecode[1], 1); $direction = $compass_expanded[$direction]; $output .= 'seen in a location ' . $distance . $direction . ' of the observation point'; } if($metar_toDecode[$iCount] == 'MOV') { $iCount ++; $output .= ', the weather system is moving '; } $lastMatchUSA = $usaf2013[18][1]; break; default: if($show_diagnosticsR) echo '
Not matched to ' . $fmh1_2017[20] . ' (' . $usaf2013[18][0] . ' → ' . $usaf2013[18][1] . ')'; goto skip1; } if(count($metar_toDecode) > $iCount and $metar_toDecode[$iCount] == 'DSNT') { $iCount++; $output .= ' more than 10 miles (16 km) towards '; } if(count($metar_toDecode) > $iCount and in_array($metar_toDecode[$iCount], $compass)) { $output .= ' ' . $compass_expanded[$metar_toDecode[$iCount]]; $iCount++; }elseif(strpos($metar_toDecode[$iCount], '-') !== false) { $before = substr($metar_toDecode[$iCount], 0, strpos($metar_toDecode[$iCount], '-')); if(in_array($before, $compass)) $output .= ' ' . $compass_expanded[$before] . ' through '; $after = substr($metar_toDecode[$iCount], 1 + strlen($before)); if(strpos($after, '-') === false) { if(in_array($after, $compass)) $output .= $compass_expanded[$after]; $iCount++; }else{ $before = substr($after, 0, strpos($after, '-')); if(in_array($before, $compass)) $output .= $compass_expanded[$before] . ' to '; $after = substr($after, 1 + strlen($before)); if(in_array($after, $compass)) $output .= $compass_expanded[$after]; $iCount++; } } $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[20]); goto end_while; skip1: if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' looking at ' . substr($metar_toDecode[0], 0, 2); //==================================================================================================// // Now deal with less complicated specifications where first segment easy to categorise... // //==================================================================================================// switch(substr($metar_toDecode[0], 0, 2)) { case '00': case '01': case '02': case '03': case '04': case '05': case '06': case '07': case '08': case '09': case '10': case '11': case '12': case '13': case '14': case '15': case '16': case '17': case '18': case '19': case '20': case '21': case '22': case '23': case '24': case '25': case '26': case '27': case '28': case '29': case '30': case '31': case '32': case '33': case '34': case '35': case '36': case '37': case '38': case '39': case '40': case '41': case '42': case '43': case '44': case '45': case '46': case '47': case '48': case '49': case '50': case '51': case '52': case '53': case '54': case '55': case '56': case '57': case '58': case '59': case '60': case '61': case '62': case '63': case '64': case '65': case '66': case '67': case '68': case '69': case '70': case '71': case '72': case '73': case '74': case '75': case '76': case '77': case '78': case '79': case '80': case '81': case '82': case '83': case '84': case '85': case '86': case '87': case '88': case '89': case '90': case '91': case '92': case '93': case '94': case '95': case '96': case '97': case '98': case '99': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found digits at ' . substr($metar_toDecode[0], 0, 2); // USA Observer reported: Manual Prevailing Visibility (4 digits) // This interpretation has been assumed based on the non-appearance of anything from automatic sensor in WMO Standard Prevailing Visibility Group in the examples below. // USA does not normally use metres for visibility, indeed Handbook 1 says '6.5.1 Unit of Measure. Visibility shall be reported in statute miles.' // But I am still fairly certain the manually inserted figure here is in metres, because it does not have fractions, while the 6.5.1. figures are in fractional miles. // Flight Utilities decodes any 4 digit numerical code by taking its value as visibility in metres whether the code appears in Prevailing Visibility Group or Remarks Group // (NOAA decoder ignores a 4 digit number in Remarks) // (metaf2xml decoder treats a 4 digit number (I'm sure incorrectly) as a pressure change in the last 3 hours) if(strlen($metar_toDecode[0]) == 4 and is_numeric($metar_toDecode[0])) { // 2017/12/29 09:00 METAR CXDI 290900Z AUTO 07001KT M13/M15 RMK AO1 3PAST HR 8020 SLP270 P0001 T11291149 50003 // 2017.12.29 1100 UTC METAR CXDI 291100Z AUTO 09003KT M13/M14 RMK AO1 5PAST HR 7023 SLP257 P0002 T11271135 50010 optionalDiagnosticOutput('WMO Remarks Group: "Prevailing Visibility" checking'); if($show_diagnosticsR) echo '
WMO Mandatory Standard Group: "Prevailing Visibility" examining ' . $metar_toDecode[0] . ' in Remarks Group'; $input = novel_allVisibility($metar_toDecode[0], '', '', 'WMO Prevailing Visibility Group'); if(isset($input['pV_match']) and $input['pV_match']) { $lastMatchUSA = $usa_extras[3]; $decodeGroupCount['USA_extras']++; $decodeInfo['BY'] = 'Automated observations with Observer augmentation'; $decodeGroupCount['BY']++; if(strpos($decodeGroupProcessed['BY'], 'RMK') !== false) $decodeGroupProcessed['BY'] = substr($decodeGroupProcessed['BY'], 0, -1) . '_' . $metar_toDecode[0] . '}'; else $decodeGroupProcessed['BY'] .= '_{RMK_' . $metar_toDecode[0] . '}'; // Prevailing visibility means furthest distance seen that applies for at least half of the 360 degree horizon (not necessarily a continuous half) $decodeInfo['VISIBILITY'] = $input['prevailVisibility'] . ' (by manual observation as automatic sensor failed)'; $done = array_shift($metar_toDecode); // take out processed segment $remarks_toDecode[$done] = 'Pass =' . $passCountUSA . ', specification=' . $lastMatchUSA; $decodeGroupProcessed['VISIBILITY'] = 'RMK_' . $done; if($show_diagnosticsR) echo '

Decoding Remarks Group → matched against pass=' . $passCountUSA . ' ' . $lastMatchUSA . '>> ' . ' Manual Prevailing Visibility (4 digits) → ' . $done . '

'; goto end_while; } } break; case 'AC': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found AIRCRAFT at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. x) (previously w.) Aircraft Mishap (ACFT_MSHP) // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 22 if(array_key_exists(1, $metar_toDecode) and $metar_toDecode[0] == 'ACFT' and $metar_toDecode[1] == 'MSHP') { $output = 'This report is taken to document weather conditions when notified of an aircraft mishap'; $lastMatchUSA = $usaf2013[22]; do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[24]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'AL': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found ALTIMETER at ' . substr($metar_toDecode[0], 0, 2); // USA Air Force 'USAF Manual 15-111: Remark Number 24: (2) Estimated Wind and Pressure', /* (2) Estimated Wind and Pressure. WND DATA ESTMD or ALSTG/SLP ESTMD indicates the winds and/or pressure values from the primary airfield sensors are suspect or inoperative, and back-up equipment is being used. Report winds as estimated if using the AN/TMQ-53 as a back-up at permanent (non-tactical) locations. */ if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'ALSTG/SLP' and $metar_toDecode[1] == 'ESTMD') { // 'Estimated pressure SLP and ALSTG' // AL~STG stands for Altimeter Setting $lastMatchUSA = $usaf2013[24][2]; $output = 'All pressure readings reported in this METAR are based upon estimated observations'; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' - Estimated all pressures' . ''); $decodeGroupCount['USAF']++; goto end_while; }elseif(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'ALSTG' and $metar_toDecode[1] == 'ESTMD') { // 'Estimated pressure ALSTG' // AL~STG stands for Altimeter Setting $lastMatchUSA = $usaf2013[24][2]; $output = 'The Altimeter (airfield level) pressure reported in this METAR is based upon estimated observations'; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' - Estimated altimeter pressure' . ''); $decodeGroupCount['USAF']++; goto end_while; } break; case 'AU': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found AURORA at ' . substr($metar_toDecode[0], 0, 2); // USA Air Force 'USAF Manual 15-111: Remark Number 24: (4) Aurora observed in the past hour', // (4) Aurora observed in the past hour. Include AURBO in the next METAR and subsequent METARs throughout period of occurrence. if($metar_toDecode[0] == 'AURBO') { $output = 'Aurora observed in the past hour'; $lastMatchUSA = $usaf2013[24][4]; do_output($decodeGroupCount['REMARK'], 1, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> (4) Aurora observed in the past hour'); $decodeGroupCount['USAF']++; goto end_while; } break; case 'BK': case 'FE': case 'OV': case 'SC': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found CLOUD AMOUNT at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. s) (previously r.) Variable Sky Condition // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 17 if(count($metar_toDecode) > 1 and strpos($metar_toDecode[1],'V') !== false) { /* The variable sky condition remark shall be coded in the format, NsNsNs (hshshs)_V_ NsNsNs, where NsNsNs (hshshs) and NsNsNs identifies the two operationally significant sky conditions and V denotes the variability between the two ranges (see paragraphs 9.4.2.d and 9.5.9). If there are several layers with the same sky condition amount, the layer height (hshshs) of the variable layer shall be coded. For example, a cloud layer at 1,400 feet that is varying between broken and overcast would be coded 'BKN014 V OVC'. */ $output = ' The cloud reported as ' . $cloudAmountBandCode[substr($metar_toDecode[0], 0, 3)]; if(is_numeric(substr($metar_toDecode[0],3))) { $altitudeFT = (integer) 100 * substr($metar_toDecode[0],3); $altitudeM = 'with layer height between ' . 10 * round(($altitudeFT - 50)/32.8084) . ' and ' . 10 * round(($altitudeFT + 50)/32.8084); $output .= ' at a ' . $altitudeM . ' m (reported as ' . $altitudeFT . 'ft)'; } $output .= ' has varied to ' . $cloudAmountBandCode[$metar_toDecode[2]]; $lastMatchUSA = $usaf2013[17]; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[19]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'CI': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found CEILING at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - '12.7.1 q) Variable Ceiling Height (CIG_h h h Vh h h ). /* 9.4.7 Height of Sky Cover. b) Height of Layers. The height of a layer shall be the height of the cloud bases or obscurations for the layer being evaluated. Layers of clouds that are 50 feet or less above the surface shall be observed as layers with a height of zero. When the height of a ceiling layer increases and decreases rapidly by the amounts given in Table 9-2, during the period of evaluation, it shall be considered variable and the ascribed height shall be the average of all the varying values. At mountain stations, clouds below the level of the station may be observed. 9.5.7 Variable Ceiling. When the height of the ceiling layer is variable, and the ceiling layer is below 3,000 feet, a remark shall be included in the report giving the range of variability (see paragraphs 9.4.7.b and 12.7.1.q). 12.7.1 q) Variable Ceiling Height The variable ceiling height shall be coded in the format, CIG_h h h Vh h h where CIG is the remark identifer, h h h is the lowest ceiling height evaluated, V denotes variability between two values, and h h h is the highest ceiling height evaluated (see paragraph 9.5.7 and Table 9-1). There shall be one space following the remark identifier; no spaces between the letter V and the lowest/highest ceiling values. For example, “CIG 005V010” would indicate a ceiling that was varying between 500 and 1,000 feet. */ // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 15 Variable Ceiling Height /* Encode variable ceiling height (height variable and ceiling layer Ceiling Height below 3000 feet) in format, CIG_hnhnhnVhxhxhx, where CIG is the remark identifier, hnhnhn is the lowest ceiling height evaluated, V denotes variability between two values, and hxhxhx is the highest ceiling height evaluated. There is one space following the remark identifier; no spaces between the letter V and the lowest/highest ceiling values. For example, “CIG 005V010” would indicate a ceiling that was varying between 500 and 1000 feet. */ if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'CIG' and strpos($metar_toDecode[1],'V') !== false) { if(preg_match('~^([0-9]{3})([0-9]{3})?[V]([0-9]+)$~',$metar_toDecode[1],$pieces) === 1) { $lastMatchUSA = $usaf2013[15]; if($show_diagnosticsR) print_array($pieces, 'Variable ceiling height pieces'); $output = 'Variable ceiling height: lowest=' . (100 * $pieces[1]); if(strlen($pieces[2]) > 0) $output .= ' (relates to cloud layer in main part of decode that was reported as at ' . 100 * $pieces[2] . ' feet)'; $output .= ', highest=' . (100 * $pieces[3]) . ' ft a.g.l.'; do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[17]); goto end_while; } } // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. u) (previously t.) Ceiling Height at Second Location (CIG_hhh_[LOC]) // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 19 if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'CIG' and is_numeric($metar_toDecode[1]) and !is_numeric($metar_toDecode[2])) { $lastMatchUSA = $usaf2013[19]; $altitudeFT = 100 * $metar_toDecode[1]; $output= number_format($altitudeFT) . ' feet a.g.l. (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $iCount = 2; $locationArray = array_slice($metar_toDecode, $iCount); $return2 = searchLocation($locationArray, $lastMatchUSA); $jCount = $iCount + $return2[1]; $output .= $return2[0]; do_output($decodeGroupCount['REMARK'], $jCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[21]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'CIG' and $metar_toDecode[1] == 'HYR' || $metar_toDecode[1] == 'LWR') { // 2018/01/29 21:54 METAR KBUF 292154Z 05015G21KT 10SM BKN011 BKN070 OVC230 M02/M04 A3011 RMK AO2 SLP205 CIG HYR SE-SW T10221044 $output = 'Variable ceiling height: '; $output .= $metar_toDecode[1] == 'HYR' ? 'higher ' : 'lower '; if(in_array($metar_toDecode[2], $compass)) { $output .= 'towards ' . $compass_expanded[$metar_toDecode[2]]; }elseif(strpos($metar_toDecode[2], '-') !== false) { $before = substr($metar_toDecode[2], 0, strpos($metar_toDecode[2], '-')); if(in_array($before, $compass)) $output .= 'towards ' . $compass_expanded[$before] . ' through '; $after = substr($metar_toDecode[$iCount], 1 + strlen($before)); if(strpos($after, '-') === false) { if(in_array($after, $compass)) $output .= $compass_expanded[$after]; }else{ $before = substr($after, 0, strpos($after, '-')); if(in_array($before, $compass)) $output .= $compass_expanded[$before] . ' to '; $after = substr($after, 1 + strlen($before)); if(in_array($after, $compass)) $output .= $compass_expanded[$after]; } } $lastMatchUSA = $usa_extras[5]; $decodeGroupCount['USA_extras']++; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' extra specification=' . $lastMatchUSA . ' '); goto end_while; } break; case 'CO': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found CONDENSATION at ' . substr($metar_toDecode[0], 0, 2); // USA Air Force 'USAF Manual 15-111: Remark Number 24: (5) Condensation Trails', /* (5) Condensation Trails. Include CONTRAILS to indicate condensation trails are observed. Although snippet added below, earlier call to Canada single segment specifications will pick up condensation trails first */ if(strlen($metar_toDecode[0]) > 6 and substr($metar_toDecode[0], 0, 4) == 'CONT') // NB using substring for just partial match as have seen at least 5 different spellings being used as in examples below { $lastMatchUSA = $usaf2013[24][5]; if($show_diagnostics or $show_diagnosticsR) { echo '
USA remarks begins examining ' . $metar_toDecode[0] . ' reporting Condensation Trails
'; } if(array_key_exists('SKY-DETAILS', $decodeInfo) and array_key_exists(0, $decodeInfo['SKY-DETAILS'])) { if(array_key_exists(2, $decodeInfo['SKY-DETAILS'])) $decodeInfo['SKY-DETAILS'][2][0] .= ' (r55)'; elseif(array_key_exists(1, $decodeInfo['SKY-DETAILS'])) $decodeInfo['SKY-DETAILS'][1][0] .= ' (r55)'; else $decodeInfo['SKY-DETAILS'][0][0] .= ' (r55)'; $output = '(r55) = Highest reported cloud layer either completely, or partly, consists of persisting aircraft condensation trail(s)'; # print_array($decodeInfo['SKY-DETAILS'], 'sky-details'); }else $output = 'Observed cloud layer either completely, or partly, consists of persisting aircraft condensation trail(s)'; do_output($decodeGroupCount['REMARK'], 1, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> (5) Condensation Trails'); $decodeGroupCount['USAF']++; break; }elseif($metar_toDecode[0] == 'COR' /* single R seen in example */ or $metar_toDecode[0] == 'CORR' /* double R as specified in USAF document */) { // METAR KSTV 011058Z COR 02010G17KT 1400 R36/4000 HZ SCT007 BKN020 OVC070 20/17 A3019 RMK AO2A SLP015 ALSTG/SLP ESTMD COR 1104; // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] remark number 36 $lastMatchUSA = $usaf2013[36]; $output = 'Corrected at ' . $metar_toDecode[1] . ' UTC'; $decodeGroupCount['USAF']++; do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' extra specification=' . $lastMatchUSA . ' '); goto end_while; }elseif($metar_toDecode[0] == 'CONS') { goto lightning; } break; case 'FI': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found FIRST at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. aa)Other Signifcant Information [Plain Language]. // Agencies may add to a report other information significant to their operations, such as information on fog dispersal operations, runway conditions, “FIRST” or “LAST” report from station, etc. 12.7.2 Additive and Automated Maintenance Data. Additive data groups are only reported at des // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] remark number 36 if($metar_toDecode[0] == 'FIRST') { $output = 'This is the first observation of the day.   This is used in limited operation mode or where purely manual observations.'; $lastMatchUSA = $fmh1_2017[36]; do_output($decodeGroupCount['REMARK'], 1, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[27]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'FR': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found FREQUENT at ' . substr($metar_toDecode[0], 0, 2); if ($metar_toDecode[0] == 'FRQ') { goto lightning; } break; case 'GR': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found HAILSTONE at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. n) Hailstone Size (GR_[size]) [Plain Language] // Changed in handbook from November 2017: 'GR 1 3/4' means size of hailstones is 1 and three quarter inches. // For small hail (see paragraph 8.3.1.g) code as less than one quarter inch (i.e. GR LESS THAN 1/4 ). // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 14 Hailstone Size // Encode hailstone size ≥ local warning criteria in format, GR_[size]_[Plain Language], where GR is the remark // identifier and [size] is the diameter of the largest hailstone. The hailstone size is encoded in ¼-inch increments. // For example, 'GR 1 3/4' would indicate that the largest hailstones were 1¾ inches in diameter. // If GS is encoded in the body of the report, no hailstone size remark is required. $lastMatchUSA = $usaf2013[14]; if(count($metar_toDecode) > 3 and $metar_toDecode[1] == 'LESS' and $metar_toDecode[2] == 'THAN' and $metar_toDecode[3] == '1/4') { $output = 'Hailstone size less than ¼ inch (6.3 mm)'; do_output($decodeGroupCount['REMARK'], 4, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[14]); break; } $output = 'Hailstone size'; $iCount = 1; // A massive hailstone that fell in America - This hailstone measured 20.5cm wide (8 inches), weighed in at almost 1kg and is the biggest hailstone ever recorded! if(is_numeric($metar_toDecode[1]) ) { $output .= ' ' . $metar_toDecode[1] . ' inches (' . number_format(2.54 * $metar_toDecode[1], -1) . ' mm)'; $iCount++; } if($metar_toDecode[$iCount] == '1/4') { $output .= '¼ inch (6.3 mm)'; $iCount++; }elseif($metar_toDecode[$iCount] == '1/2') { $output .= '½ inch (13 mm)'; $iCount++; }elseif($metar_toDecode[$iCount] == '3/4') { $output .= '¾ inch (19 mm)'; $iCount++; } do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[14]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; case 'GS': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found SNOW at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - '12.7.1 o) Snow pellets (GS_[intensity])', # new // (N.B. Up to Oct 2017, stated 'If GS is coded in the body of the report, no hailstone size remark is required.') # From here on renumbered, and paragraph references changed in this script at its version 0.1.4 (27 September 2017), # ready for November 2017 changes in USA handbook FRM 1 (replacing September 2005 edition) with hailstone/snow separation // Not in Surface Weather Observations: Air Force Manual 15-111 $lastMatchUSA = $fmh1_2017[14]; switch($metar_toDecode[1]) { /* Advance copy Federal Meteorological Handbook No.1 Effective November 30, 2017: 12.6.8.a1 Due to ASOS/AWOS-C software limitations, intensity for GS shall be manually encoded into the RMK section as GS LGT , GS MOD , or GS HVY . Reporting of the intensity of GS will revert to the conventional ('+', '', or '-') method for precipitation intensity when ASOS upgrades are implemented (projected for October 2020). 12.7.1 o) The snow pellet intensity shall be coded as LGT, MOD, or HVY indicating light, moderate, or heavy intensity, respectively (see paragraph 12.6.8.a1). */ case 'LGT': $output = 'Light intensity precipitation made up of snow pellets [Precipitation of white, opaque grains of ice. '; break; case 'MOD': $output = 'Moderate intensity precipitation made up of snow pellets [Precipitation of white, opaque grains of ice. '; break; case 'HVY': $output = 'Heavy intensity precipitation made up of snow pellets [Precipitation of white, opaque grains of ice. '; break; } $output .= '  The grains are round or sometimes conical.   Diameters range from about 0.08 to 0.2 inch (2 to 5 mm).]'; do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' Federal Meteorological Handbook No.1=' . $lastMatchUSA . ''); $decodeGroupCount['FMH1']++; goto end_while; case 'LA': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found LAST at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. aa)Other Signifcant Information [Plain Language]. // Agencies may add to a report other information significant to their operations, such as information on fog dispersal operations, runway conditions, “FIRST” or “LAST” report from station, etc. 12.7.2 Additive and Automated Maintenance Data. Additive data groups are only reported at des // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] remark number 36 // Port Alexander Seaplane Base (FAA: AHP)|Port Alexander, Alaska=====2015/11/17 19:58 PAAP 171958Z VRB02KT 10SM SCT035 03/M00 A2950 RMK LAST NO SPECI // example 'LAST' - warning that the station is now closing and this METAR was the last to be issued until the station reopens. if($metar_toDecode[0] == 'LAST') { $output = 'This is the last observation of the day.   This is used in limited operation mode or where purely manual observations.'; $lastMatchUSA = $fmh1_2017[36]; do_output($decodeGroupCount['REMARK'], 1, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[27]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'LT': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found LIGHTNING at ' . substr($metar_toDecode[0], 0, 2); if ($metar_toDecode[0] == 'LTg') { goto lightning; } break; case 'NO': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found NO at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. y) (previously x.) No SPECI Reports Taken (NOSPECI) // Port Alexander Seaplane Base (FAA: AHP)|Port Alexander, Alaska=====2015/11/17 19:58 PAAP 171958Z VRB02KT 10SM SCT035 03/M00 A2950 RMK LAST NO SPECI // Need to check again if space between NO and SPECI is an error. Also note sequencing disparity. if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'NOSPECI' || ($metar_toDecode[0] == 'NO' && $metar_toDecode[1] == 'SPECI')) { $output = 'No changes in weather conditions will be reported until the next METAR (as no reports of type SPECI are issued from here)'; $lastMatchUSA = $fmh1_2017[25]; if($metar_toDecode[0] == 'NOSPECI') do_output($decodeGroupCount['REMARK'], 1, $output, 'pass=' . $passCountUSA . ' Federal Meteorological Handbook No.1=' . $lastMatchUSA . ''); else do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' Federal Meteorological Handbook No.1=' . $lastMatchUSA . ''); $decodeGroupCount['FMH1']++; goto end_while; } break; case 'OC': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found OCAISIONAL at ' . substr($metar_toDecode[0], 0, 2); if ($metar_toDecode[0] == 'OCNL') { goto lightning; } break; case 'PA': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found PAST at ' . substr($metar_toDecode[0], 0, 2); // USA Plain Language, PAST HR not in specification // Examples: 2017/11/16 20:00 METAR CWGD 162000Z AUTO 34019G28KT 03/01 RMK AO1 4PAST HR 1033 PK WND 34030/1909 SLP162 P0002 T00270013 50007 // 2017/11/15 23:00 METAR CXDI 152300Z AUTO 18007KT 06/04 RMK AO1 8PAST HR 6029 SLP087 P0007 T00590040 50018 // 2017/11/21 23:00 METAR CWGD 212300Z AUTO 27015G23KT 04/02 RMK AO1 0PAST HR SOG 00 M PK WND 26023/2251 SLP099 P0004 T00410023 if(count($metar_toDecode) > 1 and substr($metar_toDecode[0], -4) == 'PAST' and $metar_toDecode[1] == 'HR') { $processed = '(' . $decodeGroupCount['REMARK'] . ') → RMK_' . $metar_toDecode[0] . '_' . $metar_toDecode[1]; if(strlen(substr($metar_toDecode[0], 0, -4)) < 2) $minute = '0' . substr($metar_toDecode[0], 0, -4); else $minute = substr($metar_toDecode[0], 0, -4); $localTime = adjustTime('99' . $minute); $output = 'r4 Barometer reading delayed until ' . $localTime['minute'] . ' minutes past hour (local time)'; $iCount = 2; $lastMatchUSA = $usa_extras[1]; do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' Undocumented specification=' . $lastMatchUSA . ''); foreach($remarks_toDecode as $key => $value) { if(substr($key,0,3) == 'SLP') walk_SLP($key, 'pass=' . $passCountUSA . ' ' . $lastMatchUSA . '>> ' . $fmh1_2017[26]); } $decodeInfo['SLP'] .= ' r4'; goto end_while; } break; case 'PC': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found PRECIPITATION at ' . substr($metar_toDecode[0], 0, 2); // 'PCPN VRY LGT' // 2018/01/13 17:54 KBUF 131754Z 33010KT 9SM SN BKN023 M11/M16 A3027 RMK AO2 SNB06 SLP265 PCPN VRY LGT 4/006 933007 P0000 60000 T11111156 11106 21117 51015 $ if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'PCPN' and $metar_toDecode[1] == 'VRY' and $metar_toDecode[2] == 'LGT') { $lastMatchUSA = $usa_extras[6]; optionalDiagnosticOutput('decode_USA_remarks() specification iterator=' . $lastMatchUSA . ' processing very light precipitation', $file); // Diagnostic on condition match $output = "Very light precipitation observed"; $decodeGroupCount['USA_extras']++; $decodeInfo['BY'] = 'Automated observations with Observer augmentation'; $decodeGroupCount['BY']++; if(strpos($decodeGroupProcessed['BY'], 'RMK') !== false) $decodeGroupProcessed['BY'] = substr($decodeGroupProcessed['BY'], 0, -1) . '_' . $metar_toDecode[0] . '}'; else $decodeGroupProcessed['BY'] .= '_{RMK_' . $metar_toDecode[0] . '}'; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' ' . $lastMatchUSA . '>> ' . '(light precipitation)'); goto end_while; } break; case 'PK': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found PEAK at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. d. Peak Wind // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 4 # Seems to appear out of the sequence described in both FMH1 and air force manual far too often if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'PK' and $metar_toDecode[1] == 'WND' and preg_match('/([0-3][0-9][0])([0-9]{2,3})([\/])([0,1,2][0-9])?([0-5][0-9])$/', $metar_toDecode[2], $pieces) === 1) { // Examples: from Canadian sites reporting using USA format: // 2017/11/16 20:00 METAR CWGD 162000Z AUTO 34019G28KT 03/01 RMK AO1 4PAST HR 1033 PK WND 34030/1909 SLP162 P0002 T00270013 50018 // 2017/11/21 23:00 METAR CXDI 212300Z AUTO 23009G17KT 09/02 RMK AO1 PK WND 21018/2202 SLP094 T00890015 51020 // 2017/11/21 23:00 METAR CWGD 212300Z AUTO 27015G23KT 04/02 RMK AO1 0PAST HR SOG 00 M PK WND 26023/2251 SLP099 P0004 T00410023 $lastMatchUSA = $usaf2013[4]; $input = novel_decode_wind($metar_toDecode[2], $lastMatchUSA); $output ='The maximum instantaneous wind speed since the last METAR was ' . $input['knots'] . ' knots at a bearing of ' . $input['bearing'] . ' (from ' . $input['direction'] . ') at ' . $input['time']; // The following is in specification, but frequently speeds below 20 knots reported, so it has now been commented out # $output .= '  [The maximum instantaneous wind speed since the last METAR that exceeded 25 knots].'; $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; if($show_diagnosticsR) echo '
Processing the match to ' . $lastMatchUSA; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[4]); goto end_while; } break; case 'FU': case 'TO': case 'WA': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found ACTIVITY at ' . substr($metar_toDecode[0], 0, 2); // Federal Meteorological Handbook No.1 - 12.7.1 Automated, Manual, and Plain Language Remarks. b. Funnel Cloud and Tornado activity // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 2 // Tornadic activity shall be coded as the first remark after the 'RMK' entry. // Format: Tornadic activity_B/E(hh)mm_LOC/DIR_(MOV) where '_' denotes a space between the segments // Tornadic activity is one of the items in the following array: $tornadic_activity = array( 'TORNADO', 'FUNNEL', // ' CLOUD' in next segment 'WATERSPOUT' ); if(count($metar_toDecode) > 2 and in_array($metar_toDecode[0], $tornadic_activity)) { $lastMatchUSA = $usaf2013[2]; $iCount = 0; if($metar_toDecode[0] == 'FUNNEL') $iCount++; $partLocal = $metar_toDecode[$iCount + 1]; $prefixLocal = $processedLocal = $outputLocal = $restLocal = ''; $activity = $metar_toDecode[0]; if($metar_toDecode[0] == 'FUNNEL') $activity .= '-' . $metar_toDecode[1]; if(begin_end($partLocal, $activity, $lastMatchUSA)) { $locationArray = array_slice($metar_toDecode, $iCount); $return = searchLocation($locationArray, $lastMatchUSA); $location = $return[0]; $decodeGroupCount['REMARK'] --; $output = $outputLocal . ' reported ' . $location; do_output($decodeGroupCount['REMARK'], $return[1] , $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[2]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } } break; case 'TW': case 'SF': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found TOWER at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. f. Tower or Surface Visibility (TWR_VIS_vv_vvv or SFC_VIS_vv_vvv) // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 6 - Tower Visibility // For example, the control tower visibility of 1 1/2 statute miles would be coded TWR VIS 1 1/2 . if(count($metar_toDecode) > 3 and ($metar_toDecode[0] == 'SFC' or $metar_toDecode[0] == 'TWR') and $metar_toDecode[1] == 'VIS') { // 2018/01/29 17:35 METAR KDTW 291735Z 36011KT 1 1/4SM R03R/3500VP6000FT -SN BR FEW009 OVC015 M02/M04 A3023 RMK AO2 TWR VIS 1 1/2 P0002 T10221039 // 2018/02/05 00:53 METAR KDTW 050053Z 34011G17KT 1SM R03R/P6000FT -SN OVC015 M08/M10 A2997 RMK AO2 SFC VIS 1 1/2 SLP158 P0001 T10781100 $ $lastMatchUSA = $usaf2013[5]; $location = $metar_toDecode[0] == 'SFC' ? 'standard (surface)' : 'control tower'; $return = novel_allVisibility($metar_toDecode[0], $metar_toDecode[2], $metar_toDecode[3], $lastMatchUSA); if($show_diagnostics) print_array($return,'visibility in miles'); do_output($decodeGroupCount['REMARK'], $return['segments'], $return['mdv'], 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[6]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'SN': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found SNOW at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. z) (previously y.) Snow Increasing Rapidly (SNINCR_[inches-hour/inches on ground]) e.g. 'SNINCR 2/10' // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 23 if(array_key_exists(1, $metar_toDecode) and $metar_toDecode[0] == 'SNINCR' and strpos($metar_toDecode[1], '/') !== false) { $lastMatchUSA = $usaf2013[23]; $output = 'Snow Increasing Rapidly at rate of ' . substr($metar_toDecode[1], 0, strpos($metar_toDecode[1], '/')) . 'inches per hour; current depth is ' . substr($metar_toDecode[1], strpos($metar_toDecode[1], '/') + 1); do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[25]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; case 'SO': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found SNOW at ' . substr($metar_toDecode[0], 0, 2); // USA Observer reported: Snow on Ground - Snow depth measured on ground by Observer // Plain Language, SOG not in specification, but is in use in live METAR // EXTRA - Not part of USA specifications, but decoded here as sometimes seen in this part of the sequence // NOTE - Handbook 1: officially USA snow on ground is a (4/sss) format - see case 34 for automatic sensor case. if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'SOG' and is_numeric($metar_toDecode[1])) // I was originally only guessing at the meaning of SOG as snow on ground { // but other decoders recognise it and translate it as snow depth in cm if(array_key_exists(2, $metar_toDecode) and $metar_toDecode[2] == 'M') $output = 'Snow on ground to depth of ' . $metar_toDecode[1] . ' m by manual observation'; else $output = 'Snow on ground to depth of ' . 1 * $metar_toDecode[1] . ' cm by manual observation'; // units corrected from my guess of inches to cm reported by other decoders // 'Metar Reader 3.1' from http://www.flightutilities.com/MR.aspx output is 'unknown token' // http://metaf2xml.sourceforge.net/cgi-bin/metaf.pl?lang=en&format=html&mode=latest&hours=24&type_metaf=metar&msg_metaf=METAR+CWGD+110800Z+AUTO+33013KT+M04%2FM10+RMK+AO1+SOG+03+1015+PK+WND+34020%2F0709+SLP151+T10391095&.submit=decode // decodes as SOG 03 snow depth: 3 cm 1.18 in. $lastMatchUSA = $usa_extras[2]; $decodeGroupCount['USA_extras']++; $decodeInfo['BY'] = 'Automated observations with Observer augmentation'; $decodeGroupCount['BY']++; if(strpos($decodeGroupProcessed['BY'], 'RMK') !== false) $decodeGroupProcessed['BY'] = substr($decodeGroupProcessed['BY'], 0, -1) . '_' . $metar_toDecode[0] . '}'; else $decodeGroupProcessed['BY'] .= '_{RMK_' . $metar_toDecode[0] . '}'; do_output($decodeGroupCount['REMARK'], 2 + (array_key_exists(2, $metar_toDecode) and $metar_toDecode[2] == 'M'), $output, 'pass=' . $passCountUSA . ' ' . $lastMatchUSA . '>> ' . '(snow lying)'); goto end_while; } break; case 'TS': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found THUNDERSTORM at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. m. Thunderstorm Location (TS_LOC_(MOV_DIR)) // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 13 if(array_key_exists(1, $metar_toDecode) and !is_numeric($metar_toDecode[1]) ) { $lastMatchUSA = $usaf2013[13]; $iCount = 1; $locationArray = array_slice($metar_toDecode, $iCount); $return2 = searchLocation($locationArray, $lastMatchUSA); $location = $return2[0]; $jCount = $iCount + $return2[1]; $output = 'Thunderstorm seen ' . $metar_toDecode[1] . '   [A cumulonimbus cloud that is accompanied by lightning and thunder, or for automated systems, a storm detected by lightning detection systems]'; $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[13]); goto end_while; } break; case 'VI': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found VIRGA or VISIBILITY at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. p) (previously o.) Virga (manual operator reporting of wisps of precipitation not reaching the ground) // p) Virga (VIRGA_(DIR)) [Plain Language]. Virga shall be coded in the format, VIRGA_(DIR), where VIRGA is the remark identifier and DIR is the direction from the station. // The direction of the phenomena from the station is optional, e.g., “VIRGA” or “VIRGA SW”. if($metar_toDecode[0] == 'VIRGA') { if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' confirmed VIRGA at ' . substr($metar_toDecode[0], 0, 2); $lastMatchUSA = $fmh1_2017[16]; $iCount = 1; $output = 'Observed precipitation, but it is not reaching ground level.'; if(array_key_exists(1, $metar_toDecode) and in_array($metar_toDecode[1], $compass)) { $output .= '   Seen looking ' . $compass_expanded[$metar_toDecode[1]]; $iCount++; } do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' Federal Meteorological Handbook No.1=' . $lastMatchUSA . ''); $decodeGroupCount['FMH1']++; break; }elseif($metar_toDecode[0] == 'VIS' and array_key_exists(1, $metar_toDecode)) { if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' confirmed VISIBILITY at ' . substr($metar_toDecode[0], 0, 2); /* // Not in USAF guide nor FAA Manual - Visibility lower or higher and compass direction // Buffalo/Cheektow, New York, USA (station id=KBUF) 2018/01/18 20:54 KBUF 182054Z 24022G28KT 6SM SN BLSN BKN016 OVC022 M06/M09 A3001 RMK AO2 PK WND 24030/2008 SLP175 VIS LWR WNNE VIS HYR SW P0000 60000 T10611089 58008 Went wrong at (2) → RMK   Not de-coded=VIS - 'visibility', changed PHP above so recognises this first, before looking at rest Went wrong at (2) → RMK   Not de-coded=LWR - now recognised by switch/case below Went wrong at (2) → RMK   Not de-coded=WNNE # NB 4 character compass direction ? Can't be right, could be NNE if 16 compass directions permitted here */ switch($metar_toDecode[1]) { case 'LWR': case 'HYR': $lastMatchUSA = $usa_extras[4]; $decodeGroupCount['USA_extras']++; $output = 'Visibility is '; $output .= $metar_toDecode[1] == 'LWR'? 'lower ' : 'higher '; $output .= 'than prevailing visibility '; if(in_array($metar_toDecode[2], $compass)) { $output .= 'towards ' . $compass_expanded[$metar_toDecode[2]]; }elseif(strpos($metar_toDecode[2], '-') !== false) { // 2018/02/02 13:43 METAR KBUF 021343Z 30013KT 3SM R23/6000VP6000FT -SN FEW012 BKN020 BKN035 M11/M14 A3020 RMK AO2 VIS HYR S-W-NW DRSN P0000 T11111144 $before = substr($metar_toDecode[2], 0, strpos($metar_toDecode[2], '-')); if(in_array($before, $compass)) $output .= 'towards ' . $compass_expanded[$before] . ' through '; $after = substr($metar_toDecode[$iCount], 1 + strlen($before)); if(strpos($after, '-') === false) { if(in_array($after, $compass)) $output .= $compass_expanded[$after]; }else{ $before = substr($after, 0, strpos($after, '-')); if(in_array($before, $compass)) $output .= $compass_expanded[$before] . ' to '; $after = substr($after, 1 + strlen($before)); if(in_array($after, $compass)) $output .= $compass_expanded[$after]; } } do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USA extra specification=' . $lastMatchUSA . ' '); goto end_while; } /* Federal Meteorological Handbook No.1 - November 2017 edition (FCM-H1-2017): Surface Weather Observations and Reports, paragraphs 6.3 Visibility Parameters The visibility parameters are: a. Prevailing visibility. The visibility that is considered representative of visibility conditions at the station; the greatest distance that can be seen throughout at least half the horizon circle, not necessarily continuous. b. Sector visibility. The visibility in a specified direction that represents at least a 45 degree arc of the horizon circle. c. Surface visibility. The prevailing visibility determined from the usual point of observation. d. Tower visibility. The prevailing visibility determined from the airport traffic control tower (ATCT) at stations that also report surface visibility. 6.4 Visibility Observing Standards. Visibility may be manually determined at either the surface, the tower level, or both. If visibility observations are made from just one level, e.g., the airport traffic control tower, that level shall be considered the 'usual point of observation' and that visibility shall be reported as surface visibility. If visibility observations are made from both levels, the visibility at the tower level may be reported as tower visibility. */ // Variable Prevailing Visibility (VIS_vvvvvVvvvv) ####################################################################################################################################################### # Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 7, Variable Prevailing Visibility # # Encode variable prevailing visibility (visibility < 3 statute miles (4800 meters) increases/decreases by ½ statute miles(800 meters) during # # observation) in format VIS_VIS_vvvvvVvvvv # ####################################################################################################################################################### # Federal Meteorological Handbook No.1 - November 2017 edition (FCM-H1-2017): Surface Weather Observations and Reports, # # paragraph 6.5.3 Variable Prevailing Visibility. Variable prevailing visibility shall be reported if the prevailing visibility is less than # # 3 miles and rapidly increases or decreases by 1/2 statute mile or more during the time of observation. The minimum and maximum visibility values # # observed shall be reported in remarks section (see paragraph 12.7.1.g). # # 12.7.1 Automated, Manual, and Plain Language Remarks. g. Variable Prevailing Visibility (VIS_vvvvvVvvvv) # ####################################################################################################################################################### # Both of the above two sources quote the same text: 'Where VIS is the remark identifier, first 'vvvvv' is the lowest visibility evaluated, # # V denotes variability between two values, and last 'vvvvv' is the highest visibility evaluated. # # There is one space following the remark identifier; no spaces between the letter V and the lowest/highest values. # ####################################################################################################################################################### // 2018/03/13 21:53 KMNN 132153Z AUTO 02007KT 2SM -SN BKN020 BKN029 OVC038 M01/M04 A2997 RMK AO2 VIS 1 1/2V3 SNB29 SLP163 P0000 T10111044 if(strpos($metar_toDecode[1],'V') !== false or (array_key_exists(2, $metar_toDecode) && strpos($metar_toDecode[2],'V') !== false)) { if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' confirmed VARIABLE at ' . $metar_toDecode[1] . ' or ' . $metar_toDecode[2]; $lastMatchUSA = $usaf2013[7]; $return = novel_allVisibility($metar_toDecode[0], $metar_toDecode[1], $metar_toDecode[2], $usaf2013[7]); $output = 'The prevailing surface visibility has varied between ' . $return['min'] . ' and ' . $return['max']; if($show_diagnosticsR) print_array($return, 'Return for USA Variable Prevailing Visibility'); do_output($decodeGroupCount['REMARK'], $return['segments'], $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[7]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; }else{ if($show_diagnosticsR) echo 'Identified as either representative prevailing visibility or Visibility At Second Location'; // Sector Visibility (VIS_[DIR]_vvvvv) /* 6.4.6 Sector Visibility. When the manually-derived visibility is not uniform in all directions, the horizon circle shall be divided into arcs that have uniform visibility and represent at least one eighth of the horizon circle (45 degrees). The visibility that is evaluated in each sector is sector visibility. ... 6.5.7 Sector Visibility. Sector visibility shall be reported in remarks when it differs from the prevailing visibility by one or more reportable values and either the prevailing or sector visibility is less than 3 miles (see paragraph 12.7.1.h). */ ####################################################################################################################################################### # Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 8, Sector Visibility # # Encode sector visibility (visibility < 3 statute miles (4800 meters) increases/decreases by ½ statute miles(800 meters) during # # observation) in format VIS_[DIR]_vvvvv, where VIS is the remark identifier, DIR is the optional compass direction, and vvvv is the visibility. # # There is one space following the remark identifier; another space after the optional compass direction; # # and the visibility value can consist of a fraction only, an integer only, or an integer then a space and then a fraction. # ####################################################################################################################################################### # Federal Meteorological Handbook No.1 - November 2017 edition (FCM-H1-2017): Surface Weather Observations and Reports, # # paragraph 6.5.7 Sector Visibility. Sector visibility shall be reported in remarks when it differs from the prevailing visibility by one or # # more reportable values and either the prevailing or sector visibility is less than 3 miles (see paragraph 12.7.1.h). # # h. Sector Visibility (VIS_[DIR]_vvvvv) [Plain Language]. The sector visibility shall be coded in the format, VIS_[DIR]_vvvvv, where VIS # # is the remark identifier, [DIR] defines the sector to 8 points of the compass, and vvvvv is the sector visibility in statute miles, # # using the appropriate set of values in Table 12-1 (see paragraphs 6.4.6 and 6.5.7). # ####################################################################################################################################################### # For example, a visibility of 2 1/2 statute miles in the north-eastern octant would be coded 'VIS NE 2 1/2', # # and 'VIS 1 1/2', 'VIS SW 1', or 'VIS 2' are also allowed, but a compass direction like 'SSW' or a distance like '1/8' would not be allowed. # ####################################################################################################################################################### // 2018/03/13 19:54 KBUF 131954Z 23010KT 7SM -SN FEW027 BKN034 BKN050 BKN270 M01/M04 A2981 RMK AO2 SNB30 SLP104 VIS SE-SW-W 1 3/4 P0000 T10061044 $lastMatchUSA = $usaf2013[8]; $iCount = 1; $output = 'Visibility observed '; if(is_numeric(substr($metar_toDecode[1], 0, 1))) // only check first digit to allow for integer or fraction { ################################################################################################################################################## # The segments after 'VIS', i.e. the 'vvvv' are passed from this case into function novel_allVisibility() for decoding. As the visibility is # # expressed in statute miles with fractions, there could be one segment after 'VIS' that is either a whole number or a fraction. # # Alternatively, there can be two segments, with a space between whole and fractional parts. # # For example, a visibility of 2 1/2 statute miles in the north-eastern octant would be coded VIS NE 2 1/2 # ################################################################################################################################################## if(array_key_exists(1 + $iCount, $metar_toDecode)) $reply = novel_allVisibility('Sector', $metar_toDecode[$iCount], $metar_toDecode[1 + $iCount], $lastMatchUSA); else $reply = novel_allVisibility('Sector', $metar_toDecode[$iCount], '', $lastMatchUSA); if($show_diagnosticsR) print_array($reply, 'distance found'); $iCount += $reply['segments']; if(array_key_exists($iCount, $metar_toDecode)) { // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. i. Visibility At Second Location // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 9 /* i. Visibility At Second Location (VIS_vvvvv_[LOC]). At designated automated stations, the visibility at a second location shall be coded in the format VIS_vvvvv_[LOC], where VIS is the remark identifier, vvvvv is the measured visibility value, and [LOC] is the specific location of the visibility sensor(s) at the station (see paragraph 6.5.6). This remark shall only be generated when the condition is lower than that contained in the body of the report. For example, a visibility of 2 1/2 statute miles measured by a second sensor located at runway 11 would be coded 'VIS 2 1/2 RWY11'. */ // 2018/02/19 21:20 METAR KDTW 192120Z 19008KT 3SM -RA BR FEW002 BKN015 OVC025 08/08 A2990 RMK AO2 SFC VIS 4 VIS 1/4 3L P0001 T00830083 $ if($show_diagnosticsR) echo '
Examine ' . $metar_toDecode[$iCount] . ' for location'; $locationArray = array_slice($metar_toDecode, $iCount -1); $return2 = searchLocation($locationArray, $fmh1_2017[9]); if($return2[1]) { if($show_diagnosticsR) print_array($return2,'second location - 2'); $location = $return2[0]; $iCount += $return2[1]; $output = 'Visibility observed ' . $location . ' is ' . $reply['value']; $lastMatchUSA = $usaf2013[9]; $specification = $fmh1_2017[9]; do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $specification); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } } do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[8]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; }elseif(is_numeric(substr($metar_toDecode[2], 0, 1))) { if($show_diagnosticsR) echo '
Identified as sector visibility specifying directions'; ######################################################################################################################################## # This is when the optional 'DIR' segment follows after 'VIS', before the 'vvvv' segments. # # The 'DIR' may contain one or more compass directions, (e.g. 'VIS SE-SW-W 1 3/4') so use PREG_SPLIT(). # # The one or two 'vvvv' segments are passed from this case into function novel_allVisibility() for decoding . As the visibility is # # expressed in statute miles with fractions, there could be one segment after 'VIS' that is either a whole number or a fraction. # # Alternatively, there can be two segments, with a space between whole and fractional parts. # # For example, a visibility of 2 1/2 statute miles in the north-eastern octant would be coded VIS NE 2 1/2 # ######################################################################################################################################## if($show_diagnosticsR) echo '
looking for hyphens in ' . $metar_toDecode[$iCount] . ' that separate compass directions'; $directions = preg_split('{[-]}', $metar_toDecode[$iCount]); # added 12 Mar 2018 if($show_diagnosticsR) print_array($directions, 'directions found'); $iCount++; } $reply = novel_allVisibility('Sector', $metar_toDecode[$iCount], $metar_toDecode[1 + $iCount], $lastMatchUSA); if($show_diagnosticsR) print_array($reply, 'distance found'); $output .= ' is ' . $reply['value']; $iCount += $reply['segments']; if(isset($directions)) { $output .= ' looking '; $iCount++; foreach($directions as $part) { $output .= $compass_expanded[$part] . '; '; } } do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[8]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } } break; case 'WN': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found WIND at ' . substr($metar_toDecode[0], 0, 2); // USA Air Force 'USAF Manual 15-111: Remark Number 24: (2) Estimated Wind and Pressure', /* (2) Estimated Wind and Pressure. WND DATA ESTMD or ALSTG/SLP ESTMD indicates the winds and/or pressure values from the primary airfield sensors are suspect or inoperative, and back-up equipment is being used. Report winds as estimated if using the AN/TMQ-53 as a back-up at permanent (non-tactical) locations. */ // 'Estimated wind WND DATA' if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'WND' and ($metar_toDecode[1] == 'DATA' or $metar_toDecode[2] == 'ESTMD')) { $lastMatchUSA = $usaf2013[24][2]; $output = 'The wind reports in this METAR are based upon estimated observations'; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ''); $decodeGroupCount['USAF']++; goto end_while; } break; case 'WS': if($show_diagnosticsR) echo '
USA pass ' .$passCountUSA . ' found WIND at ' . substr($metar_toDecode[0], 0, 2); // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. e. Wind Shift // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 5 if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'WSHFT' and (strlen($metar_toDecode[1]) == 2 or strlen($metar_toDecode[1]) == 4)) { $lastMatchUSA = $usaf2013[5]; if($metar_toDecode[1] == 'AT') { $done = array_shift($metar_toDecode); // take out processed segment $remarks_toDecode[$done] = $lastMatchUSA; } if(strlen($metar_toDecode[1]) == 2 and is_numeric($metar_toDecode[1])) { $timeArray = adjustTime('99' . $metar_toDecode[1]); $output = 'Wind Shift started at ' . $timeArray['minute'] . ' minutes past hour (local time)'; }elseif(strlen($metar_toDecode[1]) == 4) { $timeArray = adjustTime($metar_toDecode[1]); $output = 'Wind Shift started at' . $timeArray['hour'] . ':' . $timeArray['minute'] . ' (local time)'; }else $output = 'Wind Shift reported (malformed content prevents determination of time)'; if($metar_toDecode[2] == 'FROPA') $output .= ' as a result of a front passing; '; $output .= '   [A change in the wind direction of 45 degrees or more in less than 15 minutes with sustained wind speeds' . ' of 10 knots or more throughout the wind shift].'; do_output($decodeGroupCount['REMARK'], 2, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[5]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } break; default: /* [U.S. Air Force] Manual 15-111: Surface Weather Observations: Remark Number 24 'Added to report information significant to aircraft safety or resource protection. Amplifies entries in main observation. Some remarks will use the same order of entry as data the remark most closely relates (e.g., a VIS LWR E remark would have the entry as a sector visibility Remark.' Not sure if that quote means the above example should be at case 11 as an alternative to other sector visibility (as originally assumed) or should be placed here, but where other remarks added here they appear in same order as earlier e.g. any to do with wind would precede this one and anything to do with precipitation would follow and then pressure after that? */ // USA Air Force 'USAF Manual 15-111: Remark Number 24: (3) Significant Atmospheric Phenomena not Reported Elsewhere', /* 3) Significant Atmospheric Phenomena not Reported Elsewhere. Present weather observed but not occurring at the point of observation or in the vicinity (e.g., SHRA OVR MTNS N showers of rain over mountains to North). */ $return1 = novel_decode_phenomena($metar_toDecode[0], 'Rem_USA_53'); if(!array_key_exists(0, $return1) or is_null($return1[0])) goto next_try; $output = $return1[0]; $lastMatchUSA = $usaf2013[24][3]; $iCount = $return1['segment']; if(!array_key_exists($iCount, $metar_toDecode)) goto endPhenomena; if($metar_toDecode[$iCount] == 'OVR') $iCount++; if($metar_toDecode[$iCount] == 'MTNS') { $output .= ' is present weather not nearby but over mountains '; $iCount++; } if(count($metar_toDecode) > $iCount and $metar_toDecode[$iCount] == 'VC') { // 2018/02/02 21:54 METAR KBUF 022154Z 28014KT 10SM FEW024 FEW044 M10/M18 A3028 RMK AO2 SLP268 SNE30 SHSN VC NW-NE P0000 T11001183 if($show_diagnosticsR) echo '
processed ' . $metar_toDecode[$iCount]; $remarks_toDecode[$metar_toDecode[$iCount]] = 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' '; $iCount++; $output .= ' nearby, but neither on aerodrome nor beyond 10 statute miles,'; }elseif(count($metar_toDecode) > $iCount and $metar_toDecode[$iCount] == 'DSNT') { // 2018/02/02 22:54 METAR KBUF 022254Z 26015G18KT 10SM FEW025 FEW040 M11/M18 A3030 RMK AO2 SLP275 SHSN DSNT NW-NE T11061178 if($show_diagnosticsR) echo '
processed unmatched segment number ' . $iCount . ' containing ' . $metar_toDecode[$iCount] . ' into Distant'; $remarks_toDecode[$metar_toDecode[$iCount]] = 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' '; $iCount++; $output .= ' beyond 10 statute miles,'; } if(array_key_exists($iCount, $metar_toDecode) and $metar_toDecode[$iCount] == 'ALQDS') # added 12 Mar 2018 { // 2018/01/11 20:54 METAR KBUF 112054Z 19012KT 10SM FEW045 BKN065 BKN095 OVC240 15/09 A2995 RMK AO2 SLP143 SHRA DSNT ALQDS T01500094 56015 // Showers of rain in distance all quadrants $output .= ' in all quadrants'; }elseif(array_key_exists($iCount, $metar_toDecode) and in_array($metar_toDecode[$iCount], $compass)) { # 2017/12/10 21:56 METAR TJSJ 102156Z 08008KT 10SM FEW030 FEW045 SCT070 27/23 A2996 RMK AO2 SLP145 SHRA DSNT SW T02670228 $ // Went wrong at (1) → RMK_AO2   Not de-coded=SHRA $output .= ' to ' . $compass_expanded[$metar_toDecode[$iCount]]; $iCount++; if($show_diagnosticsR) echo '
processed only compass direction ' . $metar_toDecode[$iCount]; }else{ // 2018/02/02 21:54 METAR KBUF 022154Z 28014KT 10SM FEW024 FEW044 M10/M18 A3028 RMK AO2 SLP268 SNE30 SHSN VC NW-NE P0000 T11001183 // 2018/03/12 19:54 METAR KBUF 121954Z 05005KT 10SM FEW029 BKN037 OVC044 00/M07 A2992 RMK AO2 SLP141 SHSN DSNT W-N-NE T00001067 if($show_diagnosticsR) echo '
looking for hyphens in ' . $metar_toDecode[$iCount] . ' that separate compass directions'; $directions = preg_split('{[-]}', $metar_toDecode[$iCount]); # added 12 Mar 2018 if($show_diagnosticsR) print_array($directions, 'directions found'); $output .= ' looking '; $iCount++; foreach($directions as $part) { $output .= $compass_expanded[$part] . '; '; } } endPhenomena: do_output($decodeGroupCount['REMARK'], $iCount, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' '); goto end_while; next_try: // At this point there are a lot of METAR that having matched (in some cases) other codes to USA standards are also including code combinations that can not be decoded # $decodeGroupCount['FMH1']++; # $decodeGroupCount['USAF']++; // // USA Air Force 'USAF Manual 15-111: Remark Number 24: (1) Unofficial Weather Reports', /* (1) Unofficial Weather Reports. Information important to local aviation and public safety reported by an individual not task certified to take official weather observations. For example, UNOFFL RPT TORNADO 9 W OF KKAC PER LAW ENFORECMENT or CLD LYR AT 400 FT ON APCH RWY 23 RPRTD BY PIREPS, CIG VIS LWR ON APCH RWY14L. if($metar_toDecode[0] == 'UNOFFL' and $metar_toDecode[1] == 'RPT') ..... # what can I test for next? if(array_key_exists('LAW', $remarks_toDecode) and array_key_exists('ENFORECMENT', $remarks_toDecode)) ..... # what do I do next, can I assume will include 'PER'? if(array_key_exists('PIREPS', $remarks_toDecode) and array_key_exists('BY', $remarks_toDecode) and array_key_exists('RPRTD', $remarks_toDecode)) ..... # what do I look for before this "CIG VIS LWR ON APCH RWY14L" ...... Reports on approach are most likely to mention ceiling or cloud, but could be about visibility or almost any phenomena that might vary across aerodrome ..... I could do same tests as elsewhere for runway identifier, but how else might syntax vary? I could code to work for above examples, but how do I generalise from them for similar possible content? Until I work that out, no point in coding further here */ # $decodeGroupCount['USAF']++; // USA Air Force 'USAF Manual 15-111: Remark Number 24: (6) Location Unique Information', /* (6) Location Unique Information (as required), e.g., fog dispersal, rawinsonde data, state of ground, wind difference between parallel runways. No examples given of actual syntax, so unable to code for this */ // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. r) (previously q.) Obscurations # 18 // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 16 Partial Obscurations $lastMatchUSA = ' USAF specification=' . $usaf2013[16]. ' >> ' . $fmh1_2017[18]; // 2018/03/12 19:54 METAR KBUF 121954Z 05005KT 10SM FEW029 BKN037 OVC044 00/M07 A2992 RMK AO2 SLP141 SHSN DSNT W-N-NE T00001067 $return = doObscuring($lastMatchUSA, '(r16) ='); /* USA airfields where some content failed to be recognized but should now be decoded here ======================================================================================= Weather phenomena (like shower of rain) qualifiers: 2018/01/11 20:54 METAR KBUF 112054Z 19012KT 10SM FEW045 BKN065 BKN095 OVC240 15/09 A2995 RMK AO2 SLP143 SHRA DSNT ALQDS T01500094 56015 Went wrong at REM_DSNT   Not de-coded=ALQDS - Should decode as 'all quadrants' 2018/02/16 21:54 METAR KBUF 162154Z 35010KT 10SM BKN038 BKN045 M01/M06 A3008 RMK AO2 SLP194 SHSN VC E T10111056 Went wrong at REM_VC   Not de-coded=E 2018/01/12 13:54 METAR KBUF 121354Z 21010KT 10SM FEW017 BKN035 OVC065 14/12 A2962 RMK AO2 RAB14E51 SLP029 SHRA VC NNE AND DSNT ALQDS P0001 T01390122 Went wrong at REM_VC_NNE   Not de-coded=AND - presumably this is only used for joining location clauses Went wrong at REM_VC_NNE   Not de-coded=DSNT - should decode as 'distant' Went wrong at REM_VC_NNE   Not de-coded=ALQDS - Should decode as 'all quadrants' Went wrong at REM_VC_NNE   Not de-coded=P0001 - assume not decoded because of preceding content, move array walk earlier to deal with that Went wrong at REM_VC_NNE   Not de-coded=T01390122 - assume not decoded because of preceding content, move array walk earlier to deal with that 2018/01/12 14:54 KBUF 121454Z 20013KT 10SM BKN031 BKN055 OVC080 14/13 A2961 RMK AO2 SLP026 SHRA DSNT SES 60002 T01440128 56009 Went wrong at REM_DSNT   Not de-coded=SES - if this meant to mean South-south-east? Or is it a reference to a place? */ #2017/12/27 12:56 METAR TJSJ 271256Z 08017G24KT 10SM FEW028 27/23 A3010 RMK AO2 RAB09E24 SLP191 VCSH N P0000 T02670228 // Went wrong at REM_VCSH   Not de-coded=N if(array_key_exists(0, $return) and array_key_exists('CONDITIONS', $decodeGroupProcessed)) // changed 29 Jan 2018, 4 Mar 2018 { $decodeGroupProcessed['CONDITIONS'] .= ' (r16)'; $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; goto end_while; } } // end first two characters switch unrecognised_remarks(); goto end_while; goto end_while; /* 2018/02/05 00:53 METAR KDET 050053Z 33009G18KT 1 1/2SM -SN FEW014 OVC020 M08/M11 A2996 RMK AO2 PK WND 35026/2357 SLP158 P0 Went wrong at (1) → RMK_PK_WND_35026/2357   Not de-coded=P0 - assume this was truncated METAR - ignore this */ lightning: // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. j. Lightning (Frequency_LTG(type)_[LOC]). // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 10 /* 1. When lightning is observed at a manual station, the frequency, type of lightning, and location shall be reported. The remark shall be coded in the format Frequency_LTG(type)_[LOC]. The contractions for the type and frequency of lightning shall be based on Table 12-5. The location and direction shall be coded in accordance with paragraph 12.7.c. For example, “OCNL LTGICCG OHD”, “FRQ LTG VC”, or “LTG DSNT W”. */ $lastMatchUSA = $usaf2013[10]; // For aerodromes employing an observer to produce METAR, a number of lightning reports can be included according to above specification 12.7.1 j, each uses multiple segments // With observers reporting there are 3 possibilities just for the first segment, for example 'OCNL LTGICCG OHD', FRQ LTG VC', or 'LTG DSNT W'. // The first segment always contains either 'LTG' - can't determine frequency of flashes, or 'OCNL' - less than 1 flash per minute, or 'FRQ' - 1 to 6 flashes per minute, // or 'CONS' - more than 6 flashes per minute // The second segment optionally contains ' CG' - lightning between cloud and ground, 'IC' - lightning within cloud, 'CC' - lightning reaching from one cloud to another, // or 'CA' - streaks of lightning that pass from cloud to air, but do not strike the ground. // The final segment always contains 'OHD' - overhead, 'DSNT' - beyond 10 miles, or one of the 8 points of the compass. // As manually reported lightning has not yet been seen in any METAR, it has not yet been coded in this script if(array_key_exists(2, $metar_toDecode) and ($metar_toDecode[1] == 'LTGCG' || $metar_toDecode[1] == 'LTGIC' || $metar_toDecode[1] == 'LTGCC') and in_array($metar_toDecode[2], $compass)) { /* 'OCNL LTGIC SW' = 'Lightning details similar to Canada - When lightning is observed, indicate frequency (OCNL, FRQ, CONTUS), type (LTGCG, LTGIC and LTGCC) and direction from station. The following may be used as a guide for the frequency of lightning: (i) OCNL less than one flash per minute (ii) FRQ one to six flashes per minute (iii) CONTUS more than six flashes/minute' */ $output = ' Lightning '; switch(substr($metar_toDecode[1],-2)) // substr used so will work if preceded by 'LTG' or not { // USA specification allows for more than one of these to appear - unclear whether as successive segments (not decoded) or what case 'CG': $output .= '(between cloud and ground) '; break; case 'IC': $output .= '(within cloud) '; break; case 'CC': $output .= '(between one cloud and another cloud) '; break; case 'CA': $output .= '(between cloud and \ir without reaching ground) '; break; } $output .= 'seen towards ' . $compass_expanded[$metar_toDecode[2]] . ', with '; switch($metar_toDecode[0]) { case 'OCNL': $output .= 'less than one flash '; break; case 'FRQ': $output .= 'one to six flashes '; break; case 'CONTUS': $output .= 'more than six flashes '; break; case 'LTG': $output .= 'unable to count flashes '; break; } $output .= 'per minute'; do_output($decodeGroupCount['REMARK'], 3, $output, $lastMatchUSA); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; break; }else /* 2. When lightning is detected by an automated system: a) Within 5 nautical miles of the Airport Location Point (ALP), it will be reported as TS in the body of the report with no remark; b) Between 5 and 10 nautical miles of the ALP, it will be reported as VCTS in the body of the report with no remark; c) Beyond 10 but less than 30 nautical miles of the ALP, it will be reported in remarks only as LTG DSNT followed by the direction from the ALP. */ if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'LTG' and $metar_toDecode[1] == 'DSNT' and array_key_exists($metar_toDecode[2],$compass)) { // For automated stations, the specification in Remarks Group for lightning states that the first segment will always contain 'LTG' // For automated stations, the specification in Remarks Group for lightning states that the second segment will always contain 'DSNT' // For automated stations, the specification in Remarks Group for lightning states that the third segment will always contain a compass direction $output = 'Lightning seen beyond 10 miles, but within 30 miles in ' . $compass_expanded[$metar_toDecode[2]] . ' direction; '; do_output($decodeGroupCount['REMARK'], 3, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[10]); $decodeGroupCount['FMH1']++; $decodeGroupCount['USAF']++; break; } end_while: if(count($metar_toDecode) < 1 ) goto exit_point; if($beforeThis == count($metar_toDecode))goto exit_point; } // end While exit_point: $returnArray['matched'] = $lastMatchUSA; if(count($metar_toDecode) > 0) $returnArray = array_merge($metar_toDecode, $returnArray); return $returnArray; } // end USA sub-function //==================================================// // RMK Group: USA - walk - anonymous function // // called from case 31 in above USA sub-function // // 12.7.2 Additive and Automated Maintenance Data. // //==================================================// function USA_walk(&$value, $key) { global $metar_toDecode, $decodeInfo, $decodeGroupCount, $decodeGroupProcessed, $remarks_toDecode, $show_diagnostics, $show_diagnosticsR, $dM_Rmk, $lastMatchUSA, $usaf2013, $fmh1_2017, $passCountUSA, $file; // shared with other scripts global $descriptorArray, $precipitationTypes, $cloudAmountBandCode, $compass, $compass_expanded, $hoursOffset; // shared with other sub-functions if(!count($metar_toDecode)) return; if($value != 'Un-matched') return; /* // ===========================================================================================// // Single segment codes - decode using array walk technique =================================// // The following specifications are handled within an array_walk function ===================// // ===========================================================================================// USAF remark numbers with single segment specifications handled by this array walk: ================================================================================== 'Remark number 3 - Augmented or Automated Systems - AO1, AO2 or AO2A', 'Remark number 11 - Beginning and Ending of Precipitation - w’w’B(hh)mmE(hh)mm', 'Remark number 12 - Beginning and Ending of thunderstorms - TSB(hh)mmE(hh)mm', 'Remark number 20 - Pressure rising or falling rapidly - PRESRR or PRESFR', 'Remark number 21 - Sea level pressure - SLPppp or SLPNO', 'Remark number 25 - Hourly precipitation amount - Prrrr', 'Remark number 26 - 3- and 6-Hourly precipitation amount - 6RRRR', 'Remark number 27 - 24-Hourly precipitation amount (reporting time variant each side of international date line (IDL)) - 7R24R24R24R24', 'Remark number 28 - Snow depth on ground - 4/sss', 'Remark number 29 - Hourly temperature and dew point - TsnT’T’T’snT’dT’dT’d or TsnT’T’T’', 'Remark number 30 - 6-hourly maximum temperature - 1snTxTxTx', 'Remark number 31 - 6-hourly minimum temperature - 2snTnTnTn', 'Remark number 32 - 24-hour maximum temperature and the 24-hour minimum temperature - 4snTxTxTxsnTnTnTn', 'Remark number 33 - 3-hourly pressure tendency - 5appp', 'Remark number 34 - Sensor status indicators - RVRNO, PWINO, PNO, FZRANO, TSNO, VISNO (LOC), CHINO (LOC)', 'Remark number 35 - maintenance indicator sign - $', Federal Meteorological Handbook with single segment specifications handled by this array walk: ============================================================================================== # METAR/SPECI remarks fall into 2 categories: (1) Automated, Manual, and Plain Language (see paragraph 12.7.1) '12.7.1 k. Precipitation timings (wwB[hh]mmE[hh]mm...)', # 11 // Restricted to rain/drizzle until 2017, from 2017 adds alternative precipitation types like snow, and permits inclusion of descriptors such as freezing and showers '12.7.1 l. Thunderstorm timings (TSB(hh)mmE(hh)mm)', # 12 '12.7.1 w) Sea-level pressure (SLPppp)', # 23 # METAR/SPECI remarks fall into 2 categories: (2) Additive and Maintenance Data (see paragraph 12.7.2). '12.7.2 e. 6 hour max temp - 5 digits with first digit being "1" (1snTxTxTx)', #28 '12.7.2 f. 6 hour min temp - 5 digits with first digit being "2" (2snTnTnTn)', #29 '12.7.2 h. 3 hour pressure trend - 5 digits with first digit being "5" (5appp)', #30 '12.7.2 a. (3) (b) 3 and 6 hourly precipitation - 5 digits with first digit being "6" (6RRRR)', #31 '12.7.2 a. (3) (c) 24 hour precipitation - 5 digits with first digit being "7" (7RRRR)', #32 '12.7.2 a. (3) (a) Hourly precipitation - letter "P" then 4 digits (Prrrr)', #33 '12.7.2 a. (3) (e) Water equivalent - 6 digits with first digit being "9" and second "3" (933RRR)', #34 '12.7.2 g. 24 hour temp extremes - 9 digits with first digit being "4" (4snTxTxTxsnTnTnTn)', #35 '12.7.2 c. Sunshine - 6 digits with first digit being "9" and second "8" (98mmm)', #36 '12.7.2 a. (3) (b) 3 and 6 hourly precipitation not available (6////)', #37 '12.7.2 a. (3) (d) Snow depth (4/sss)', #38 '12.7.2 b. 3 and 6 hour Cloud Types (8/ClCmCh)', #39 "12.7.2 d. Temperature and dew-point (more precise) (TsnT'T'T'snT'dT'dT'd)", #40 '12.7.2 i. ice (I1nnn), (I3nnn and I6nnn)', #41 '12.7.2 k. Maintenance Indicator. ($)', #42 // ===========================================================================================// */ if($show_diagnosticsR) echo '
USA walk examines
' . $key; if(strlen($key) > 1 and substr($key ,-1) == '$') { // If maintenance indicator is found attached to a segment, separate it off $remarks_toDecode['$'] = "Un-matched"; // create new element for maintenance indicator $metar_toDecode[] = "$"; // create new element for maintenance indicator $remarks_toDecode[$key] = null; // cancel existing $remarks_toDecode[substr($key, 0, -1)] = "Un-matched"; // create new element for corrected segment $metar_toDecode[2] = substr($metar_toDecode[1], 0, -1); $remarks_toDecode[$metar_toDecode[2]] = "Un-matched"; $remarks_toDecode[$metar_toDecode[1]] = null; $remarks_toDecode['$'] = "Un-matched"; $metar_toDecode[1] = $metar_toDecode[0]; $metar_toDecode[] = "$"; $specification = 30; // return to start of (2) Additive and Maintenance Data (see paragraph 12.7.2). $passCountUSA++; // Increment pass counter as going back ready to restart if($show_diagnostics or $show_diagnosticsR) print_array($metar_toDecode, '
Repeating from specification ' . $specification . ' with rearranged $metar_toDecode array '); } if(is_numeric($key)) // Speed up processing - if not numeric, skip these specifications { #========================================= # Single segments that are purely numeric: #========================================= if($show_diagnosticsR or $show_diagnostics) echo '>> Only processing single segment specifications that are purely numerical codes
'; $len = strlen($key); // length of METAR segment $sub = substr($key, 0, 1); // in many cases, code type 'group indicator' is just first digit if($show_diagnosticsR or $show_diagnostics) echo '>> Looking for match based on number of digits=' . $len . ', group indicator=' . $sub . '
'; switch($len . '_' . $sub) // Speed up processing - using 'switch' is more efficient than multiple use of 'if' { case '5_1': $lastMatchUSA = $usaf2013[30]; if(substr($key,1,1) < 2) // check sign digit { /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 e. 6 hour max temp - 5 digits with first digit being "1" (1snTxTxTx)', #28 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 30 // /////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 30 -> 6-hourly max temperature', $file); $output = '6-hour Maximum temperature '; $output .= substr($key,1,1) == 0 ? 0.1 * substr($key,2) . ' °C' : -0.1 * substr($key,2) . ' °C'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[28]); goto exitCall; } break; case '5_2': $lastMatchUSA = $usaf2013[31]; if(substr($key,1,1) < 2) // check sign digit { /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 f. 6 hour min temp - 5 digits with first digit being "2" (2snTnTnTn)', #29 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 31 // /////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 31 -> 6-hourly min temperature', $file); $output = '6-hour Minimum temperature '; $output .= substr($key,1,1) == 0 ? 0.1 * substr($key,2) . ' °C' : -0.1 * substr($key,2) . ' °C'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[29]); goto exitCall; } break; case '5_5': # 33 $lastMatchUSA = $usaf2013[33]; /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 h. 3 hour pressure trend - 5 digits with first digit being "5" (5appp)', #30 // // h. 3 hour pressure trend (5appp) - this contains table 12.8 listing codes for various // // changes of pressure in inches of mercury, but the codes equate to tenths of hPascals // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 33 // /////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 33 -> 3-hourly pressure trend', $file); /* if(strlen($key) == 4){ $remarks_toDecode[$key] = null; $key = '5' . $key; $remarks_toDecode[$key] = 'added'; } // metaf2xml decoder treats 4 digit numbers as if they are '3 hour pressure trend ', but it has warning 'The information below may be out-dated, inaccurate, or both. It is not suited for use in aviation.' // flightutilities decoder treats 4 digit numbers as minimum directional visibility, and has no such warning, so I follow this view */ switch(substr($key,1,1)) { case 0: $output = 's.l.p. was rising, then falling, but is now higher '; break; case 1: $output = 's.l.p. was rising, then either steady, or rising more gradually, to end higher '; break; case 3: $output = 's.l.p. was falling, then rising, and is now higher '; break; case 2: $output = 's.l.p. is higher '; break; case 4: $output = 's.l.p. is unchanged '; break; case 7: $output = 's.l.p. is lower '; break; case 5: $output = 's.l.p. was falling, then rising, and is now lower '; break; case 6: $output = 's.l.p. was falling, then either steady, or falling more gradually, to end lower '; break; case 8: $output = 's.l.p. was steady or rising, then falling, and is now lower '; break; } $output .= 'by ' . (0.1 * substr($key,2)) . ' hectoPascals compared with 3 hours ago'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[30]); goto exitCall; case '5_6': $lastMatchUSA = $usaf2013[26]; ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 a. (3) (b) 3 and 6 hourly precipitation - 5 digits with first digit being "6" (6RRRR)', #31 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 26 // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 26 -> 3- or 6-hourly Precipitation amount', $file); // Speed up processing - use of single quotes is more efficient than double quotes, as latter requires PHP parser to scan quoted content looking for variable names /* Federal Handbook 1: 3-hourly report: A METAR taken at 0300, 0900, 1500, or 2100 UTC. 6-hourly report: A METAR taken at 0000, 0600, 1200, or 1800 UTC. The 3- and 6-hourly precipitation group shall be coded in the format, 6RRRR, where 6 is the group indicator and RRRR is the amount of precipitation. The amount of precipitation shall be coded in inches, using the tens, units, tenths and hundredths digits of the amount. When an indeterminable amount of precipitation has occurred during the period, RRRR shall be coded 6////. // metaf2xml decoder treats 5 digit numbers starting with '5' as if they are '6-Hour Precipitation Amount', // but it has warning 'The information below may be out-dated, inaccurate, or both. It is not suited for use in aviation.' // Hence I ignore that divergence from the format as specified in above quote from Handbook 1. */ if(substr($decodeGroupProcessed['DAY+TIME'], 2, 2) % 6 == 0) $type = 6; else $type = 3; if(substr($metar_toDecode[0],1) > 0) $output = $type . '-hour precipitation = ' . number_format(0.254 * substr($key,1),1) . ' mm'; else { $output = $type . '-hour precipitation = trace of ';; $temperature = substr($decodeInfo['TEMP'], 0, strpos($decodeInfo['TEMP'], '°')); if($temperature > 4) $output .= 'rain (< 0.2 mm fell)'; elseif($temperature < 0 ) $output .= 'snow (accumulation < 2 mm deep)'; else $output .= 'either snow (accumulation < 2 mm deep) or rain (< 0.2 mm fell)'; } walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[31]); goto exitCall; case '5_7': $lastMatchUSA = $usaf2013[27]; ///////////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 a. (3) (c) 24 hour precipitation - 5 digits with first digit being "7" (7RRRR)', #32 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 27 // ///////////////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 27 -> 24-hourly Precipitation amount', $file); $output = '24-hour precipitation = ' . number_format(0.254 * substr($key,1),1) . ' mm'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[$lastMatchUSA]); goto exitCall; case '5_9': # 36 if(substr($key,0,2) == '98') { ///////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 c. Sunshine - 6 digits with first digit being "9" and second "8" (98mmm)', #36 // // Not in US AF document // ///////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks - Group Indicator=98 -> c. Duration of Sunshine', $file); $output = 'Duration of sunshine seen yesterday = ' . substr($key,2) . ' minutes'; $lastMatchUSA = $fmh1_2017[36]; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' FMH1 specification=' . $lastMatchUSA . ' '); goto exitCall; } break; case '6_9': # 37 if(substr($key, 0, 3) == '933') { ////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // a. Precipitation. (3) (e) Water Equivalent of Snow on Ground (933RRR) // ////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks - Group Indicator=933 -> a. Precipitation. (3) (e) Water Equivalent of Snow on Ground', $file); $output = 'Snowfall - Rain equivalent = ' . (25.4 * substr($key,3)) . ' mm'; $lastMatchUSA = $fmh1_2017[37]; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' FMH1 specification=' . $lastMatchUSA . ' '); goto exitCall; } break; case '9_4': # 38 $lastMatchUSA = $usaf2013[32]; if(substr($key,1,1) < 2 and substr($key,5,1) < 2) // check sign digits { /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // g. 24-Hour Maximum and Minimum Temperature (4SThThThSTlTlTl). // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 32 // /////////////////////////////////////////////////////////////////////////////////////////////// optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 32 -> 24-hourly max/min temperatures', $file); $output = '24-hour Maximum temperature '; $output .= substr($key,1,1) == 0 ? 0.1 * substr($key,2,3 ) . ' °C': -0.1 * substr($key,2,3) . ' °C'; $output .= ', 24-hour Minimum temperature '; $output .= substr($key,5,1) == 0 ? 0.1 * substr($key,-3) . ' °C': -0.1 * substr($key,-3) . ' °C'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA. ' >> ' . $fmh1_2017[$lastMatchUSA]); goto exitCall; } break; } // end switch }else { #============================================= # Single segments that are not purely numeric: #============================================= // Federal Meteorological Handbook No.1 - - 12.7.1 Automated, Manual, and Plain Language Remarks. v) (previously u.) Pressure Rising or Falling Rapidly // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 20 if(substr($key, 0, 4) == 'PRES') { if(array_key_exists('CLOUD-DETAILS', $decodeInfo)) { $i = strpos($decodeInfo['CLOUD-DETAILS'] ,'ft'); $decodeInfo['CLOUD-DETAILS'] = substr($decodeInfo['CLOUD-DETAILS'], 0, 2 + $i) . ' (r20)' . substr($decodeInfo['CLOUD-DETAILS'], 2 + $i); $prefix = '(r20) ='; }else $prefix = ''; $output = $prefix . 'Pressure '; $lastMatchUSA = $usaf2013[20]; switch($key) { case 'PRESRR': $output .= 'rising rapidly (> 2hPa per hour)'; break; case 'PRESFR': $output .= 'falling rapidly (> 2hPa per hour)'; break; } walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $usaf2013[20] . ' >> ' . $fmh1_2017[22]); goto exitCall; } // Federal Meteorological Handbook No.1 - - 12.7.1 Automated, Manual, and Plain Language Remarks. c. Type of Automated Station // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark number 3 - Augmented or Automated Systems - AO1, AO2 or AO2A // mandatory for Automated stations to include this in their METAR, so decoded in this script. $lastMatchUSA = $usaf2013[3]; switch($key) { case 'AO1': // 2018/01/25 10:00 METAR CWGD 251000Z AUTO 11005KT M12/M13 RMK AO1 SOG 00 1009 SLP299 T11181132 $output = 'Automatic detectors at this airport do not record precipitation'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $usaf2013[3] . ' >> ' . $fmh1_2017[3]); goto exitCall; case 'AO2': # Example METAR // from Ansbach, Germany (using USA format): METAR ETEB 011308Z AUTO 31005KT 9999 OVC031 14/07 A3020 RMK AO2 SLP237 // McConnell Air Force Base|Wichita, Kansas, USA=====2017/10/26 15:56 KIAB 261556Z AUTO 26007KT 10SM CLR 18/05 A2963 RMK AO2 SLP030 T01800045 // Niagara Falls International Airport|Niagara Falls, New York, USA===== // 2017/10/26 16:00 KIAG 261600Z 30008KT 10SM SCT028 BKN050 BKN060 10/02 A2988 RMK AO2 T01000022 // Cyril E. King Airport|St. Thomas, US Virgin Islands=====2017/10/26 12:53 TIST 261253Z AUTO 10010KT 10SM CLR 29/24 A2995 RMK AO2 SLP142 T02940239 $ // Luis Muñoz Marín International Airport|San Juan, Puerto Rico===== // 2017/10/26 15:56 TJSJ 261556Z 05015KT 10SM FEW038 FEW120 31/26 A2992 RMK AO2 SLP129 T03110256 $decodeInfo['BY'] = 'Automated observations (without Observer augmentation)'; // Wording variant from that seen widely, including on http://www.aviationweather.gov/metar $decodeGroupProcessed['BY'] = '{RMK_' . $key . '}'; $decodeGroupCount['BY']++; $remarks_toDecode[$key] = $lastMatchUSA; // Change value for segment that has now been decoded $array_element_id = array_search($key, $metar_toDecode); // find which item is to be removed from still to be decoded array $done = array_splice($metar_toDecode, $array_element_id, 1); // remove that item if($show_diagnostics or $show_diagnosticsR) echo '
Removing ' . $done[0] . ' as updated caption setting `Automated` in heading
'; goto exitCall; case 'AO2A': /* Examples from Figure 13.2 in Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Note all include suffix 'A' to 'AO2' METAR ETAR 010756Z VRB06KT 1400 R09/1220 -RA BR FEW000 SCT008 OVC012 01/M01 A2938 RMK AO2A TWR VIS 1600 VIS N 3200 CIG 010V015 BR FEW000 SLPNO ALSTG ESTMD; METAR KHLN 011158Z 27004KT 3/4SM R32/P6000FT -RA BR FEW000 SCT005 OVC020 00/M01 A2992 RMK AO2A TWR VIS 2 BR FEW000 SLP982 ALSTG/SLP ESTMD 60010 70100 4/002 10010 21002 52010; METAR EOIN 011157Z 30003KT 9999 CLR M04/M10 A3003 RMK AO2A SLP985 70010 4/002; METAR RKTG 010358Z 00000KT 0800 FG VV011 24/24 A2998 RMK AO2A TWR VIS 1000 SLP982 RVRNO; METAR ETAB 010655Z 24010G18KT 9999 TS SCT020CB BKN035 30/27 A2993 RMK AO2A TS 4SW MOV NE SLPNO; METAR KGRF 011157Z 24012KT 10SM -TSRA FEW008 FEW025TCU SCT030CB 25/17 A2992 RMK AO2A PK WND 28045/10 TS 2NE MOV SE FU FEW008 SCT030 V BKN TCU SE-S SLPNO 60010 70010 52010 */ // Mildenhall: 2017/08/04 07:38 EGUL 040738Z 24019G24KT 9999 FEW023 18/14 A2975 RMK AO2A SLP077 $decodeInfo['BY'] = 'Automated observations with Observer augmentation'; // Wording variant from that in Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] $decodeGroupProcessed['BY'] = '{RMK_' . $key . '}'; $decodeGroupCount['BY']++; $remarks_toDecode[$key] = $lastMatchUSA; // Change value for segment that has now been decoded $array_element_id = array_search($key, $metar_toDecode); // find which item is to be removed from still to be decoded array $done = array_splice($metar_toDecode, $array_element_id, 1); // remove that item if($show_diagnostics or $show_diagnosticsR) echo '
Removing ' . $done[0] . ' as updated caption setting `Observer Augmented` in heading
'; goto exitCall; } // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. w) (previously v.) Sea-Level Pressure // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 21 if(substr($key, 0, 3) == 'SLP') { $return = walk_SLP($key, 'pass=' . $passCountUSA . ' USAF specification=' . $usaf2013[21] . ' >> ' . $fmh1_2017[23]); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // a. Precipitation. (3) (a) Hourly Precipitation Amount (Prrrr) // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 25 // /////////////////////////////////////////////////////////////////////////////////////////////// if(substr($key, 0, 1) == 'P' and strlen($key) == 5 and is_numeric(substr($key, 1))) { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 25 -> Hourly Precipitation amount', $file); $lastMatchUSA = $usaf2013[25]; if(substr($key,1) > 0) $output = 'Precipitation last hour = ' . number_format(0.254 * substr($key,1),1) . ' mm'; else { $output = 'Precipitation last hour = A trace of ';; $temperature = substr($decodeInfo['TEMP'], 0, strpos($decodeInfo['TEMP'], '°')); if($temperature > 4) $output .= 'rain (< 0.2 mm fell)'; elseif($temperature < 0 ) $output .= 'snow (accumulation < 2 mm deep)'; else $output .= 'either snow (accumulation < 2 mm deep) or rain (< 0.2 mm fell)'; } walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $usaf2013[25]); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA - Report not available (1////) // // '12.7.2 e. 6 hour max temp - 5 digits with first digit being "1" (1snTxTxTx)', #28 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 30 // /////////////////////////////////////////////////////////////////////////////////////////////// if($key == '1////') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 30 -> 6-hourly max temperature not available (1////)', $file); $output = '6-hour Maximum temperature could not be determined'; $lastMatchUSA = $usaf2013[30]; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[28]); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA - Report not available (2////) // // '12.7.2 f. 6 hour min temp - 5 digits with first digit being "2" (2snTnTnTn)', #29 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 31 // /////////////////////////////////////////////////////////////////////////////////////////////// if($key == '2////') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 31 -> 6-hourly min temperature not available (2////)', $file); $output = '6-hour Minimum temperature could not be determined'; $lastMatchUSA = $usaf2013[31]; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[29]); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA - Report not available (5////) // // '12.7.2 h. 3 hour pressure trend - 5 digits with first digit being "5" (5appp)', #30 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 33 // /////////////////////////////////////////////////////////////////////////////////////////////// if($key == '5////') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 33 -> 3-hourly pressure trend not available (5////)', $file); $lastMatchUSA = $usaf2013[33]; $output = 'Sea Level Pressure Trend could not be determined'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[30]); goto exitCall; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA - Report not available (6////) // // '12.7.2 a. (3) (b) 3 and 6 hourly precipitation - 5 digits with first digit being "6" (6RRRR)', #31 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 26 // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// if($key == '6////') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 26 -> 3 and 6 hourly precipitation not available (6////)', $file); $lastMatchUSA = $usaf2013[26]; if(substr($decodeGroupProcessed['DAY+TIME'], 2, 2) % 6 == 0) $type = 6; else $type = 3; $output = $type . '-hour precipitation amount could not be determined'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA. ' >> ' . $fmh1_2017[31]); goto exitCall; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 a. (3) (c) 24 hour precipitation - 5 digits with first digit being "7" (7RRRR)', #32 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 27 // ///////////////////////////////////////////////////////////////////////////////////////////////////////// if($key == '7////') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 27 -> 24-hourly Precipitation amount not available (7////)', $file); $lastMatchUSA = $usaf2013[27]; $output = '24-hour precipitation amount could not be determined'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA. ' >> ' . $fmh1_2017[32]); goto exitCall; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 a. (3) (d) Snow depth (4/sss)', #38 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 28 // // Another snow-on-ground format uses multiple segments, with the first being 'SOG', see case 'SO' // ////////////////////////////////////////////////////////////////////////////////////////////////////////// if( strlen($key) == 5 and substr($key,0,2) == '4/' and is_numeric(substr($key,2)) ) { $lastMatchUSA = $usaf2013[28]; $output = 'Snow depth = ' . (25.4 * substr($key,2)) . ' mm'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[38]); goto exitCall; } ////////////////////////////////////////////////////////// // RMK Group: USA // // '12.7.2 b. 3 and 6 hour Cloud Types (8/ClCmCh)', #39 // // Not in US AF document // ////////////////////////////////////////////////////////// if(substr($key,0,2) == '8/' and is_numeric(substr($key,2,1))) { $lastMatchUSA = $fmh1_2017[39]; /* 3-hourly report: A METAR taken at 0300, 0900, 1500, or 2100 UTC. 6-hourly report: A METAR taken at 0000, 0600, 1200, or 1800 UTC. */ if(substr($decodeGroupProcessed['DAY+TIME'], 2, 2) % 6 == 0) $type = 6; else $type = 3; $output = 'Cloud types: a ' . $type . '-hourly report: Lowest Cloud Cover = '; switch(substr($key,2,1)) { case 0: $output .= '(none)'; break; case 1: $output .= 'fair weather Cumulus'; break; case 2: $output .= 'towering Cumulus'; break; case 3: $output .= 'Cumulonimbus'; break; case 4: $output .= 'Stratocumulus (few rolls, large elements)'; break; case 5: $output .= 'Stratocumulus (many rolls, small elements)'; break; case 6: $output .= 'fair weather Stratus or Fractostratus'; break; case 7: $output .= 'bad weather Fractocumulus or Fractostratus'; break; case 8: $output .= 'Cumulus and Stratocumulus'; break; case 9: $output .= 'Cumulonimbus'; break; } $output .= ', Middle Cloud Cover = '; switch(substr($key,3,1)) { case 0: $output .= '(none)'; break; case 1: $output .= 'thin Altostratus'; break; case 2: $output .= 'thick Altostratus'; break; case 3: $output .= 'thin Altocumulus'; break; case 4: $output .= 'patchy Altocumulus'; break; case 5: $output .= 'thickening Altocumulus'; break; case 6: $output .= 'Altocumulus'; break; case 7: $output .= 'Altocumulus with other types'; break; case 8: $output .= 'Altocumulus with turrets'; break; case 9: $output .= 'chaotic Altocumulus'; break; case '/': $output .= '(obscured as lower level overcast)'; }; $output .= ', Upper Cloud Cover = '; switch(substr($key,4,1)) { case 0: $output .= '(none)'; break; case 1: $output .= 'filaments of Cirrus'; break; case 2: $output .= 'Cirrus'; break; case 3: $output .= 'dense Cirrus'; break; case 4: $output .= 'thickening Cirrus'; break; case 5: $output .= 'low Cirrus / Cirrostatus'; break; case 6: $output .= 'high Cirrus / Cirrostatus'; break; case 7: $output .= 'Cirrostatus'; break; case 8: $output .= 'partial Cirrostatus'; break; case 9: $output .= 'Cirrocumulus or Cirrocumulus / Cirrus / Cirrostatus'; break; case '/': $output .= '(obscured as lower level overcast)'; }; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' FMH1 specification=' . $lastMatchUSA . ' '); goto exitCall; } ///////////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA // // "12.7.2 d. Temperature and dew-point (more precise) (TsnT'T'T'snT'dT'dT'd)", #40 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 29 // ///////////////////////////////////////////////////////////////////////////////////////////////////// // Leeward Point Field (US Naval Station Guantanamo Bay)| Guantánamo=====2017/10/26 15:56 MUGM 261556Z 00000KT 9999 FEW030 BKN140 BKN280 31/24 A2991 RMK SLP129 T03110239 // Naval Support Facility Diego Garcia|Diego Garcia, British Indian Ocean Territory=====2017/10/26 13:55 FJDG 261355Z 04003KT 9999 CLR 30/24 A2991 RMK SLP125 T02990241 /* Hourly Temperature and Dew Point (TsnT'T'T'snT'dT'dT'd). At designated stations, the hourly temperature and dew point group shall be coded to the tenth of a degree Celsius in the format, TsnT'T'T'snT'dT'dT'd, where T is the group indicator, sn is the sign of the temperature, T'T'T' is the temperature, and T'dT'dT'd is the dew point (see paragraphs 10.5.1 and 10.5.3). The sign of the temperature and dew point shall be coded as 1 if the value is below 0 C and 0 if the value is 0 C or higher. */ if(substr($key,0,2) == 'T0' or substr($key,0,2) == 'T1') // Higher precision temperature and dew-point report than main METAR Group (to meet USA demands) { $lastMatchUSA = $usaf2013[29]; $decodeGroupProcessed['TEMP'] .= ' + RMK_' . $key; $tp = 0.1 * substr($key,2,3); $tempAir = ('1' == substr($key,1,1)) ? -$tp : $tp; if(strlen($key) > 4) $dp = 0.1 * substr($key,6); else $dp = '////'; if(is_numeric($dp)) $dp = ('1' == substr($key,5,1)) ? -$dp : $dp; $array_element_id = array_search($key, $metar_toDecode); // find which item is to be removed from still to be decoded array $done = array_splice($metar_toDecode, $array_element_id, 1); // remove that item $remarks_toDecode[$key] = 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[40]; if($show_diagnosticsR or $show_diagnostics) { echo '

USA segment = ' . $key . ' → matched Temperature:' . $tp . ' and matched Dew-point: ' . $dp . ' within sub-function "USA_walk()"

'; } rev_get_temperature($tempAir, $dp); goto exitCall; } ///////////////////////////////////////////////// // RMK Group: USA FMH1 - '12.7.2 i. ice // // (I3nnn and I6nnn)', #41 // // Includes often used format I1nnn // // Not in USAF guide // ///////////////////////////////////////////////// if(strlen($key) == 5 and substr($key,0,1) == 'I' and is_numeric(substr($key,1,1))) { # 44 $lastMatchUSA = $fmh1_2017[41]; switch (substr($key,1,1)) { case 1: // 1. Hourly Ice Accretion Amount (I1nnn) $output = 'hourly '; break; case 3: // 2. 3-Hourly Ice Accretion Amount (I3nnn) $output = '3-hourly '; break; case 6: // 2. 6-Hourly Ice Accretion Amount (I3nnn) $output = '6-hourly '; break; default: $output = ''; } // Output below updated 13 Feb 2018 if(substr($key, 3) > 0 ) $output = substr($key, 2, 1) . '.' . substr($key, 3) . ' inches of ice accretion has occurred, during the ' . $output . ' reporting period'; else $output = 'No ice accretion has occurred, during the ' . $output . ' reporting period'; walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USFM1 specification=' . $lastMatchUSA . ' '); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA FMH1 - '12.7.2 k. Maintenance Indicator. ($)', #42 // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 35 // /////////////////////////////////////////////////////////////////////////////////////////////// if($key == '$') { optionalDiagnosticOutput('Walk - new_USA_remarks = Air Force Manual 15-111 [U.S. Air Force] Remark Number 35 -> Maintenance indicator', $file); $lastMatchUSA = $usaf2013[35]; $output = 'Some Data Above may be INACCURATE! The automated system detects that maintenance is needed'; // Wording partly copied from http://www.aviationweather.gov/metar walk_output($decodeGroupCount['REMARK'], $key, $output, 'pass=' . $passCountUSA . ' USAF specification=' . $lastMatchUSA . ' >> ' . $fmh1_2017[42]); goto exitCall; } /////////////////////////////////////////////////////////////////////////////////////////////// // RMK Group: USA FMH1 - '12.7.1 k. Precipitation timings (wwB[hh]mmE[hh]mm...)', // // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] // // Remark number 11 - Beginning and Ending of Precipitation - w’w’B(hh)mmE(hh)mm // /////////////////////////////////////////////////////////////////////////////////////////////// $keyCopy = $key; // Take copy of segment so can strip any matched leading characters out of copy if(in_array(substr($keyCopy, 0, 2), $descriptorArray)) { $descLocal = substr($keyCopy, 0, 2); $descriptor = array_search($descLocal, $descriptorArray);// Look for optional descriptor like 'Shower', 'Freezing' etc. if($show_diagnosticsR) echo '
function USA_walk(): Translated descriptor "' . $descLocal . '" into "' . $descriptor . '"'; $keyCopy = substr($keyCopy, 2); }else{ $descLocal = ''; $descriptor = ''; } if(in_array(substr($keyCopy, 0, 2), $precipitationTypes)) { $precipLocal = substr($keyCopy, 0, 2); $precipitation = array_search($precipLocal, $precipitationTypes); // Look for mandatory precipitation type like 'Rain', 'Snow', 'Hail stones' etc. if($show_diagnosticsR) echo '
function USA_walk(): Translated precipitation type "' . $precipLocal . '" into "' . $precipitation . '"'; $keyCopy = substr($keyCopy, 2); }else $precipitation = ''; if(strlen($key) != strlen($keyCopy) and (substr($keyCopy, 0, 1) == 'B' || substr($keyCopy, 0, 1) == 'E')) // Look for mandatory 'Begin' or 'End' { // Condition selects here only those that match pattern for this specification ([ww]wwB[hh]mmE[hh]mm...) $lastMatchUSA = $usaf2013[11] . ' (' . $fmh1_2017[11] . ')'; $return = weatherBeginEnd($key, $lastMatchUSA, $descLocal, $descriptor, $precipLocal, $precipitation, $keyCopy); goto exitCall; } // end of if /////////////////////////////////////////////////////////////////////////////////////////////// // USA FMH1 - 12.7.1 Automated, Manual, and Plain Language Remarks. l. Beginning and Ending of Thunderstorms // Surface Weather Observations: Air Force Manual 15-111 [U.S. Air Force] Remark Number 12 /////////////////////////////////////////////////////////////////////////////////////////////// if(substr($key,0,2) == 'TS' // thunderstorm not defined in precipitation types, so added as separate test and (strpos($key,'B') == 2 or strpos($key,'E') >= 2)) { $lastMatchUSA = $usaf2013[12] . ' (' . $fmh1_2017[12] . ')'; $descLocal = substr($key, 0, 2); $descriptor = array_search($descLocal, $descriptorArray); // Look up descriptor of 'Thunderstorm' if($show_diagnosticsR) echo '
function USA_walk(): Translated descriptor "' . $descLocal . '" into "' . $descriptor . '"'; $keyCopy = substr($key, 2); $return = weatherBeginEnd($key, $lastMatchUSA, $descLocal, $descriptor, '', '', $keyCopy); } } exitCall: } // end anonymous function - USA_walk ############################################################################################################################## # Novel sub-function written by SPAWS to process any number of outputs from decoding one segment in the Remarks Group. # # This is called for just two specifications, both within the USA collection, that report beginning and ending times. # ############################################################################################################################## function weatherBeginEnd($key, $lastMatchUSA, $descCode, $descriptor, $precipCode, $precipitation, $keyRest) { global $metar_toDecode, $remarks_toDecode, $descriptorArray, $precipitationTypes, $decodeInfo, $decodeGroupCount, $decodeGroupProcessed, $faa2017, $show_diagnosticsR, $file; // shared with other scripts $keyLocal = $key; $keyProcessed = ''; $timeArray = array (); // Initialise array used for converting UTC times to local times // Elmendorf Air Force Base|Anchorage, Alaska===== // 2017/10/26 15:56 PAED 261556Z AUTO 30011KT 10SM FEW046 BKN060 BKN070 OVC085 04/03 A2954 RMK AO2 RAB1456E13DZB13E27DZB35E47 SLP000 P0000 T00370025 // If the precipitation were showery, the relevant segment of the Remark Group would be coded "SHRAB05E30SHSNB20E55". // 2017/12/04 19:56 METAR PAED 041956Z AUTO 00000KT 10SM FEW048 OVC070 04/00 A2915 RMK AO2 RAB02E14DZB14E17RAB17E30RAB33E42 PRESRR SLP868 P0000 T00350002 $ // 2018/02/11 08:53 METAR KDET 110853Z 07007KT 6SM BR BKN009 OVC012 M06/M07 A2999 RMK AO2 UPB15E17FZRAB17E38SNE15 CIG 006V011 SLP163 P0001 60002 I1001 I3001 T10561072 58027 if($show_diagnosticsR) { echo '
Parameters passed into function weatherBeginEnd() are:'; echo '
Segment to process = ' . $keyLocal; echo '
Specification to process = ' . $lastMatchUSA; echo '
Descriptive to process = ' . $descCode . ' = ' . $descriptor; echo '
Weather to process =' . $precipCode . ' = ' . $precipitation; echo '
Begin/End to process =' . $keyRest; } $keyProcessed = $descCode . $precipCode; restart: $outputLocal = ''; $processedLocal = '(' . $decodeGroupCount['REMARK'] . ') → RMK_'; $subsetArray = preg_split('{([BE])}', $keyLocal, -1, PREG_SPLIT_DELIM_CAPTURE); if($show_diagnosticsR and $subsetArray[0] == $descCode . $precipCode) print_array($subsetArray, 'Ready to process timings'); for($jCount= 1; $jCount < count($subsetArray); $jCount += 2) { $type = $subsetArray[$jCount] == 'B' ? 'began ' : 'ended '; $keyProcessed .= $subsetArray[$jCount]; if($show_diagnosticsR) echo '
Cumulative processed = ' . $keyProcessed; if($show_diagnosticsR) echo '
began or ended = ' . $type . ' applied to ' . $subsetArray[1 + $jCount]; if(is_numeric($subsetArray[1 + $jCount])) { $timeString = $subsetArray[1 + $jCount]; if($show_diagnosticsR) echo '
timing = ' . $timeString; $rest = ''; }else{ if(is_numeric(substr($subsetArray[1 + $jCount], 0, 4))) $timeString = substr($subsetArray[1 + $jCount], 0, 4); else $timeString = substr($subsetArray[1 + $jCount], 0, 2); $rest = substr($subsetArray[1 + $jCount], strlen($timeString)); if($show_diagnosticsR) echo '
timing = "' . $timeString . '" and rest = ' . $rest; } $processedLocal .= $descCode . $precipCode . $subsetArray[$jCount] . $timeString; if($show_diagnosticsR) echo '
Just processed = ' . $processedLocal; $outputLocal .= $descriptor; if($descCode == 'SH' and $precipitation != '') $outputLocal .= ' of'; $outputLocal .= ' ' . $precipitation . ' ' . $type . ' at '; if(strlen($timeString) == 4) // hour and minutes in sub-string { $timeArray = adjustTime($timeString); $outputLocal .= $timeArray['hour'] . ":" . $timeArray['minute'] . " (local time); "; }else{ // only minutes specified $timeArray = adjustTime("99" . $timeString); // minutes derived from the sub-string $timeArray['hour'] = substr($decodeInfo['LOCAL'], 11, 2); // hour derived from METAR day_time segment $outputLocal .= $timeArray['hour'] . ':' . $timeArray['minute'] . ' (local time); '; } if($show_diagnosticsR) echo '
Output (' . $decodeGroupCount['REMARK'] .') = ' . $outputLocal; $keyProcessed .= $timeString; if($show_diagnosticsR) echo '
Original = ' . substr($key, 0, strlen($keyProcessed)) . '~' . substr($key, strlen($keyProcessed)) . '; Cumulative processed = ' . $keyProcessed; if($rest != '') { // Process just the one timing $decodeInfo['REMARK'][$decodeGroupCount['REMARK']] = $outputLocal; $decodeGroupProcessed['REMARK'][$decodeGroupCount['REMARK']] = $processedLocal; if($show_diagnosticsR) { echo "

Decoding count = " . $decodeGroupCount['REMARK'] . ' → matched ' . $keyProcessed . " =" . $decodeGroupProcessed['REMARK'][$decodeGroupCount['REMARK']] . ' against ' . $lastMatchUSA . '

'; file_put_contents($file, "Decoding count = " . $decodeGroupCount['REMARK'] . " → matched " . $keyProcessed . " =" . $decodeGroupProcessed['REMARK'][$decodeGroupCount['REMARK']] . " against " . $lastMatchUSA . "\r\n", FILE_APPEND | LOCK_EX); } $decodeGroupCount['REMARK'] ++; break; } } if($rest != '') { $keyLeft = substr($key, strlen($keyProcessed)); if($show_diagnosticsR) echo '
Segment to process = ' . $keyLeft; if(in_array(substr($keyLeft, 0, 2), $descriptorArray)) // Look for optional descriptor like 'Shower', 'Freezing' etc. { $descCode = substr($keyLeft, 0, 2); $descriptor = array_search($descCode, $descriptorArray); if($show_diagnosticsR) echo '
function weatherBeginEnd(): Translated descriptor "' . $descCode . '" into "' . $descriptor . '"'; $keyLeft = substr($keyLeft, 2); $keyProcessed .= $descCode; }else{ $descCode = ''; $descriptor = ''; } if(in_array(substr($keyLeft, 0, 2), $precipitationTypes)) // Look for mandatory precipitation type like 'Rain', 'Snow', 'Hail stones' etc. { $precipCode = substr($keyLeft, 0, 2); $precipitation = array_search($precipCode, $precipitationTypes); if($show_diagnosticsR) echo '
function weatherBeginEnd(): Translated precipitation type "' . $precipCode . '" into "' . $precipitation . '"'; $keyLeft = substr($keyLeft, 2); $keyProcessed .= $precipCode; }else{ $precipCode = ''; $precipitation = ''; } $keyLocal = $descCode . $precipCode . $keyLeft; goto restart; } $array_element_id = array_search($key, $metar_toDecode); // find which item is to be removed from still to be decoded array $done = array_splice($metar_toDecode, $array_element_id, 1); // remove that item $remarks_toDecode[$key] = $lastMatchUSA; // Change value for segment that has now been completely decoded if($show_diagnosticsR) { echo '

Finished matching ' . $keyProcessed . ' against ' . $lastMatchUSA . '

'; } return $decodeGroupCount['REMARK']; } // end function weatherBeginEnd() /* Version history =============== 0.0.0 17 July 2017 First developmental version. Skeleton of idea to decode Remarks Group for USAF aerodromes in UK, composing script based on Wikipedia article https://en.wikipedia.org/wiki/METAR for North America. 0.0.1 28 July 2017 Improved script with good commenting and neat layout. 0.0.2 31 July 2017 Now written/expanded the various conditions that decode all the predicted content of Remarks Group. Ready for extensive testing. 0.1.0 13 Aug 2017 Revised the specified testing conditions in code for RMK Group after reading USA standard FCM-H1-2005, particularly addressing multiple begin and end times for precipitation, but also tackling other USA specifications that were over-simplified in Wikipedia article on METAR. 0.1.1 23 Aug 2017 Recoded Remarks sub-function, now clearer that 20 specifications are coded out of 40 possible for USA, removing some Remark Group specifications listed as used according to https://en.wikipedia.org/wiki/METAR, but not in FCM-H1-2005 guide. No non-USA codes in this version. Improved the optional diagnostic output throughout script so easier to see what decoder has done for future de-bugging. Improved the logging of full METAR reports where not all the content was successfully decoded, so building up a file for future tests. 0.1.2 31 Aug 2017 If temperature and dew-point specified in Remarks Group with one decimal place, this replaces integer figures from main Temperature / Dew point Group. If sea level pressure specified in Remarks Group, in this version it is is appended to altimeter pressure derived from sea/airfield level Pressure Group. Added first attempt at time zone conversion to one USA specification (12.7.1 k. Precipitation timings), this is just for adjusting any displayed hour on output (not adjusting minutes, and does not cope around midnight). 0.1.3 5 Sep 2017 Still finding new METAR being logged because cannot decode all parts. Amended code so for USA this version decodes 26 specifications out of the 40 applying in standard FCM-H1-2005. An ongoing problem is automated reports from aerodromes that are not following this USA standard for the order of specifications, so changed coding so it loops through the specifications a further time until reaches end of METAR report. Danger of this is an endless loop, so added incrementing counter to ensure discarding segments, as too complicated to implement the other idea to discard specifications already processed. 0.1.4 27 Sep 2017 Made minor changes re precipitation handling (Not yet fully implement precipitation handling changes in began/ended sub-function) ready for USA changes that take effect November 2017. Successfully completed renumbering in USA sub-function of Remarks sub-function due to separation of hail and snow pellets in standard FCM-H1-2017. Now handling 32 out of 41 USA Remark specifications, due to separation of hail and snow pellets. Added many examples to prepare for future changes to RMK decoding. 0.2.0 29 Oct 2017 Moved initialisation code for Remarks Group from 'decodeMETAR_sub_funct.php' to 'decodeMETAR_Rmk.php' so latter now does everything related to Remarks Group. As now have aim of script working worldwide, redesigned that script (it is loaded direct from 'decodeMETAR_sub_funct.php'), breaking it into multiple functions. Within 'decodeMETAR_Rmk.php' retained function for undetermined country common pressure and cloud decoding and sub-functions for Australia, Italy, and New Zealand standards. The new sub-function for United States was moved from 'decodeMETAR_Rmk.php', to this new script 'decodeMETAR_USA_Rmk.php' that is now loaded by 'decodeMETAR_Rmk.php' and it is the default for other nations. The United States Air Force operates in many countries using the USAF book as the basis of METAR they issue regardless of which country they are operating in! Satisfied that USA are working well for all codes met in METAR decoded so far. 0.2.1 22 Nov 2017 Now processing the 45 specifications based on USA standard FCM-H1-2017 and Air Force Manual 15-111 [U.S. Air Force] and a few extras not found in the handbooks but used when Observers add reporting segments to the RemarksGroup that in effect replace Standard METAR Groups for which the automatic equipment has failed to quantify. Following addition (in 'decode_METAR.php') of sub-function adjustTime() to change a UTC time in raw METAR into a Local Time for output, re-worked this script so everywhere that a time is output the common sub-function is called to convert it to local time. This affects conversion to Local Time in 12.7.1 e. Wind shift, 12.7.1 k. Precipitation timings, and 12.7.1 l. Thunderstorm timings with this new functionality that can adjust day of month, the hour, and the minute figures for output. As part of the simplification of the USA sub-function, a new common (sub) 'function do_output(there are 5 parameters)' has been introduced, it is in the 'decodeMETAR_sub_funct.php' script. The main function for USA in this script calls that function to handle those requirements that are repeated for each specification (taking processed segments out of still to be decoded array, placing those removed segments into array for processed Groups as typically used in mouse-over on a web page, adding text passed in via parameter to output array and incrementing the counter used within Remarks Group processing). Other places where more than one specification requires same code have also had that code moved into new (sub) functions initially kept within this script. The new sub-functions cover begin and end times, dealing with locations, dealing with sea level pressure, and recognizing visibility fractions. 0.2.2 27 Nov 2017 The non USA-specific sub-functions for dealing with locations, dealing with sea level pressure, and recognizing visibility fractions have all been moved to 'decodeMETAR_sub_funct.php' because this script is only loaded to use with USA specifications, and those could be called from elsewhere. 0.2.3 30 Dec 2017 Substantial re-write of sub-function 'decode_USA_remarks(,)'; 1) corrected interpretation of 4 digit numeric segment, 2) corrected units from inches to cm for report of Observer measured 'snow on ground', 3) added pass-count to capture looping back within the function, 4) as a consequence added speed-up jumping using first character in segment to determine whether can jump forwards or backwards a number of specifications, instead of having to try all in sequence and potentially do a further pass of the while loop to find any out of sequence; better implementation of start, stop to eliminate repeating USA specifications already tested against, this applies if have to enter the whole sub-function a further time for another pass; and 5) some slight renumbering of first few specifications to match actual sequence in real METAR. I added a pass count to capture that looping back within the USA sub-function in the optional logging of any METAR with entries in RMK Group that stores how each segment in Remarks Group was matched. Now records every time goes into USA sub-function, so shows for example if codes for not just USA found or if the USA sub-function entered twice. 0.2.4 12 Jan 2018 For automatic reporting stations where manual observations added, now adjust '$decodeGroupProcessed['BY'] to highlight manual augmentation. Tiny bug correction - USA specification 41 (24-Hour Maximum and Minimum Temperature), string length should be tested to be 9 not 7 characters long. 0.3.0 26 Jan 2018 Conversion of some of USA sub-function (mostly the part for 12.7.2 Additive and Automated Maintenance Data) into array walk, but generally making all the USA instructions more efficient through better design of PHP instructions. Removal of key of 'Country' from $decodeGroupCount. Replacement by series of new Country-specific keys in same array such as $decodeGroupCount['USA_extras']. 0.3.2 14 Feb 2018 Now that it appears almost all the possible METAR content in Remarks Group for USA is being handled successfully, this update focussed on improving the look of outputs. For USA array walk (1, 3, and 6-Hourly Precipitation) - defined trace mentioning rain, or snow, purely depending on actual air temperature. (Decided not to mention hail and not to look to see if rain mentioned elsewhere). Corrected counting for USA decoding (previously some specifications did not increment counter). Adjusted script so could cope with any USA single segment specification even when it appears out of sequence that handbook stipulates. Corrected incorrect condition in USA decoding that prevented 'Snow depth (4/sss)' being decoded. Improved output format for precipitation (rain 1, 3, 6, 24 hourly) by using 'number_format()'. Corrected processing for USA specification 14 (12.7.1 k. Precipitation timings (wwB[hh]mmE[hh]mm...)) that was not working for 'freezing' and other descriptors (USA made change in November 2017 that permitted timings to apply to precipitation other than rain, but my script was not adjusted). Improved output format for USA case 20 (Variable Ceiling Level) by using 'number_format()'. Removal of comments into portable document format paper. 0.3.3 1 Mar 2018 An attempt to renumber the specifications in USA to try to get them in correct sequence, according to USAF guidance that although generally repeating FMH is presented in an easier to assimilate tabular format. In particular USAF guide splits some FMH groups into sub-groups with additional examples. The problem with both USAF guide and FMH1 is that some of guidance is too vague about the exact formats permitted and the given examples don't match what I have seen. Added snippet (based on Canada one) for condensation trails. Small correction to case 10 (Tower or Surface Visibility) to ensure correctly processed by function novel_allVisibility(). Further work on quick jump (e.g. for sea level pressure, do direct call to sub-function and then jumps to end of loop) and adjusting counting for USAF and FMH, the two sources of documented specifications. Some comments rewritten in a more informative style quoting from manual and including paragraph references, alternative formats, and example. 0.4.0 11 Mar 2018 Total redesign, eliminating old serial specification processing. New design selects by first two characters of first segment for multi-segment specifications, and all single segment processing done first by array walk. Then for multi-segment processing the first two characters used in switch/case selection approach with most case in alphabetical order. Revised function begin_end() to cope with began time after ending time. New function weatherBeginEnd() called once per specification and using preg_split() replaces potentially multiple calls to old function begin_end() with lots of global variable updating in two functions. Removed processing loop for timing of precipitation, and thunderstorms so that replacement function weatherBeginEnd() is only called once per specification. 0.4.1 14 Mar 2018 Made that total redesign work, by jumping to end of while loop at end of each successful match. Improved processing for precipitation observed in multiple directions. Improved processing for sector visibility, now extended to include in multiple directions, improved processing for visibility at second location. Improved processing for weather phenomenon and obscuration, now coping better with the range of possible content (and different numbers of segments). */ /* ?> not used to end script as per PHP manual instruction 'If a file is pure PHP code, it is preferable to omit the PHP closing tag at the end of the file. This prevents accidental white-space or new lines being added after the PHP closing tag, which may cause unwanted effects because PHP will start output buffering when there is no intention from the programmer to send any output at that poi