script= 'decodeMETAR_Can_Rmk.php', © author= SPAWS, version= 0.3.6, "; // 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, but 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 July 2017 to February 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 # #-----------------------------------------------------------# // ============== CANADA ================= ################################################################################################################################################################################ # The following sub-function, to decode what Canada put in the Remarks Group of a METAR report, was added when I decided to extend my decoder coverage to worldwide. # # The Manual of Surface Weather Observations (MANOBS) - Seventh edition, Amendment 19 effective April 2015, prescribes the standard procedures of the Meteorological # # Service of Canada for observing, recording and reporting weather conditions. An addendum 19a, has been effective as of May 2016. The document is available at # # https://ec.gc.ca/manobs/default.asp?lang=En&n=3a380ded-1 and I have based this sub-function on that. # # ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- # # Grateful thanks to Paul (from Komoka Village, Ontario), for all his assistance. In October 2017, he gave me the link to the manual cited above, and he has been testing # # this decoding script since the 9th of November 2017, although I am testing my whole suite against METAR from all other the world, he has given this Canada sub-function # # (and the USA equivalent) a really through testing in a live environment, and helped me greatly in my pursuit of achieving the best decoding possible. # ################################################################################################################################################################################ function decode_CAN_remarks() { global $metar_toDecode, $decodeInfo, $decodeGroupCount, $decodeGroupProcessed, $pressureHPA, $temp, $dp, $show_diagnostics, $show_diagnosticsR, $dM_Rmk; // shared with main script of suite global $remarks_toDecode, $precipitationTypes, $cloudAmountBandCode, $cloud_type_array, $compass, $compass_expanded, $hoursOffset, $file, $passCountCan; // shared with other sub-functions /* One day at London Airport, Ontario, Canada (selected METAR, not showing all that were issued) 201801051600 METAR CYXU 051600Z 30002KT 15SM -SHSN FEW030 M18/M24 A3010 RMK CU2 SLP221= 201801051500 METAR CYXU 051500Z 32006KT 15SM FEW040 M19/M24 A3009 RMK SC1 SC TR SLP217= 201801051400 METAR CYXU 051400Z 30004KT 15SM FEW040 M20/M24 A3006 RMK SC1 SC TR SLP208= 201801051300 METAR CYXU 051300Z 29004KT 15SM FEW040 M21/M25 A3005 RMK SC1 SLP205= 201801051200 METAR CYXU 051200Z 26004KT 15SM SCT035 M21/M24 A3004 RMK SC3 SLP201= 201801051000 METAR CYXU 051000Z 26004KT 260V320 15SM BKN036 M21/M24 A3001 RMK SC7 FROIN SLP189= 201801050800 METAR CYXU 050800Z 31004KT 15SM FEW025 M20/M24 A3000 RMK SC1 SC TR FROIN SLP184= 201801050700 METAR CYXU 050700Z 31004KT 15SM FEW025 M20/M23 A2998 RMK SC1 FROIN SLP178= 201801050600 METAR CYXU 050600Z 29006KT 15SM FEW025 M19/M22 A2997 RMK SC1 FROIN SLP175= 201801050500 METAR CYXU 050500Z 29004KT 15SM FEW025 FEW160 M19/M22 A2997 RMK SC1AC1 AC TR SLP173= 201801050400 METAR CYXU 050400Z 29005KT 15SM FEW042 SCT120 M19/M22 A2997 RMK SC2AC1 SLP173= 201801050300 METAR CYXU 050300Z 32007KT 15SM -SHSN BKN025TCU M18/M22 A2997 RMK TCU7 SLP175= 201801050100 METAR CYXU 050100Z 34007KT 15SM -SHSN BKN025 M18/M22 A2998 RMK SC5 CVCTV CLD EMBD SLP175= 201801050046 SPECI CYXU 050046Z 34008KT 15SM -SHSN BKN027 M17/M21 A2998 RMK SC5 CVCTV CLD EMBD SLP175= 201801050000 METAR CYXU 050000Z 35012KT 12SM SCT033 M16/M21 A2996 RMK SC4 SLP170= 201801042300 METAR CYXU 042300Z 35010KT 15SM FEW020TCU M16/M21 A2996 RMK TCU2 SLP169= 201801042000 METAR CYXU 042000Z 33013G23KT 15SM DRSN FEW025 M13/M19 A2992 RMK SC2 SLP155= 201801041600 METAR CYXU 041600Z 34008KT 15SM FEW040 M12/M18 A2995 RMK SC2 SLP163= 2018/01/12 16:20 CYXU 121620Z 33018G26KT 10SM OVC012 M02/M03 A2977 RMK SF8 CIG RAG SLP091 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=RAG 2018/01/12 20:09 CYHM 122009Z 34017G24KT 1 1/2SM SN SCT007 OVC039 M03/M05 A2980 RMK SN2SC2SC4 VIS VRB 1 2 SLP098 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=2 2018/01/12 20:09 CYHM 122009Z 34017G24KT 1 1/2SM SN SCT007 OVC039 M03/M05 A2980 RMK SN2SC2SC4 VIS VRB 1 2 SLP098 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=2 2018/01/12 22:20 CYSN 122220Z 33013KT 3/4SM SN DRSN VV007 M05/M07 A2993 RMK SN8 CIG VRB 59 VIS VRB 1/21 PRESRR SLP140 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=59 Went wrong at (0) → RMK   Not de-coded=VIS Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=1/21 Went wrong at (0) → RMK   Not de-coded=PRESRR 2018/01/13 00:07 CYCK 130007Z AUTO 01031G40KT 1 1/4SM SN OVC020 M06/M08 A2999 RMK VIS VRB 3/42 1/4 PRESRR SLP165 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=3/42 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1/4 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=PRESRR 2018/01/13 00:00 CYSN 130000Z 34011G18KT 3/4SM SN DRSN BKN006 OVC017 M07/M08 A2998 RMK SN4SF2SC2 CIG VRB 48 VIS VRB 1/21 PRESRR SLP158 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=48 Went wrong at (0) → RMK   Not de-coded=VIS Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=1/21 Went wrong at (0) → RMK   Not de-coded=PRESRR 2018/01/13 02:00 CYSN 130200Z 35016G23KT 3/4SM SN DRSN BKN008 OVC012 M08/M10 A2999 RMK SN4SF2SC2 /S02/ CIG VRB 610 VIS VRB 1/21 LAST STFD OBS/NEXT 131200 UTC SLP160 Went wrong at (1) → RMK_/S02/   Not de-coded=CIG Went wrong at (1) → RMK_/S02/   Not de-coded=VRB Went wrong at (1) → RMK_/S02/   Not de-coded=610 Went wrong at (1) → RMK_/S02/   Not de-coded=VIS Went wrong at (1) → RMK_/S02/   Not de-coded=VRB Went wrong at (1) → RMK_/S02/   Not de-coded=1/21 Went wrong at (1) → RMK_/S02/   Not de-coded=LAST Went wrong at (1) → RMK_/S02/   Not de-coded=STFD Went wrong at (1) → RMK_/S02/   Not de-coded=OBS/NEXT Went wrong at (1) → RMK_/S02/   Not de-coded=131200 Went wrong at (1) → RMK_/S02/   Not de-coded=UTC 2018/01/23 04:01 CYCK 230401Z AUTO 24011KT 4SM TSRA BR SCT023 BKN030 BKN040 OVC055 08/07 A2955 RMK LTNG DIST ALQDS PRESRR SLP012 Went wrong at (0) → RMK   Not de-coded=LTNG Went wrong at (0) → RMK   Not de-coded=DIST Went wrong at (0) → RMK   Not de-coded=ALQDS # ALQDS All Quadrants Went wrong at (0) → RMK   Not de-coded=PRESRR Being wrongly decoded as "Partial" PRESFR 2018/02/25 07:29 METAR CYHM 250729Z 06015G21KT 2SM RA BR OVC002 01/00 A2980 RMK FG1SF7 PRESFR SLP101 */ static $obscuring_array = array( // Specific to Canada 'Blowing Dust' => 'BLDU', 'Blowing Sand' => 'BLSA', 'Blowing Snow' => 'BLSN', 'Dust Storm' => 'DS', 'Drizzle (including Freezing Drizzle)' => 'DZ', 'Fog (any form including Mist)' => 'FG', 'Smoke' => 'FU', 'Hail' => 'GR', 'Haze' => 'HZ', 'Ice Crystals' => 'IC', 'Ice Pellets (including Ice Pellet Showers)' => 'PL', 'Rain (any form including Showers and Freezing)' => 'RA', 'Snow (including Showers, Pellets, Grains)' => 'SN', 'Sandstorm' => 'SS', 'Volcanic Ash' => 'VA' ); ////////////////////////////////////////////////////////////// // RMK Group: Canadian codes - What it says in MANOBS // ////////////////////////////////////////////////////////////// /* METAR in CANADA: See page 435 in Manual of Surface Weather Observations (MANOBS) for summary of phenomena in main part of METAR and whether they require something in Remarks. "Part A Observing procedures — general" of MANOBS works through all the different types of observations and gives guidance on correct procedures. "Part B Hourly observations" includes 'Chapter 10 Recording the hourly observations' and that has many examples of entries for the Remarks 'column', some of which also appear in Chapter 16 in Part E. It is these examples that have guided the writing of this sub-function. Cross-references to the relevant paragraphs in "Part E Rate-of-rainfall and METAR" that includes 'Chapter 16 - METAR - Aviation Routine Weather Report', describing the content of every Group in a Canadian METAR are given throughout my suite, not just in this sub-function. WMO volume II (which lists the agreements between Canada and WMO for how their METAR will be reported) on page 12 says Canadian METAR will always contain a 'RMK' Group. Canadian METAR example without remarks Group: METAR CWTU 010800Z AUTO ///// A2928= # Absolute minimum - just mandatory cloud and pressure in main part, no Remarks Group 16.3.13 Remarks (RMK) The Remarks portion of the METAR observation is used to describe meteorological information of importance. Entries in Remarks are by no means restricted to the examples shown in this document. Directions in Remarks shall be recorded in a clockwise order. Example: Prevailing visibility is 7 mi. in light rain. Visibility to the east-southeast is reduced to 2 mi. in mist. METAR CYBX 111300Z 29008KT 7SM -RA OVC020 13/12 A2904 RMK SC8 VIS 2 E-SE BR SLP836 Remarks shall appear in the following order: 1) Layer type and amount. 2) General aviation remarks. 3) MSL pressure (SLPppp). */ ////////////////////////////////////////////////////////////// // RMK Group: Canadian codes - Design of sub-function // ////////////////////////////////////////////////////////////// /* 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. In the Remarks Group the allowed content is not defined internationally, but by individual nations. The quote "Remarks shall appear in the following order" from MANOBS above gives very little clue as to the order in which to consider conditions and "Entries in Remarks are by no means restricted to the examples shown in this document" suggests there is an infinite range of possible content. This decoding suite has two aims, first to decode everything (not possible if don't know what everything is) and second to process the METAR as quickly as possible (by not looping through lots of conditions repeating trying to find matches). As the MANOBS does not define precisely what can appear for Canada in the Remarks Group, this sub-function has been particularly difficult to design to achieve those aims. The known possible content in the Remarks Group for Canada involves either: 1) a single segment that has a defined meaning e.g. 'PRESRR' means that the station pressure is rising at the rate of 2.0 hPa or more per hour; 2) a single segment that includes several codes that are decrypted separately e.g. 'SC3AC1' is cloud type SC with 3 oktas and cloud type AC with 1 okta; or 3) a combination of a number of segments that together have a meaning e.g. 'OBS TAKEN +18' as the first of '16.3.13.2.3 Weather Remarks' means some observations were taken 18 minutes after the hour when they were due. */ $timeArray = array (); // Initialise array used for converting UTC times to local times if(!array_key_exists('Canada', $decodeGroupCount)) $decodeGroupCount['CanadaWalk'] = $decodeGroupCount['Canada'] = 0; // initialise Counter that will record number of matches within this sub-function $decodeGroupProcessed['CAN'] = "Manual of Surface Weather Observations - Seventh edition, amendment 19a effective May 2016: Chapter 16 - METAR"; ////////////////////////////////////////////////////////////////// $matched = false; // Initialise that no Canada specification has yet been matched // /////////////////////////////////////////////////////////////////// static $manobs = array ( 'Manual of Surface Weather Observations: Chapter 16 - METAR', // Remarks shall appear in the following order: // 1) Layer type and amount. '1) Layer type and amount. a) 16.3.13 Additional Layer information - large', // case 1 '1) Layer type and amount. a) 16.3.13 Additional Layer information - with precipitation', // case 2 '1) Layer type and amount. a) 16.3.13 Additional Layer information - extras not in Sky Cover Group', // case 3 '1) Layer type and amount. a) 16.3.13 Additional Layer information - unstable not in Sky Cover Group', // case 4 '1) Layer type and amount. b) 16.3.13.1 Obscuring Layer type and amount (oktas)', // case 5 // 2) General aviation remarks. '2) General aviation remarks. 16.3.1.2 General Aviation Remarks - sensors unavailable', // case 6 // 2) General aviation remarks. a) wind '2) General aviation remarks. a) 16.3.13.2.1 Wind Remarks (i) wind shift', // case 7 '2) General aviation remarks. a) 16.3.13.2.1 Wind remarks (ii) wind estimated (optionally due to ice accretion)', // case 8 // 2) General aviation remarks. b) visibility '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (i) 16.3.6.2 Sector visibilities', // case 9 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (ii) 16.3.6.3 Variable visibility', // case 10 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iii) visibility improving rapidly', // case 11 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iv) 16.3.6.4 Point of observation' . ' (visibility from specified location)', // case 12 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (v) 10.2.19.3 Visibility (Remarks) when obscured', // case 13 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vi) Runway Visual Range', // case 14 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vii) Obscuration affecting visibility', // case 15 // 2) General aviation remarks. c) weather '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (i) Present Weather Phenomena affecting aviation', // case 16 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ii) Lightning', // case 17 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iii) Tornado, and directions', // case 18 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iv) Qualifying precipitation - intermittent', // case 19 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (v) Qualifying precipitation - wet', // case 20 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (vi) Qualifying precipitation - occasional', // case 21 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (vii) precipitation not reaching ground', // case 22 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (viii) Hail', // case 23 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ix) Light precipitation', // case 24 // 2) General aviation remarks. d) Sky condition '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (i) sky cover', // case 25 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (ii) sky ceiling', // case 26 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (iii) convective clouds', // case 27 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (iv) cloud tops', // case 28 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (v) orographic clouds', // case 29 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (vi) condensation trails', // case 30 // 2) General aviation remarks. e) Precipitation summary '2) General aviation remarks. e) 16.3.13.2.6 Snowfall Remarks', // case 31 '2) General aviation remarks. e) 16.3.13.2.7 Rainfall Remarks', // case 32 '2) General aviation remarks. e) 16.3.13.2.8 Snowfall (or rainfall) reporting procedures for part-time stations', // case 33 // 2) General aviation remarks. f) Reporting status '2) General aviation remarks. f) 16.3.13.3 Late weather observations', // case 34 '2) General aviation remarks. g) 16.3.13.4 Observational program status', // case 35 "Extra for volcanoes etc", // case 36 "Density altitude", /* case 37 - this item is not mentioned in MANOBS, but frequently is real final segment in Remarks Group for Canada */ 'VIA', // case 38 'dummy', // final case ); $specification = 1; // Start with first of Multiple segment specifications. $passCountCan = 1; // Allow for more than one pass, if necessary, through these specifications optionalDiagnosticOutput($manobs[0], $file); while (count($metar_toDecode) > 0 and $specification < 40) { /////////////////////////////////////////////////////////////////////// // RMK Group: Canadian codes - Multiple Segments per specification // //////////////////////////////////////////////////////////////////////// // Identify if first segment of multiple segments is one of following, and if so skip quickly to the relevant specification switch($metar_toDecode[0]) { case 'DENSITY': $specification = 37; break; case 'LAST': $specification = 35; break; case 'OBS': $specification = 34; break; case 'PPCN': $specification = 32; break; case 'ASOCTD': $specification = 28; break; case 'CVCTC': $specification = 27; break; case 'HAIL': $specification = 23; break; case 'VIRGA': $specification = 22; break; case 'SN': $specification = 20; break; case 'TORNADO': $specification = 18; break; case 'RVR': $specification = 14; break; case 'WSHFT': $specification = 7; break; case 'LRGA': $specification = 1; break; // in other specifications, either the content of the first segment can vary, or the same first segment may apply to several specifications } ///////////////////////////////////////////////////////// // RMK Group: Canadian codes - Single Segment Walk // ///////////////////////////////////////////////////////// /* The quickest approach, experience has shown, is to attempt to decode single segments first, as the others have to be processed in the correct sequence to identify how many segments are to be inked together. Single segments can be processed by an array_walk, each element of an array is run through all the conditions testing for a match. The single segments identified in the MANOBS and handled by 'function canadaWalk()' are: - Cloud type and Oktas for Cloud Layers already reported in Sky Cover Group - wind direction variation dddVddd - two bearings in one segment - Pressure Changes (rapidly falling or rapidly rising) - FROIN - RIME - CONTRAIL - Sea Level Pressure (SLPxxx) */ if(!isset($doneCanWalk)) // Only walk through all elements in the Remarks Group once looking for Canadian single segment matches { optionalDiagnosticOutput('decode_CAN_remarks(): array walk for Canada specifications using single segments', $file); $beforeThis = count($metar_toDecode); if(!array_key_exists('CanadaWalk', $decodeGroupCount)) $decodeGroupCount['CanadaWalk'] = 0; $testArray['Layer'] = $testArray['DirectionVariation'] = $testArray['PressureChange'] = false; array_walk($remarks_toDecode, "canadaWalk" , $testArray); if(count($metar_toDecode) == $beforeThis) { $doneCanWalk = false; if($show_diagnostics or $show_diagnosticsR) echo '
No segments were matched by Canada array_walk
'; }else{ $doneCanWalk = true; $matched = true; $decodeGroupCount['CanadaWalk'] += $beforeThis - count($metar_toDecode); if($show_diagnostics or $show_diagnosticsR) echo '
' . $decodeGroupCount['CanadaWalk'] . ' segments were matched by Canada array_walk
'; } if(count($metar_toDecode) < 1) goto exit_here; // if all segments decoded by array_walk optionalDiagnosticOutput('decode_COM_remarks(): array walk for Common specifications using single segments', $file); ############################################################################ # If country-specific specifications have matched some single segments # # yet there are some segments left, try common single specifications # ############################################################################ # $returnCOM = decode_COM_remarks(1); if(count($metar_toDecode) < 1) goto exit_here; // if all segments decoded by array_walk } /* Otherwise work through all the known combinations of multiple segments in turn. These have to be processed in the correct sequence to identify how many segments are to be linked together. The specifications are numbered based on the order that the examples appear in MANOBS. The loop that decodes these multiple segments makes assumptions about the sequence in which they are likely to be found and assigns a case number to each (as using 'switch' and 'case' is the most efficient way of dealing with multiple options in the language (PHP) used for this script). There is a possibility that the multiple segment specifications are included in a different order, to the order I have chosen to test for them here. Consequently, the loop can be passed through more than once (but there is a pass-counter to prevent endless looping). Like the other functions in the suite, if content is found that cannot be matched to a specification (or the start and/or end of a specification that could be matched is not identified), then the relevant content is both displayed as "not decoded" and output to a file where it is placed in context. Should this script be edited so it can cope with previously un-recognised content, the file can be used as a source to test the revised script. As quoted above, the MANOBS list is not exhaustive and so a certain amount of guesswork has been employed to determine what conditions to use to identify a specification. In general, a condition starts with checking that there are sufficient segments present (complicated if the specification has some optional parts so the number of segments can vary), and then either checks for precise content (in fixed content segments) or for the right format (in variable content segments). An additional complication is that operators entering (or modifying) the METAR report do not always follow the examples in MANOBS and so the conditions have to cope with some malformed segments (or segments in the wrong order) as well as attempting to cope with reporting items for which an example does not appear in the MANOBS. */ optionalDiagnosticOutput("Remarks Group: decode_CAN_remarks() specification iterator=$specification - paragraph=" . $manobs[$specification] . ", pass=" . $passCountCan, $file); // Diagnostic on loop start switch($specification) // Specification numbers defined in array above, not exact match to paragraphs in manual { /* Collection 1): Layer type and amount. '1) Layer type and amount. a) 16.3.13 Additional Layer information - large', // case 1 '1) Layer type and amount. a) 16.3.13 Additional Layer information - with precipitation', // case 2 '1) Layer type and amount. a) 16.3.13 Additional Layer information - extras not in Sky Cover Group', // case 3 '1) Layer type and amount. a) 16.3.13 Additional Layer information - unstable not in Sky Cover Group', // case 4 '1) Layer type and amount. b) 16.3.13.1 Obscuring Layer type and amount (oktas)', // case 5 */ case 1: // CANADA 16.3.13.1 Layer type - additional - a) Large ////////////////////////////////////////////////////////////// // RMK Group: Most widely used codes to try - Cloud type // ////////////////////////////////////////////////////////////// if(count($metar_toDecode) > 1 and $metar_toDecode[0] == "LRG" and in_array($metar_toDecode[1], $cloud_type_array)) { optionalDiagnosticOutput("
decode_CAN_remarks() -> Large Cloud type", $file); $output = "Large cloud of type " . array_search($metar_toDecode[1], $cloud_type_array) . " seen"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 2: // CANADA 16.3.13.1 Layer type - additional - b) with precipitation - 1 or 2 segments if(count($metar_toDecode) > 0 and in_array($metar_toDecode[0], $cloud_type_array)) { optionalDiagnosticOutput("
decode_CAN_remarks() -> other Cloud type - possible association with precipitation", $file); $output = "Some cloud of type " . array_search($metar_toDecode[0], $cloud_type_array) . " seen"; $matched = true; $iCount = 1; if(count($metar_toDecode) > 1 and $metar_toDecode[1] == 'TR') // an example "METAR CYYC 010800Z 34011KT 15SM FEW015 FEW056 BKN087 07/03 A2979 RMK SF1SC2AC5 SF TR SLP107 DENSITY ALT 3700FT=" { // (viii) 'TR' = "Trace reported for precipitation - either snow (< 2 mm deep) or rain (< 0.2 mm fell)" $iCount++; $output .= ' and associated with a Trace reported for precipitation - '; $temperature = substr($decodeInfo['TEMP'], 0, strpos($decodeInfo['TEMP'], '°')); if($temperature > 4) $output .= 'rain (< 0.2 mm fell)'; elseif($temperature < 0 ) $output .= 'snow (< 2 mm deep)'; else $output .= 'either snow (< 2 mm deep) or rain (< 0.2 mm fell)'; }elseif(count($metar_toDecode) > 2 and $metar_toDecode[1] == 'VRY' and $metar_toDecode[1] == 'THN') { // 2018/01/29 07:00 METAR CYHM 290700Z 02008KT 15SM SCT025 SCT210 M04/M06 A3030 RMK SC3CI1 SC VRY THN SLP273 $output .= ', but in a very thin layer'; $iCount += 2; } do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; if(count($metar_toDecode) < 1) break; $specification--; if($show_diagnostics or $show_diagnosticsR) echo "
Repeat specification to check next segment as it could be to same specification
"; break; } break; case 3: // CANADA 16.3.13 Additional Layer information - extras not in Sky Cover Group - 2 segments // Here there is no matching to main Cloud Layer Group, the Remarks Group entry is reporting additional Cloud Amounts and Cloud Type if(count($metar_toDecode)> 1 and array_key_exists($metar_toDecode[0], $cloudAmountBandCode) and array_search($metar_toDecode[1], $cloud_type_array, true)) // Note first condition is searching for key in array, and second condition is searching for value in array. // For purely historical development reasons, some of my arrays are defined with METAR content in key (easy to use key to output value), // others have METAR content in value (search for value returns key and key is in full English word or words). // It is all part of the pleasures of trying to learn PHP programming in small chunks at a time, by trying different approaches. # Example 2017/11/25 13:00 METAR CYXU 251300Z 27008KT 12SM -SHRA BKN009 OVC070 06/04 A2959 RMK SF7AC1 FEW ACC SLP026 # This case decodes FEW ACC { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing", $file); // Diagnostic on condition match $output = "Observed " . array_search($metar_toDecode[1], $cloud_type_array, true) . " cloud " . $cloudAmountBandCode[$metar_toDecode[0]]; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; } break; case 4: // CANADA 16.3.13 Additional Layer information - unstable conditions not in Sky Cover Group - multiple segments if($metar_toDecode[0] == 'CB' || $metar_toDecode[0] == 'TCU' || $metar_toDecode[0] == 'ACC') { if(count($metar_toDecode)> 1 and array_key_exists($metar_toDecode[1],$compass)) { // ACC W $iCount = 2; $output = "Observed " . array_search($metar_toDecode[0], $cloud_type_array, true) . " cloud seen looking " . $compass_expanded[$metar_toDecode[1]]; }elseif(count($metar_toDecode)> 2 and substr($metar_toDecode[1], 0, 3) == 'TOP' and array_key_exists($metar_toDecode[2],$compass)) { // 'CB TOPS SW' // CB not reported since base of observed CB clouds not visible. $iCount = 3; $output = "Observed " . array_search($metar_toDecode[0], $cloud_type_array, true) . ' cloud, but not reported as layer since only ' . $metar_toDecode[1] . ' of cloud seen, looking ' . $compass_expanded[$metar_toDecode[2]]; }elseif(count($metar_toDecode)> 3 and array_key_exists($metar_toDecode[3],$compass) and $metar_toDecode[2] == 'DIST') { // CB TOP DIST NW $iCount = 4; $output = "Observed " . array_search($metar_toDecode[0], $cloud_type_array, true) . ' cloud, but not reported as layer since only ' . $metar_toDecode[1] . ' of cloud seen in distance, looking ' . $compass_expanded[$metar_toDecode[3]]; } // Process $matched = true; $iCount = $metar_toDecode[2] == "UNAVBL" ? 3 : 2; do_output($decodeGroupCount['REMARK'], $iCount, $output, "decode_CAN_remarks - " . $manobs[$specification]); $decodeGroupCount['Canada']++; } case 5: // CANADA 1) Layer type and amount (oktas). b) 16.3.13.1 Each obscuring phenomenon that is present is quoted as an abbreviation followed by amount in oktas. if(array_key_exists('CONDITIONS', $decodeInfo) or array_key_exists('SKY-DETAILS', $decodeInfo)) { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing obscuring phenomena", $file); // Diagnostic on condition match $return = doObscuring($manobs[$specification], "(r3) ="); for($iCount = 0; $iCount < $return['count']; $iCount++) { $decodeGroupCount['Canada']++; if($return[$iCount] == 'CONDITIONS' and array_key_exists('CONDITIONS', $decodeInfo)) { $decodeInfo['CONDITIONS'] .= " (r3)"; if($show_diagnosticsR) echo '
added cross-reference indicator to weather phenomena'; }elseif($return[$iCount] != 'CONDITIONS' and array_key_exists('SKY-DETAILS', $decodeInfo)) { $decodeInfo['SKY-DETAILS'][] = array($return[$iCount][0] . " (r3):", $return[$iCount][1], '(ground level)'); $decodeGroupCount['SKY']++; if($show_diagnosticsR) echo '
added cross-reference indicator to sky cover'; } } }else{ // No weather phenomena reported $return = doObscuring($manobs[$specification], ""); } break; /* Collection 2) General aviation remarks. '2) General aviation remarks. 16.3.1.2 General Aviation Remarks - sensors unavailable', // case 6 */ case 6: // CANADA 2) 16.3.1.2 General Aviation Remarks if(count($metar_toDecode)> 2 and ($metar_toDecode[2] == "UNAVBL" || $metar_toDecode[1] == "UNAVBL")) // Revised 24 Jan 2018 to work with 2 segments as well as 3. { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing detector type", $file); // Diagnostic on condition match // First segment of specification // Example 2017/11/24 09:00 METAR CYKF 240900Z AUTO 21005KT 9SM OVC031 01/M03 A2988 RMK TS/LTNG TEMPO UNAVBL SLP130 if($metar_toDecode[0] == "TS/LTNG") $output = "Thunderstorm detector "; elseif($metar_toDecode[0] == "BARO") $output = "Barometric pressure detector "; elseif($metar_toDecode[0] == "CLD") $output = "Cloud detector "; elseif($metar_toDecode[0] == "DPLR" or $metar_toDecode[0] == "WND") $output = "Wind or Precipitation detector "; elseif($metar_toDecode[0] == "WX") $output = "General weather detector "; else $output = $metar_toDecode[0] . " "; // another real example: METAR CYZX 010800Z 25002KT 15SM SKC 01/00 A3027 RMK WNDY HILL UNAVBL SLP251= # ? how do I identify 3 items of free text and will it always be 3 segments? # is 'WNDY' (not included in list of allowed abbreviations) a valid segment to treat on own? should this just be treating 'HILL' (hill)and 'UNAVBL' (unavailable)? # Coding adjusted to cope with 2 or 3 segments and to recognise 'UNAVBL' as terminating this specification, but accept anything before that // Optional Middle segment if($metar_toDecode[1] == "TEMPO") $output .= "temporarily "; elseif($metar_toDecode[1] == "PERMLY") $output .= "permanently "; elseif($metar_toDecode[1] == "UNAVBL") $output .= ""; // added 24 Jan 2018 else $output .= $metar_toDecode[1] . " "; // copes with other text - Oct 2017 original // Final segment in specification $output .= " not available"; // "UNAVBL" // Process $matched = true; $iCount = $metar_toDecode[2] == "UNAVBL" ? 3 : 2; do_output($decodeGroupCount['REMARK'], $iCount, $output, "decode_CAN_remarks - " . $manobs[$specification]); $decodeGroupCount['Canada']++; } break; /* Collection 2) General aviation remarks. a) wind '2) General aviation remarks. a) 16.3.13.2.1 Wind Remarks (i) wind shift', // case 7 '2) General aviation remarks. a) 16.3.13.2.1 Wind remarks (ii) wind estimated (optionally due to ice accretion)', // case 8 '2) General aviation remarks. a) 16.3.13.2.1 Wind estimation' // case 9 */ case 7: // CANADA 2) General aviation remarks. a) 16.3.13.2.1 Wind remarks (i) Wind Shift 'WSHFT hhmm' = " wind shift occurred at (time)" // METAR…27040G55 …RMK… WSHFT 0850 if($metar_toDecode[0] != 'WSHFT') break; if($metar_toDecode[1] == 'AT') $iCount = 2; // 2018/02/28 23:33 CYTZ 282333Z AUTO 33012G17KT 9SM OVC065 12/03 A2979 RMK WSHFT AT 2318Z SLP091 // extra segment 'AT' inserted is not in MANOBS guidance else $iCount = 1; // as per MANOBS guidance // METAR 27040G55 RMK WSHFT 0850 // 'WSHFT 0850' = "wind shift at 08:50" (time variable, but in period since previous METAR) # DONE if(count($metar_toDecode) > $iCount and 1 === preg_match('~^([012][0-9])?([0-5][0-9])$~', $metar_toDecode[$iCount],$pieces)); // always just 2 segments, definite pattern to match { optionalDiagnosticOutput("Processing Wind Shift: specification iterator=$specification processing " . $metar_toDecode[0], $file); // Diagnostic on condition match if(strlen($metar_toDecode[$iCount]) == 2 and is_numeric($metar_toDecode[$iCount])) { $timeArray = adjustTime("99" . $metar_toDecode[$iCount]); $output = "Wind Shift started at " . $timeArray['minute'] . " minutes past hour (local time)"; }else{ $timeArray = adjustTime(substr($metar_toDecode[$iCount], 0, 4)); $output = "Wind Shift started at " . $timeArray['hour'] . ":" . $timeArray['minute'] . " (local time)"; } $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]."; $matched = true; do_output($decodeGroupCount['REMARK'], 1 + $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 8: // CANADA 2) General aviation remarks. a) 16.3.13.2.1 Wind remarks (ii) wind estimated (optionally 'due to ice accretion') if(count($metar_toDecode) > 1 and ($metar_toDecode[0] == "WND" || $metar_toDecode[0] == "WIND") and $metar_toDecode[1] == 'ESTD') { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing wind estimation", $file); // Diagnostic on condition match /* MANOBS: 16.3.13.2.1 Wind remarks METAR ... 05009 ... RMK ... WND ESTD DUE ICE ACCRETION If winds are estimated due to ice accretion, the remark shall be included in the report. METAR ... 14014 ...RMK ... WND ESTD If winds are estimated for reasons other than ice accretion, the remark shall be included in the report. */ // MANOBS: 7.4.4 If winds are estimated for reasons other than ice accretion, the following remark shall be included in the report: WND ESTD $iCount = 2; $output = '(r7)= Wind speed and direction report is estimated (and gusts are not reported)'; if(array_key_exists('SPEED', $decodeInfo)) { $decodeInfo['SPEED'] .= " (r3)"; if($show_diagnosticsR) echo '
added cross-reference indicator to wind speed report'; } if(count($metar_toDecode) > 4 and $metar_toDecode[2] == "DUE" and $metar_toDecode[3] == 'ICE' and $metar_toDecode[4] == 'ACCRETION') { // MANOBS: 7.4.3 If winds are estimated due to ice accretion, the following remark shall be included in the report: WND ESTD DUE ICE ACCRETION $iCount = 5; $output .= ' due to the accumulation of ice on the wind sensor (in this period of freezing rain, freezing drizzle or freezing fog)'; } $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; /* Collection 2) General aviation remarks. b) visibility '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (i) 16.3.6.2 Sector visibilities', // case 9 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (ii) 16.3.6.3 Variable visibility', // case 10 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iii) visibility improving rapidly', // case 11 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iv) 16.3.6.4 Point of observation (visibility from specified location)', // case 12 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (v) 10.2.19.3 Visibility (Remarks) when obscured', // case 13 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vi) Runway Visual Range', // case 14 '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vii) Obscuration affecting visibility', // case 15 */ case 9: // CANADA b) 16.3.13.2.2 (i) Sector Visibility Remarks to complement the Prevailing Visibility or Present Phenomena reported in that Group if($metar_toDecode[0] != 'VIS') break; /* MANOBS: 16.3.6.2 Sector visibilities Sector Visibilities that are either half or less, or double or more, of the prevailing visibility shall be reported in Remarks. Example (1): The prevailing visibility is 15 SM; visibility to the north quadrant is observed to be 3 SM. METAR CYTH 241800Z 13017KT 15SM FEW020 FEW220 15/07 A3011 RMK SF1CI1 VIS N 3 SLP205 Example (2): The prevailing visibility is 3 SM; visibility to the south quadrant is observed to be 6 SM. METAR CYGK 201600Z 11003KT 3SM BR FEW020 08/07 A2948 RMK FG1SC1 VIS S 6 SLP980 MANOBS: 10.2.9.3 For different directions Example where sector visibility combined with obscuring phenomenon: METAR ... no present weather conditions reported ... RMK ... VIS SW 6 BR ... Example where sector visibility related to specified present weather condition: METAR ... VCFG ... RMK ... VIS NE 2 ... Some METAR are malformed, and items appear in wrong order: 2018/01/22 21:49 METAR CYYZ 222149Z 09010G17KT 1/2SM R06R/P6000FT/U R06L/P6000FT/N R05/P6000FT/D FG VV002 01/00 A2984 RMK FG8 VIS 1 SW SLP113 The Sector visibility could involve a fraction e.g. VIS E 1 1/2 so this test may cover either 3 or 4 segments. 2018/02/07 22:34 METAR CYSN 072234Z 32006KT 10SM -SHSN BKN027 M04/M08 A3012 RMK SC7 VIS LWR SW CVCTV CLD EMBD SLP208 2018/01/14 08:00 CYXU 140800Z 25006KT 8SM SHSN OVC033 M12/M15 A3056 RMK SC8 CVCTV CLD ASOCTD SLP372 Went wrong at (0) → RMK   Not de-coded=CVCTV Went wrong at (0) → RMK   Not de-coded=CLD Went wrong at (0) → RMK   Not de-coded=ASOCTD 2018/01/15 23:13 CYSN 152313Z 08007KT 2SM SN VV012 M08/M11 A3037 RMK SN8 VIS VRB 1 1/22 1/2 SLP293 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1/22 Went wrong at (1) → RMK_VIS_VRB   Not de-coded=1/2 2018/01/16 02:00 CYSN 160200Z 10007KT 1SM SN VV008 M07/M08 A3035 RMK SN8 CIG VRB 610 VIS VRB 3/41 1/4 /S01/ LAST STFD OBS/NEXT 161200 UTC SLP285 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=610 Went wrong at (0) → RMK   Not de-coded=VIS Went wrong at (0) → RMK   Not de-coded=VRB Went wrong at (0) → RMK   Not de-coded=3/41 Went wrong at (0) → RMK   Not de-coded=1/4 Went wrong at (0) → RMK   Not de-coded=/S01/ Went wrong at (0) → RMK   Not de-coded=LAST Went wrong at (0) → RMK   Not de-coded=STFD Went wrong at (0) → RMK   Not de-coded=OBS/NEXT Went wrong at (0) → RMK   Not de-coded=161200 Went wrong at (0) → RMK   Not de-coded=UTC 2018/01/12 15:00 CYXU 121500Z 34020G28KT 3SM FZRA PL OVC010 M00/M02 A2975 RMK SF8 CIG RAG SLP082 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=RAG 2018/01/12 14:45 CYXU 121445Z 33015G23KT 3SM FZRA PL OVC010 M00/M01 A2975 RMK SF8 CIG RAG PRESRR SLP082 Went wrong at (0) → RMK   Not de-coded=CIG Went wrong at (0) → RMK   Not de-coded=RAG Went wrong at (0) → RMK   Not de-coded=PRESRR */ if(count($metar_toDecode) > 2 && in_array($metar_toDecode[2], $compass) && $metar_toDecode[1] == 'LWR') { $output = '(r8)= Visibility lower towards ' . $compass_expanded[$metar_toDecode[2]]; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; $decodeInfo['VISIBILITY'] .= ' (r8)'; break; }elseif(count($metar_toDecode) > 2 && strpos($metar_toDecode[2], '-') !== false && $metar_toDecode[1] == 'LWR') { $output = '(r8)= Visibility lower towards ' . $compass_expanded[substr($metar_toDecode[2], 0 , strpos($metar_toDecode[2], '-'))] . ' and ' . $compass_expanded[substr($metar_toDecode[2], 1 + strpos($metar_toDecode[2], '-'))]; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; $decodeInfo['VISIBILITY'] .= ' (r8)'; break; }elseif( (count($metar_toDecode) > 3 && in_array($metar_toDecode[1], $compass) && statuteMiles($metar_toDecode[2], $metar_toDecode[3]) !== false) or (count($metar_toDecode) > 2 && in_array($metar_toDecode[1], $compass) && statuteMiles($metar_toDecode[2], '') !== false) or (count($metar_toDecode) > 2 && in_array($metar_toDecode[2], $compass) && statuteMiles($metar_toDecode[1], '') !== false) or (count($metar_toDecode) > 3 && in_array($metar_toDecode[1], $compass) && statuteMiles($metar_toDecode[2], '') !== false && in_array($metar_toDecode[3], $obscurationTypes)) ) { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing sector VIS", $file); // Diagnostic on condition match if(count($metar_toDecode) > 3 && in_array($metar_toDecode[1], $compass) && statuteMiles($metar_toDecode[2], '') !== false && in_array($metar_toDecode[3], $obscurationTypes)) { $weather = array_search($metar_toDecode[3], $obscurationTypes); $returnV = novel_allVisibility($metar_toDecode[1], $metar_toDecode[2], '', 'WMO RMK ' . $manobs[$specification]); $returnV['segments']++; }elseif(count($metar_toDecode) > 3 && in_array($metar_toDecode[1], $compass) && statuteMiles($metar_toDecode[2], $metar_toDecode[3]) !== false) { // 2018/03/13 23:02 CYSN 132302Z 27010KT 8SM -SHSN SCT013 BKN027 OVC110 M00/M03 A2983 RMK SC4SC3AC1 CVCTV CLD EMBD VIS SW 3 SLP107 $returnV = novel_allVisibility('Sector', $metar_toDecode[2], $metar_toDecode[3], 'WMO RMK ' . $manobs[$specification]); if(array_key_exists('CONDITIONS', $decodeInfo)) { $weather = $decodeInfo['CONDITIONS']; $decodeInfo['CONDITIONS'] .= ' (r8)'; } $output = 'Sector Visibility is ' . $returnV['value'] . ' looking ' . $compass_expanded[$metar_toDecode[1]]; }else{ $returnV = novel_allVisibility($metar_toDecode[2], $metar_toDecode[1], '', 'WMO RMK ' . $manobs[$specification]); } if($show_diagnosticsR) print_array($returnV, 'visibility amount'); if($returnV !== false) { $output = '(r8)= ' . $returnV['prevailVisibility']; if(isset($weather)) $output .= ' associated with ' . $weather; do_output($decodeGroupCount['REMARK'], 2 + $returnV['segments'], $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; $decodeInfo['VISIBILITY'] .= ' (r8)'; } } break; case 10: // CANADA MANOBS: 16.3.6.3 (ii) Variable visibility if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'VIS' and $metar_toDecode[1] == 'VRB') { /* If the prevailing visibility is observed to be fluctuating rapidly and increasing and decreasing from a mean value by 1/4 or more of the mean value, the range of variation shall be entered in Remarks beginning with the lowest visibility value. The mean value shall be entered as the prevailing visibility. Example (1): Prevailing visibility of 1/2 SM is varying between 1/4 and 3/4 statute miles METAR ... 1/2SM FG ... RMK ... VIS VRB 1/4-3/4 ... Example (2): Prevailing visibility of 1 SM is varying between 3/4 and 1 1/4 SM. METAR CYHZ 241800Z 35009KT 1SM BR OVC008 16/14 A2986 RMK SF8 VIS VRB 3/4-1 1/4 SLP112 Example (3): Prevailing visibility of 3 SM is varying between 1 SM and 5 SM. METAR CYTS 251800Z 06010G25KT 3SM BLSN SKC M12/M15 A3041 RMK VIS VRB 1-5 SLP311 Example (4) METAR ... 4SM BR ... RMK ... VIS VRB 2-6 */ optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing VIS VRB ", $file); // Diagnostic on condition match // Some examples that do follow the rules, the figure in the main Prevailing Visibility Group is between the two figures in the range in the Remarks Group: // METAR...1/2SM FG ... RMK... VIS VRB 1/4-3/4 // or METAR...4SM BR ... RMK... VIS VRB 2-6 // 2018/01/22 16:44 CYXU 221644Z 10011KT 3/4SM R15/4000FT/D RA BR BKN002 OVC005 03/02 A2989 RMK FG5SF2NS1 VIS VRB 1/2-1 SLP132 $iCount = 2; $fourth = $third = ""; if(array_key_exists(3, $metar_toDecode)) $third = $metar_toDecode[3]; if(array_key_exists(4, $metar_toDecode)) $fourth = $metar_toDecode[4]; $return1 = novel_allVisibility($metar_toDecode[2], $third, $fourth, $metar_toDecode[1]); if(array_key_exists('VISIBILITY', $decodeInfo)) { $decodeInfo['VISIBILITY'] .= " (r10)"; $output = '(r10)= The prevailing surface visibility has varied between ' . $return1['min'] . ' and ' . $return1['max']; }else $output = 'The prevailing surface visibility has varied between ' . $return1['min'] . ' and ' . $return1['max']; do_output($decodeGroupCount['REMARK'], $return1['count'] + $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; break; } break; case 11: // CANADA MANOBS: '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iii) visibility improving rapidly' // METAR...3/4SM BR ... RMK... VIS IMPRG RPDLY if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'VIS' and $metar_toDecode[1] == 'IMPRG' and $metar_toDecode[2] == 'RPDLY') { if(array_key_exists('VISIBILITY', $decodeInfo)) { $decodeInfo['VISIBILITY'] .= " (r11)"; $output = '(r11)= The prevailing surface visibility is improving rapidly'; }else $output = 'The prevailing surface visibility is improving rapidly'; do_output($decodeGroupCount['REMARK'], $return1['count'] + $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; break; } break; case 12: // CANADA MANOBS: '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (iv) 16.3.6.4 Point of observation visibility from specified location', if(count($metar_toDecode) > 2 and ($metar_toDecode[0] == 'ROOF' || $metar_toDecode[0] == 'TOWER') and $metar_toDecode[1] == 'VIS' and is_numeric($metar_toDecode[2])) { /* When observing visibility from elevated positions, such as a control tower or roof (see 2.6.1) and the visibility differs by a reportable value or more from the prevailing visibility observed on the ground (at eye level), the visibility from the elevated position and the identification of that position shall be reported in Remarks. Example: Prevailing visibility is 3 SM in blowing snow; however, visibility from the roof is observed to be 10 SM. METAR CYXU 251100Z 06015G25KT 3SM BLSN BKN025 M09/M10 A3026 RMK SC6 ROOF VIS 10 SLP248 METAR...1/2SM BLSN ... RMK... TOWER VIS 2 'TOWER VIS 2' = "visibility from specified location is specified distance in statute miles" */ if(array_key_exists('VISIBILITY', $decodeInfo)) { $decodeInfo['VISIBILITY'] .= " (r12)"; $output = '(r11)= The prevailing surface visibility is only applicable at eye level, from the elevated position of the '; }else $output = 'The prevailing surface visibility is only applicable at eye level, from the elevated position of the '; $iCount = 2; $fourth = $third = ""; if(array_key_exists(3, $metar_toDecode)) $third = $metar_toDecode[3]; if(array_key_exists(4, $metar_toDecode)) $fourth = $metar_toDecode[4]; $return1 = novel_allVisibility($metar_toDecode[2], $third, $fourth, $metar_toDecode[0]); $output .= $metar_toDecode[0] . ', the visibility differs and was observed to be '; if($return1['pV_match']) $output .= $return1['prevailVisibility']; do_output($decodeGroupCount['REMARK'], $return1['segments'] + $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; break; } break; case 13: // CANADA MANOBS: '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (v) 10.2.19.3 Visibility (Remarks) when obscured' // 'VIS 100 FT' = "If fog or something similar means that the visibility appears as zero in main Group, a revised value in feet // can be specified in Remarks Group to overwrite the prevailing visibility in the main Group" // METAR... 0SM FG ... RMK... VIS 100 FT if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'VIS' and $metar_toDecode[2] == 'FT' and is_numeric($metar_toDecode[1])) { if(array_key_exists('VISIBILITY', $decodeInfo) and array_key_exists('CONDITIONS', $decodeInfo)) { $output = '(r11)= The prevailing surface visibility of ' . $decodeInfo['VISIBILITY'] . ' is affected by ' . $decodeInfo['CONDITIONS']; $decodeInfo['VISIBILITY'] .= " (r13)"; $decodeInfo['CONDITIONS'] .= " (r13)"; }else $output = 'The prevailing surface visibility is restricted by obscuring features'; $altitudeFT = 1 * $metar_toDecode[1]; $output .= ', but it has been measured as ' . $altitudeFT . ' feet (' . round(($altitudeFT + 0.5)/0.328084, 1) . ' metres)'; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; break; } break; case 14: // CANADA MANOBS: '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vi) Runway Visual Range // 10.2.19.13.6 Sites that are using the Remarks section to transmit RVR shall use only one value of RVR tendency and variations by this method //shall not be reported in Remarks. // Example: METAR 3/8SM RMK RVR RWY 06R 1600FT // = "Runway Visual Range for runway 6 Right is 1600 feet" (seems to be instead of using RVR in main part of METAR if(count($metar_toDecode) > 3 and $metar_toDecode[0] == 'RVR' and $metar_toDecode[1] == 'RWY' and 1 === preg_match('~^([0-9]{2})([LR]?[LRC])?[\_]([PM])?([0-9]{4})([F][T])?$~', $metar_toDecode[2] . '_' . $metar_toDecode[3], $pieces) and substr($metar_toDecode[3],4) == 'FT') { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing runway visibility", $file); // Diagnostic on condition match if(!array_key_exists('RVR', $decodeInfo) ) $decodeInfo['RVR'] = ""; # else $decodeInfo['RVR'] .= " (r14)"; switch($pieces[1]) // Runway code { case 88: $decodeInfo['RVR'] .= 'All Runways '; break; case 99: $decodeInfo['RVR'] .= 'No change from last report '; break; default: $decodeInfo['RVR'] .= 'Runway threshold ' . $pieces[1] . " "; switch($pieces[2]) // specific part of runway { # For parallel runways, 5 codes available to indicate runway that visibility applies to case 'LL': $decodeInfo['RVR'] .= 'Extreme Left Runway '; break; case 'L': $decodeInfo['RVR'] .= 'Left Runway '; break; case 'C': $decodeInfo['RVR'] .= 'Centre Runway '; break; case 'R': $decodeInfo['RVR'] .= 'Right Runway '; break; case 'RR': $decodeInfo['RVR'] .= 'Extreme Right Runway '; break; default: $decodeInfo['RVR'] .= "= "; break; } } $decodeInfo['RVR'] .= '(Touchdown Zone)='; if($show_diagnosticsR) echo "
Matched $pieces[1]"; switch ($pieces[3]){ case 'M': $decodeInfo['RVR'] .= "Visual range less than "; break; case 'P': $decodeInfo['RVR'] .= "Visual range more than "; break; default: $decodeInfo['RVR'] .= "Visual range "; } $distance = 1 * $pieces[4]; $decodeInfo['RVR'] .= number_format($distance); $decodeInfo['RVR'] .= array_key_exists(5, $pieces) ? ' feet (' . round(($distance + 0.5)/0.328084, 1) . ' metres)' : ' metres'; $matched = true; do_output($decodeGroupCount['REMARK'], 4, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 15: // CANADA MANOBS: '2) General aviation remarks. b) 16.3.13.2.2 Visibility Remarks (vii) Obscuration affecting visibility if(in_array($metar_toDecode[0], $obscuring_array)) { /* Other RMK examples VIS VRB 1/4-3/4 VIS VRB 2-6 FG BNK W VIS 5 VIS IMPRG RPDLY VIS 100 FT */ $output = array_search($metar_toDecode[0], $obscuring_array) . ' '; $iCount = 1; if(is_numeric($metar_toDecode[$iCount]) and $metar_toDecode[$iCount + 1] == 'FT' and $metar_toDecode[$iCount + 2] == 'THK') { // METAR...1/4SM FG ... RMK... FG 45 FT THK ROOF VIS 2 $output .= $metar_toDecode[$iCount] . ' feet thick '; $iCount += 3; if($metar_toDecode[$iCount] == 'ROOF') { $output .= 'affecting visibility at tower level and '; $iCount++; }elseif($metar_toDecode[$iCount] == 'TWR') { $output .= 'affecting visibility at roof level and '; $iCount++; } }else{ switch($metar_toDecode[1]) { case 'BANK': $iCount++; // METAR...6SM ... RMK SF8 FG BANK ALQDS // fog bank all quadrants if($metar_toDecode[$iCount] == 'ALQDS') $output .= 'bank affecting all directions (4 quadrants checked) '; elseif(in_array($metar_toDecode[$iCount], $compass)) { // METAR...6SM -RA BR RMK... BCFG SE VIS 1/4 $output .= 'affecting visibility towards ' . $compass_expanded[$metar_toDecode[$iCount]]; if($metar_toDecode[$iCount + 1] == 'QUAD') { // METAR...10SM BCFG ... RMK... BCFG SE QUAD VIS 1/2 $iCount++; $output .= ' quadrant '; }else $output .= ' '; } break; case 'DSIPTG': // METAR...3/4SM BR ... RMK... FG DSIPTG RPDLY $iCount++; if($metar_toDecode[$iCount] == 'RPDLY') { $output .= 'disappearing rapidly'; $iCount++; }else $output .= 'dissipating'; break; case 'DRFTG': $iCount++; if($metar_toDecode[$iCount] == 'OVR') { // METAR...10SM ... RMK... FU DRFTG OVR FLD VIS N 1 $output .= 'drifting over '; $iCount++; if($metar_toDecode[$iCount] == 'FLD') { $output .= 'airfield '; $iCount++; } }else $output .= 'drifting '; case 'OVER': $iCount++; if($metar_toDecode[$iCount] == 'APCH' and $metar_toDecode[$iCount] == 'RWY' and array_key_exists($iCount + 1, $metar_toDecode)) { // METAR...10SM MIFG ... RMK... MIFG OVER APCH RWY 27 $output .= 'over approach to Runway Threshold ' . $metar_toDecode[$iCount + 1]; $iCount .= 3; } break; } } if($metar_toDecode[$iCount] == 'VIS') { // METAR...10SM PRFG ... RMK... FG BANK W VIS 2 $iCount++; } if(array_key_exists($iCount, $metar_toDecode) and array_key_exists($iCount + 1, $metar_toDecode) and statuteMiles($metar_toDecode[$iCount], $metar_toDecode[$iCount + 1])) { $fourth = $third = ""; if(array_key_exists($iCount + 1, $metar_toDecode)) $third = $metar_toDecode[$iCount + 1]; if(array_key_exists($iCount + 2, $metar_toDecode)) $fourth = $metar_toDecode[$iCount + 2]; $return1 = novel_allVisibility($metar_toDecode[$iCount], $third, $fourth, 'RMK_CAN_15'); if($return1['pV_match']) $output .= 'restricting visibility to ' . $return1['prevailVisibility']; $iCount += $return1['segments']; }elseif(array_key_exists($iCount, $metar_toDecode)) { $output .= $metar_toDecode[$iCount] . ' statute miles'; $iCount++; } $matched = true; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; /* Collection 2) General aviation remarks. c) weather '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (i) Present Weather Phenomena affecting aviation', // case 16 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ii) Lightning', // case 17 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iii) Tornado, and directions', // case 18 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iv) Qualifying precipitation - intermittent' // case 19 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (v) Qualifying precipitation - wet', // case 20 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (vi) Qualifying precipitation - occasional', // case 21 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (vii) precipitation not reaching ground', // case 22 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (viii) HAIL', // case 23 '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ix) Light precipitation', // case 24 */ case 16: // CANADA MANOBS: '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (i) Present Weather Phenomena affecting aviation' $return1 = novel_decode_phenomena($metar_toDecode[0], 'Rem_CAN_16'); if(!array_key_exists(0, $return1) or is_null($return1[0])) break; $output = $return1[0]; $matched = true; $iCount = $return1['segment']; if(count($metar_toDecode) > $iCount and in_array($metar_toDecode[$iCount], $compass)) { $output .= 'affecting visibility towards ' . $compass_expanded[$metar_toDecode[$iCount]]; if(count($metar_toDecode) > 1 + $iCount and $metar_toDecode[$iCount + 1] == 'QUAD') { $iCount++; $output .= ' quadrant '; }; $iCount++; }elseif(count($metar_toDecode) > $iCount and strpos($metar_toDecode[$iCount], '-') > 0 and in_array(substr($metar_toDecode[$iCount], 0, strpos($metar_toDecode[$iCount], '-')), $compass) and in_array(substr($metar_toDecode[$iCount], 1 + strpos($metar_toDecode[$iCount], '-')), $compass)) { // METAR...1/2SM FG ... RMK... PRFG SE-N // 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 $output .= ' phenomena affecting visibility clockwise from ' . $compass_expanded[substr($metar_toDecode[$iCount], 0, strpos($metar_toDecode[$iCount], '-'))]; $output .= ' to ' . $compass_expanded[substr($metar_toDecode[$iCount], 1 + strpos($metar_toDecode[$iCount], '-'))]; $iCount++; } do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; case 17: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ii) Lightning' if(count($metar_toDecode) > 2 and ($metar_toDecode[0] == 'OCNL' || $metar_toDecode[0] == 'FRQ' || $metar_toDecode[0] == 'CONTUS') ) { $iCount = 1; $output = ' Lightning '; if($metar_toDecode[1] == 'DIST') { // 'OCNL DIST LTGCC SW' $iCount++; $output .= '(seen beyond 10 statute miles from observation point) '; } $output .= '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; } $output .= 'per minute; '; if(($metar_toDecode[$iCount] == 'LTGCG' || $metar_toDecode[$iCount] == 'LTGIC' || $metar_toDecode[$iCount] == 'LTGCC') and in_array($metar_toDecode[$iCount + 1], $compass)) { /* 'OCNL LTGIC SW' = "Lightning details similar to USA - 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" */ switch(substr($metar_toDecode[$iCount],3)) { case 'CG': $output .= '(strike between cloud and ground) '; break; case 'IC': $output .= '(strike within cloud) '; break; case 'CC': $output .= '(strike between one cloud and another cloud) '; break; case 'CA': $output .= '(strike between cloud and air without reaching ground) '; break; } $output .= 'seen towards ' . $compass_expanded[$metar_toDecode[$iCount + 1]]; $iCount += 2; $matched = true; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } }elseif(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'LTNG' and $metar_toDecode[1] == 'SIST' and $metar_toDecode[2] == 'ALQDS') { /* 2018/01/23 04:01 CYCK 230401Z AUTO 24011KT 4SM TSRA BR SCT023 BKN030 BKN040 OVC055 08/07 A2955 RMK LTNG DIST ALQDS PRESRR SLP012 Went wrong at (0) → RMK   Not de-coded=LTNG Went wrong at (0) → RMK   Not de-coded=DIST Went wrong at (0) → RMK   Not de-coded=ALQDS # ALQDS All Quadrants */ $output = 'Lightning is seen in all directions, but far away'; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 18: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iii) Tornado, and directions' # (i) 'TORNADO SW MOVG E' = "spelt out cloud phenomena, its current direction with respect to observer and the direction it is moving to" /* METAR...OVC030CB 7SM +FC ... RMK... TORNADO SW MOVG E METAR...BKN035TCU 8SM SHRA ... RMK... FUNNEL CLOUD REPD 1435Z 15 S (1) MOVG NE (1) Note (1): If a tornado, waterspout or funnel cloud is reported by the public, indicate: (i) the location with respect to the station, city or town; (ii) the direction towards which it is moving; and (iii) the time the phenomenon was observed. Other RMK examples TORNADO SW MOVG E FUNNEL CLOUD REPD 15 S MOVG NE 0830 TS MOVG N METAR ... -RA ... RMK ... -RA INTMT ... METAR ... no present weather ... RMK ... INTMT -RA ... OCNL -SHRA METAR ... SN ... RMK ... SN WET ... METAR ... DRSN ... RMK ... VIRGA N ... METAR ... CLR ... RMK ... FROIN ... */ if(count($metar_toDecode) > 3 and $metar_toDecode[0] == "TORNADO" and in_array($metar_toDecode[1], $compass) and $metar_toDecode[2] == "MOVG" and in_array($metar_toDecode[3], $compass)) { $output = "Tornado observed looking " . $compass_expanded[$metar_toDecode[1]] . " moving towards " . $compass_expanded[$metar_toDecode[3]]; $matched = true; do_output($decodeGroupCount['REMARK'], 4, $output, $manobs[$specification] . " (i) Tornado"); $decodeGroupCount['Canada']++; break; } break; case 19: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iv) Qualifying precipitation - intermittent' # (ii) '-RA INTMT' or 'INTMT -SN' = "repeats precipitation report in main Group and qualifies it as being intermittent before report issued (not raining at time of report)" if(count($metar_toDecode) > 1 and ($metar_toDecode[0] == "INTMT" || $metar_toDecode[1] == "INTMT") ) { //2017/11/12 13:34 CYQG 121334Z 17002KT 10SM FEW043 OVC100 02/M03 A3034 RMK NS2AC6 INTMT -SN SLP282 if(array_key_exists('CONDITIONS', $decodeGroupProcessed)) { $output = "(r7) = Reported precipitation was intermittent before report."; $decodeGroupProcessed['CONDITIONS'] .= " (r7)"; }else{ if($metar_toDecode[0] == "INTMT") { // '-RA INTMT' or 'INTMT -SN' = "repeats precipitation report in main Group and qualifies it as being intermittent before report issued (not raining at time of report)" if($show_diagnostics or $show_diagnosticsR) echo "
" . $metar_toDecode[1] . " applied to decode sub-function 'novel_decode_phenomena'"; $input = novel_decode_phenomena($metar_toDecode[1],'remark'); }else{ if($show_diagnostics or $show_diagnosticsR) echo "
" . $metar_toDecode[0] . " applied to decode sub-function 'novel_decode_phenomena'"; $input = novel_decode_phenomena($metar_toDecode[0],'remark'); } if(array_key_exists(0, $input)) { $output = "Intermittient precipitation of type " . $input[0] . " has now stopped."; }else $output = "Intermittient precipitation has now stopped."; } $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification] . " (ii) INTMT"); $decodeGroupCount['Canada']++; if(count($metar_toDecode) < 1) break; break; } break; case 20: // CANADA MANOBS: '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (v) Qualifying precipitation - wet // METAR...VV007 1/2SM SN ... RMK... SN WET if(count($metar_toDecode) > 1 and $metar_toDecode[0] == "SN" and $metar_toDecode[1] == "WET") { $output = "(r20) = reported precipitation was wet snow"; if(array_key_exists('CONDITIONS', $decodeGroupProcessed)) $decodeGroupProcessed['CONDITIONS'] .= " (r21)"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification] . " (v) wet snow"); $decodeGroupCount['Canada']++; break; } break; case 21: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (iv) Qualifying precipitation - occasional' # (iv) 'OCNL SHRA' = "occasional qualifier to precipitation specified in main Group indicating it was in 15 minutes before report issued" if(count($metar_toDecode) > 1 and $metar_toDecode[0] == "OCNL" ) { // METAR...SCT035 15SM SHRA ... RMK... OCNL -SHRA (5) // Note (5): Rain showers were not occurring at the time but were active within 15 minutes preceding the time of the observation. // 2017/11/20 02:00 METAR CYSN 200200Z 29013G21KT 15SM BKN033 OVC070 01/M05 A2984 RMK SC6AC2 OCNL -SHSN LAST STFD OBS/NEXT 201200 UTC SLP109 // Went wrong at (0) → RMK   Not de-coded=OCNL # Now validate $metar_toDecode[1] $return = novel_decode_phenomena($metar_toDecode[1], $manobs[$specification]); if(array_key_exists(0, $return) and strlen($return[0]) > 1) { $output = '(r21) = reported ' . $return[0] . ' was during 15 minutes before report, now stopped.'; if(array_key_exists('CONDITIONS', $decodeGroupProcessed)) $decodeGroupProcessed['CONDITIONS'] .= " (r21)"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification] . " (iv) OCNL"); $decodeGroupCount['Canada']++; break; } } break; case 22: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (vii) precipitation not reaching ground' # (v) 'VIRGA N' = "direction of Wisps of precipitation evaporating before reaching the ground" if(count($metar_toDecode) > 1 and $metar_toDecode[0] == "VIRGA" and in_array($metar_toDecode[1], $compass)) { $output = "Wisps of precipitation evaporating before reaching the ground seen looking " . $compass_expanded[$metar_toDecode[1]]; $matched = true; do_output($decodeGroupCount['REMARK'], 2,$output, $manobs[$specification] . " (v) directional VIRGA"); $decodeGroupCount['Canada']++; break; }elseif($metar_toDecode[0] == "VIRGA") { $output = "Wisps of precipitation evaporating before reaching the ground"; $matched = true; do_output($decodeGroupCount['REMARK'], 1,$output, $manobs[$specification] . " (v) VIRGA"); $decodeGroupCount['Canada']++; break; } break; case 23 : // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (viii) HAIL' # (vi) 'HAIL DIAM nn MM' = "Diameter of hailstones rounded to nearest mm" if(count($metar_toDecode) > 3 and $metar_toDecode[0] == "HAIL" and $metar_toDecode[1] == "DIAM" and is_numeric($metar_toDecode[2]) and $metar_toDecode[3] == "MM") { $output = "Hail diameter is " . $metar_toDecode[2] . " mm"; $matched = true; do_output($decodeGroupCount['REMARK'], 4, $output, $manobs[$specification] . " (vi) HAIL"); $decodeGroupCount['Canada']++; break; } break; case 24: // CANADA '2) General aviation remarks. c) 16.3.13.2.3 Weather Remarks (ix) Light precipitation' if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'PCPN' and $metar_toDecode[1] == 'VRY' and $metar_toDecode[2] == 'LGT') # PCPN VRY LGT // PCPN VRY LGT an example 2017/11/13 20:16 METAR CYXU 132016Z 29005KT 5SM -DZ BR OVC026 03/01 A3037 RMK SC8 PCPN VRY LGT SLP297 { optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing very light precipitation"); // Diagnostic on condition match $output = "Very light precipitation observed"; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; /* // 2) General aviation remarks. d) Sky condition '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (i) sky cover', // case 25 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (ii) sky ceiling', // case 26 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (iii) convective clouds', // case 27 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (iv) cloud tops', // case 28 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (v) orographic clouds', // case 29 '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (vi) condensation trails', // case 30 */ case 25: // CANADA '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (i) sky cover' // - Entry in Remarks Group extends information for entry in main Cloud Layer Group if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'SUN' and $metar_toDecode[1] == 'DMLY' and $metar_toDecode[2] == 'VSBL') { // 2017/12/04 20:00 METAR CYHM 042000Z 13009KT 15SM BKN098 OVC240 09/02 A3004 RMK AC6CS2 SUN DMLY VSBL SLP182 $output = "The sun is dimly visible through the cloud cover"; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif($metar_toDecode[0] == 'HALO') { // 2017/11/09 09:00 CYXU 090900Z 22005KT 15SM OVC250 00/M02 A3010 RMK CS8 HALO SLP204 // 2017/11/09 09:00 CYQG 090900Z 23006KT 10SM BKN250 01/M01 A3014 RMK CS7 HALO SLP212 // 2018/01/29 07:00 METAR CYYZ 290700Z 36007KT 15SM SCT030 BKN230 M03/M08 A3032 RMK SC3CS2 HALO SLP278 $output = "Observed a circular band of coloured light around the sun or moon, caused by the refraction and reflection of light "; $output .= "by ice particles suspended in the intervening atmosphere"; $matched = true; do_output($decodeGroupCount['REMARK'], 1, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'MOON' and $metar_toDecode[1] == 'VSBL') { // 2018/02/04 08:00 METAR CYHM 040800Z 22016G21KT 15SM DRSN OVC050 00/M08 A2981 RMK SC8 MOON VSBL SLP106 $output = "The moon is visible through the cloud cover"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 26: // d) 16.3.13.2.4 Sky condition Remarks (ii) sky ceiling // Aircraft reported sky ceiling if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'ACFT' and $metar_toDecode[1] == 'REPD' and $metar_toDecode[2] == 'CIG') { if(show_diagnosticsR) echo '
Canadian remark recognized about aircraft reporting sky ceiling'; // e.g. 'ACFT REPD CIG' // METAR BK N008 RMK ACFT REPD CIG if(array_key_exists('CEILING', $decodeInfo)) { $output = "(r26) = The quoted cloud ceiling was derived from an aircraft report"; $decodeInfo['CEILING'] .= " (r26)"; }else $output = "An aircraft report on the cloud ceiling has been received"; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } // Balloon giving indication of sky ceiling if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'BLN') { # BLN = Balloon if(show_diagnosticsR) echo '
Canadian remark recognized about balloon'; $output = 'Balloon '; if($metar_toDecode[1] == 'ESTD') { // BLN ESTD if(array_key_exists('CEILING', $decodeInfo)) { $output = "(r26) = The quoted cloud ceiling was estimated from a balloon ascent"; $decodeInfo['CEILING'] .= " (r26)"; }else $output .= "ascent used to estimate the cloud ceiling"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif(count($metar_toDecode) > 3 and $metar_toDecode[1] == 'DSAPRD' and is_numeric($metar_toDecode[2]) and $metar_toDecode[3] == 'FT') { // BLN DSAPRD number FT // METAR OVC004 RMK BLN DSAPRD 550 FT $altitudeFT = 1 * $metar_toDecode[2]; $output .= 'disappeared at ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } } // Sky Ceiling qualified if(count($metar_toDecode) > 1 and $metar_toDecode[0] == 'CIG') { if(show_diagnosticsR) echo '
Canadian remark recognized about sky ceiling'; if(count($metar_toDecode) > 2) { if($metar_toDecode[1] == 'LWR' and in_array($metar_toDecode[2], $compass)) { // METAR ... OVC006 ... RMK ... CIG LWR SE if(array_key_exists('CEILING', $decodeInfo)) { $output = '(r26) = The cloud ceiling is lower in the ' . $compass_expanded[$metar_toDecode[2]]; $decodeInfo['CEILING'] .= ' (r26)'; }else $output = 'The cloud ceiling is lower in the ' . $compass_expanded[$metar_toDecode[2]]; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif(is_numeric($metar_toDecode[1]) and $metar_toDecode[2] == 'FT') { // METAR ... OVC000 ... RMK ... CIG 35 FT $altitudeFT = 1 * $metar_toDecode[1]; if(array_key_exists('CEILING', $decodeInfo)) { $output = '(r26) = The cloud ceiling is ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $decodeInfo['CEILING'] .= ' (r26)'; }else $output = 'The cloud ceiling is ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif($metar_toDecode[1] == 'VRB') { if(is_numeric($metar_toDecode[2])) { // METAR ... OVC002 ... RMK ... CIG VRB 13 $altitudeFT = 100 * $metar_toDecode[2]; if(array_key_exists('CEILING', $decodeInfo)) { $output = '(r26) = The cloud ceiling is variable to ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $decodeInfo['CEILING'] .= ' (r26)'; }else $output = 'The cloud ceiling is variable to ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif(strpos($metar_toDecode[2], '-') > 1) { /* 2017/11/16 02:00 METAR CYXU 160200Z 21015G20KT 10SM -RA OVC007 07/06 A2965 RMK SF8 CIG VRB 5-9 SLP050 METAR OVC007 RMK CIG VRB 5-9 (coded for below) METAR VV002 RMK CIG VRB 1-3 CIG VRB 3-5 */ $output = "Ceiling height varying between " . 100 * substr($metar_toDecode[2], 0, strpos($metar_toDecode[2], '-') - 1) . " and " . 100 * substr($metar_toDecode[2], strpos($metar_toDecode[2], '-') + 1) . " feet"; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification] . ' variable cloud ceiling'); $decodeGroupCount['Canada']++; if($show_diagnostics or $show_diagnosticsR) { echo "
Decoding count = " . $decodeGroupCount['REMARK'] . " with sub-function = decode_CAN_remarks "; echo "matched " . $decodeGroupProcessed['REMARK'][$decodeGroupCount['REMARK']] . " against variable cloud ceiling specification
"; } $matched = true; $decodeGroupCount['REMARK'] ++; break; } }elseif($metar_toDecode[1] == 'DFUS') // DFUS Diffuse { // METAR ... OVC003 ... RMK ... CIG DFUS VERT VIS 5 if(count($metar_toDecode) > 4 and $metar_toDecode[2] == 'VERT' and $metar_toDecode[3] == 'VIS' and is_numeric($metar_toDecode[4])) { $altitudeFT = 100 * $metar_toDecode[4]; if(array_key_exists('CEILING', $decodeInfo)) { $output = '(r26) = The cloud ceiling is diffuse with vertical visibility of ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $decodeInfo['CEILING'] .= ' (r26)'; }else $output = 'The cloud ceiling is diffuse with vertical visibility of ' . $altitudeFT . ' feet (' . 10 * round(($altitudeFT + 50)/32.8084) . ' metres)'; $matched = true; do_output($decodeGroupCount['REMARK'], 5, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } } }elseif($metar_toDecode[1] == 'RAG') // RAG Ragged { // 2018/02/24 04:00 METAR CYYZ 240400Z 28009G15KT 15SM -DZ OVC016 06/04 A3011 RMK ST8 CIG RAG SLP204if(array_key_exists('CEILING', $decodeInfo)) if(array_key_exists('CEILING', $decodeInfo)) { $output = '(r26) = The cloud ceiling is ragged'; $decodeInfo['CEILING'] .= ' (r26)'; }else $output = 'The cloud ceiling is ragged'; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } } break; case 27: // CANADA General '1) a) 16.3.13 Layer information' 16.3.13.2.4 d) Sky condition Remarks (iii) convective clouds /* If clouds which indicate unstable conditions (CB, TCU or ACC) are observed they shall be reported in Remarks. METAR BKN100 RMK AC6 ACC W METAR FEW 040 RMK SC1 CB TOPS NW (*) *Note: If TCU or CB clouds base are not observed and not reported in the sky condition, they shall be reported in Remarks METAR SCT030CB RMK CB4 CB MOVG RPDLY FM SE */ if(count($metar_toDecode) > 2 and $metar_toDecode[0] == "CVCTV" and $metar_toDecode[1] == 'CLD' and $metar_toDecode[2] == 'EMBD') { /* Report of how failed in a previous version 2017/11/25 10:00 METAR CYXU 251000Z 24015G21KT 15SM -SHRA OVC065 08/01 A2952 RMK SC8 CVCTC CLD ASOCTD SLP004 Went wrong at (1) → RMK_CVCTC   Not de-coded=CLD --> CVCTC is not defined in manual Went wrong at (2) → RMK_CLD   Not de-coded=ASOCTD CLOUD and ASSOCIATED are defined in manual */ // Example: 2017/11/21 22:45 METAR CYXU 212245Z 24012KT 15SM -SHRA FEW034 BKN038 OVC049 07/01 A2980 RMK SC1SC5SC2 CVCTV CLD EMBD SLP099 $output = "Convective Cloud Embedded"; // CVCTV = Convective - That abbreviation is defined in manual - guessing rest is connected, something about embedded cloud? $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif(count($metar_toDecode) > 1 and $metar_toDecode[0] == "CVCTC" and $metar_toDecode[1] == 'CLD') { $output = "Convective Cloud"; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 28: // CANADA General '2) General aviation remarks. d) 16.3.13.2.4 Sky condition Remarks (iv) cloud tops' if($metar_toDecode[0] == 'ASOCTD') { /* 2017/12/06 14:00 METAR CYXU 061400Z 23015G21KT 15SM FEW030 M02/M09 A2984 RMK SC1 SC TR CU ASOCTD TOPS SE AND NW QUAD SLP119 Went wrong at (3) → RMK_CU   Not de-coded=ASOCTD Went wrong at (4) → RMK_ASOCTD   Not de-coded=TOPS Went wrong at (5) → RMK_TOPS   Not de-coded=SE Went wrong at (6) → RMK_SE   Not de-coded=AND Went wrong at (7) → RMK_AND   Not de-coded=NW Went wrong at (8) → RMK_NW   Not de-coded=QUAD */ $iCount = 1; $output = "Previous named cloud is associated with others already reported"; if(count($metar_toDecode) > 2 and $metar_toDecode[1] == "TOPS" and in_array($metar_toDecode[2],$compass)) { $iCount = 3; $output .= "; associated with tops in " . $compass_expanded[$metar_toDecode[2]]; if(count($metar_toDecode) > 4 and $metar_toDecode[3] == "AND" and in_array($metar_toDecode[4],$compass) and $metar_toDecode[5] == "QUAD") { $iCount = 6; $output .= " and " . $compass_expanded[$metar_toDecode[4]] . " quadrants"; }else $output .= " direction"; } $matched = true; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 29: // CANADA General '1) a) 16.3.13 Layer information' 16.3.13.2.4 d) Sky condition Remarks (v) orographic (or standing wave) clouds if($metar_toDecode[0] == 'ROTOR' and $metar_toDecode[1] == 'CLDS' and in_array($metar_toDecode[2], $compass)) { // METAR SCT060TCU RMK TCU3 ROTOR CLDS NW $output = "Rotating cloud seen to " . $compass_expanded[$metar_toDecode[2]]; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; }elseif($metar_toDecode[0] == 'ACSL' and $metar_toDecode[1] == 'OVR') { /* Orographic clouds When observed, orographic clouds, also known as standing wave clouds, shall be reported in Remarks whether or not the clouds are predominant. These clouds sometimes indicate severe turbulence aloft and are normally seen in areas up to 350 km to the leeward of mountains or hills and may occur for a period of 5 or 6 hours or longer. (*) METAR ...SCT040 SCT090 RMK SC3AC1 ACSL OVR RDG NW *Note: ACSL indicates standing lenticular alto-cumulus. */ if($metar_toDecode[2] == 'RDG' and in_array($metar_toDecode[3], $compass)) { // e.g. 'SC3AC1 ACSL OVR RDG NW' = "ACSL indicates standing lenticular alto-cumulus, RDG means ridge and its direction from observer", $output = "Standing Lenticular alto-cumulus Cloud over ridge to " . $compass_expanded[$metar_toDecode[3]]; $matched = true; do_output($decodeGroupCount['REMARK'], 4, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } }elseif($metar_toDecode[0] == 'RDG' and $metar_toDecode[1] == 'LENTICULARS') { // RDG LENTICULARS IN LYRS W if($metar_toDecode[2] == 'IN' and $metar_toDecode[3] == 'LYRS' and in_array($metar_toDecode[4], $compass)) { $output = "Ridge of Standing Lenticular cloud in layers to " . $compass_expanded[$metar_toDecode[4]]; $matched = true; do_output($decodeGroupCount['REMARK'], 5, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } } break; case 30: // CANADA General '1) a) 16.3.13 Layer information' 16.3.13.2.4 d) Sky condition Remarks (v) condensation trails if(substr($metar_toDecode[0], 0, 7) == "CONTRAI") // NB using substring as seen all the 3 versions being used as in examples below { /* METAR FEW 250 RMK CONTRAILS e.g. 'CONTRAILS' = "aircraft condensation trails are persisting for at least 15 minutes" Note: Shall be used when middle (CM) or high (CH) cloud consists in whole or in part of persistent (15 minutes or more) condensation trails. Rapidly dissipating condensation trails shall not be reported. 2017/11/14 18:00 METAR CYQG 141800Z 27007KT 210V290 10SM SCT027 BKN260 07/01 A3031 RMK CU3CI4 CONTRAILS SLP270 2017/12/01 21:00 METAR CYQG 012100Z 20011KT 15SM BKN320 07/M00 A3023 RMK CI7 CONTRAIL SLP243 2018/01/20 19:00 CYQG 201900Z 21012KT 15SM BKN270 05/M01 A2992 RMK CI7 CONTRAI Suspect in last, truncation of the complete METAR may have occurred. */ /* // No simple way identified to code for all these possibilities, so some are identified separately, others are ignored currently // 10.2.19.1 Sky cover (Remarks) “CONTRAILS” shall be used when the CM or CH cloud consists in whole or in part of persistent (15 minutes or more) condensation trails. // Rapidly dissipating condensation trails shall not be reported. // AC XTNDG RPDLY FM SW // OVC TPG HILLS NE // CONTRAILS // SC MOVG RPDLY FM SE e.g. METAR BKN070 RMK AC XTNDG RPDLY FM SW 'AC XTNDG RPDLY FM SW' = "type of cloud extending rapidly from a compass direction" e.g. METAR OVC007 RMK OVC TPG HILLS NE 'OVC TPG HILLS NE' = "cloud cover topping hills in particular direction" */ $output = "(r29) = Highest reported cloud layer either completely, or partly, consists of persisting (for 15 minutes or more) aircraft condensation trail(s)"; $matched = true; do_output($decodeGroupCount['REMARK'], 1, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; 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] .= " (r29)"; elseif(array_key_exists(1, $decodeInfo['SKY-DETAILS'])) $decodeInfo['SKY-DETAILS'][1][0] .= " (r29)"; else $decodeInfo['SKY-DETAILS'][0][0] .= " (r29)"; # print_array($decodeInfo['SKY-DETAILS'], "sky-details"); } break; } break; /* Collection 2) General aviation remarks. e) Precipitation '2) General aviation remarks. e) 16.3.13.2.6 Snowfall Remarks', // case 30 '2) General aviation remarks. e) 16.3.13.2.7 Rainfall Remarks', // case 31 '2) General aviation remarks. e) 16.3.13.2.8 Snowfall (or rainfall) reporting procedures for part-time stations', // case 32 */ case 31: // CANADA '2) f) 16.3.13.2.6 Snowfall Remarks', /* f) 16.3.13.2.6 Snowfall Remarks (part-time, i.e. daily but not all 24 hours, stations only) # DONE Example: Observer reports 1 cm of newly fallen snow since the last main synoptic report. METAR CYYQ 121500Z 30006KT 15SM -SN OVC007 M12/M14 A2956 RMK SC8 /S01/ SLP012 Montréal Mirabel International Airport|Montreal, Quebec, Canada===== Remarks Group with precise cloud, distant shower of snow, sea level pressure 2017/10/26 16:00 CYMX 261600Z 21004KT 180V250 25SM FEW070 BKN110 OVC200 11/06 A2976 RMK AC1AC6CI DIST SH S SLP082 */ if(substr($metar_toDecode[0] , 0, 2) == '/S' and substr($metar_toDecode[0] ,-1) == '/' and is_numeric(substr($metar_toDecode[0] , 2, 2))) { if(array_key_exists('CONDITIONS', $decodeInfo)) { $output = "(r30) = Since the last precipitation report, at least 1 cm of newly fallen snow has brought the accumulated depth to " . 1 * substr($metar_toDecode[0] , 2, 2) . " cm"; $decodeInfo['CONDITIONS'] .= " (r30)"; }else $output = "Since the last precipitation report, more than 1 cm of newly fallen snow has brought the accumulated depth to " . substr($metar_toDecode[0] , 2, 2) . " cm"; $matched = true; do_output($decodeGroupCount['REMARK'], 1, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'DIST' and ($metar_toDecode[1] == 'SH' && $metar_toDecode[2] == 'S') || $metar_toDecode[1] == 'S' ) { $iCount = 2; if(array_key_exists('CONDITIONS', $decodeInfo)) { $output = "(r30) = Snowfall observed beyond 10 statute miles from observation point" ; } else $output = "Snowfall observed beyond 10 statute miles from observation point" ; if($metar_toDecode[1] == 'SH') { $iCount = 3; if(array_key_exists('CONDITIONS', $decodeInfo)) { $output = "(r30) = Snowfall shower observed beyond 10 statute miles from observation point" ; } else $output = "Snowfall shower observed beyond 10 statute miles from observation point" ; } $matched = true; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; if(array_key_exists('CONDITIONS', $decodeInfo)) { $decodeInfo['CONDITIONS'] .= " (r30)"; } break; } break; case 32: // CANADA 2) f) 16.3.13.2.7 Rainfall remarks /* f) 16.3.13.2.7 Rainfall remarks (part-time, i.e. daily but not all 24 hours, stations only) # DONE All sites equipped with a recording rain gauge, the standard type B rain gauge or the AWOS precipitation gauge shall report the accumulated rainfall, since the time of the last main synoptic report, in the Remarks section of the METAR observation by means of an /Rrr/ group. The letter "R" identifies the precipitation as rain and the "rr" the units in whole millimetres. /Rrr/ is the accumulative rainfall, rounded off to the nearest whole millimetre. 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 $ 2018/01/11 00:00 METAR CYKF 110000Z AUTO 17006KT 1 1/4SM RA BR OVC002 02/02 A2998 RMK PCPN 08MM PAST HR SLP169 */ if($metar_toDecode[0] == "PCPN") { if($show_diagnosticsR) echo '
Recognized ' . $metar_toDecode[0]; if(count($metar_toDecode) > 2 and $metar_toDecode[1] == "VRY" and $metar_toDecode[2] == "LGT" ) { if(array_key_exists('CONDITIONS', $decodeInfo)) { $output = "(r31) = Observed precipitation is very light."; }else $output = "Observed precipitation is very light."; $matched = true; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; if(isset($decodeInfo['CONDITIONS'])) $decodeInfo['CONDITIONS'] .= " (r31)"; break; }elseif(count($metar_toDecode) > 3 and substr($metar_toDecode[1], -1) == 'M' and $metar_toDecode[2] == "PAST" and $metar_toDecode[3] == "HR" ) { if(array_key_exists('CONDITIONS', $decodeInfo)) { $output = '(r31) = Observed precipitation in last hour ' . (1 * substr($metar_toDecode[1], 0, -2)); }else $output = 'Observed precipitation in last hour ' . (1 * substr($metar_toDecode[1], 0, -2)); if(substr($metar_toDecode[1], -2) == 'MM') $output .= ' mm'; if(substr($metar_toDecode[1], -2) == 'CM') $output .= ' cm'; $matched = true; do_output($decodeGroupCount['REMARK'], 4, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; if(isset($decodeInfo['CONDITIONS'])) $decodeInfo['CONDITIONS'] .= " (r31)"; break; } }elseif( substr($metar_toDecode[0] , 0, 2) == '/R' and substr($metar_toDecode[0] ,-1) == '/' and is_numeric(substr($metar_toDecode[0] , 2, 2))) { $output = "Since the last precipitation report, at least 10 mm of extra rain has fallen, giving an accumulated rainfall total of " . 1 * substr($metar_toDecode[0] , 2, 2) . " mm"; $matched = true; do_output($decodeGroupCount['REMARK'], 1, $output, $manobs[$specification]); break; }elseif(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'DIST' and ($metar_toDecode[1] == 'SH' && $metar_toDecode[2] == 'R') || $metar_toDecode[1] == 'R' ) { $iCount = 2; if(array_key_exists('CONDITIONS', $decodeInfo)) { $decodeInfo['CONDITIONS'] .= " (r31)"; $output = "(r31)= Rainfall observed beyond 10 statute miles from observation point"; }else $output = " 3 and (substr($metar_toDecode[0], 1, 1) =='R' or substr($metar_toDecode[0], 1, 1) =='S') // corrected 10 Feb 2018 and is_numeric(substr($metar_toDecode[0],2)) and $metar_toDecode[1] =='AFT') // corrected 10 Feb 2018 { $iCount = 2; optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing " . $manobs[$specification] . ' segments ' . $metar_toDecode[0] . ', ' . $metar_toDecode[1], $file); // Diagnostic on condition match $output = 1 * substr($metar_toDecode[0], 2); // corrected 10 Feb 2018 $output .= substr($metar_toDecode[0], 0, 1) =='R' ? ' mm of rainfall ' : ' cm of non-melted snow depth '; /* 16.3.13.2.8 Snowfall (or rainfall) reporting procedures for part-time stations Part-time stations are defined as operating daily but fewer than 24 hours. When the station reopens, the first observation will indicate the reportable snowfall (or rainfall) amount, using format a) /Sss AFT HH/ (or /Rrr AFT HH/), where either 'S' is mandatory, and 'ss' is non-melted depth rounded to integer cm, or 'R' is mandatory and precedes quantity rr of rain collected in mm, AFT means the measurement started at, HH is the time (UTC, on the hour) of the main synoptic at or prior to the time of closing. If the closed period includes a main synoptic hour, and if the reopening time is at a non- synoptic hour, the second and subsequent snow (or rain) reports will take format b) /Sss AFT HHUTC/, where the flag indicates that HH UTC is the time the station reopened and ss is the snowfall (or rainfall) amount since the station reopened. Note that for rainfall, Sss is replaced by Rrr. Example (1): Opening time 1400 UTC (opening can be from 13 UTC to 17 UTC) First report, use the format: /Sss AFT 00UTC/ Subsequent reports, up to and including 18 UTC, use format c): /Sss AFT 14UTC/ Example (2): Opening time 0000 UTC First report, use the format: /Sss AFT 06UTC/ Subsequent reports, use conventional format: /Sss/ Example (3): Opening time 0900 UTC (opening can be from 07 UTC to 11 UTC) First report, use the format: /Sss AFT 00UTC/ Subsequent reports, up to and including 12 UTC, use format c): /Ss s AFT 09UTC/ In summary, the optional'UTC' means that the time quoted is when the station re-opened, without 'UTC', the time since is either when the station closed or a time during the previous open period. */ optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing " . $manobs[$specification] . ' segment ' . $metar_toDecode[2], $file); // Diagnostic on condition match if(is_numeric(substr($metar_toDecode[2], 0, 2)) and substr($metar_toDecode[2], -1) == '/') // format a) /Sss AFT HH/ (or /Rrr AFT HH/) { $timeArray = adjustTime(substr($metar_toDecode[2], 0, 2) . "00"); }elseif(is_numeric(substr($metar_toDecode[2], 0, 2)) and substr($metar_toDecode[2], 2) == 'UTC/') // format b) /Sss AFT HHUTC/ { $timeArray = adjustTime(substr($metar_toDecode[2], 0, 2) . "00"); }elseif(is_numeric($metar_toDecode[2]) and $metar_toDecode[3] == 'UTC/') // allowing for format where extra space inserted after hour { // 2018/02/10 12:00 METAR CYSN 101200Z 22002KT 3/4SM -SN BKN005 OVC030 M03/M04 A3015 RMK SN4SF2SC2 /S02 AFT 00 UTC/ SLP217 $timeArray = adjustTime($metar_toDecode[2] . "00"); $iCount++; } $output .= "reported since " . $timeArray['hour']; if(strlen($metar_toDecode[2]) == 2) { $output .= " hours (local time during previous period of station operation)"; }else{ $output .= " hours (local time when started this period of station operation)"; } do_output($decodeGroupCount['REMARK'], $iCount + 1, $output, $manobs[$specification]); $matched = true; $decodeGroupCount['Canada']++; $decodeGroupCount['REMARK'] ++; break; } break; /* // 2) General aviation remarks. f) Reporting status '2) General aviation remarks. f) 16.3.13.3 Late weather observations', // case 33 '2) General aviation remarks. g) 16.3.13.4 Observational program status', // case 34 "Extra for volcanoes etc", // case 35 "Density altitude", // case 36 - appears after SLP entry, this item is not mentioned in MANOBS, but frequently is real final segment in Remarks Group for Canada // after that reported as not decoded 'VIA', // case 37 */ case 34: // CANADA 16.3.13.3 Late weather observations /* The number of minutes before/after the hour that the observation has been taken shall be entered as the first of the general weather Remarks. The format of the remark shall be, OBS TAKEN ±tt , where +tt indicates the number of minutes the observation was taken after the hour, and -tt indicates the number of minutes the observation was taken before the hour. Example: The observation was taken 18 minutes after the hour (other direct-ingest weather data are from the hour). METAR CYAM 121300Z 00000KT 15SM FEW 012 FEW220 M20/M22 A3039 RMK SC1CI1 OBS TAKEN +18 SLP308 */ if(count($metar_toDecode) > 2 and $metar_toDecode[0] =='OBS' and $metar_toDecode[1] =='TAKEN' and (substr($metar_toDecode[2], 0, 1) == '+' or substr($metar_toDecode[2], 0, 1) == '-') and is_numeric(substr($metar_toDecode[2], 1))) { $matched = true; $output = '(r33) = Barometer Reading Observation was delayed until ' . substr($metar_toDecode[2], 1) . ' minutes '; $output .= substr($metar_toDecode[2], 0, 1) == '+' ? 'after the time due (on the hour)' : 'before the time due (on the hour)'; $decodeGroupProcessed['BAROMETER'] .= ' (r33)'; do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; } break; case 35: // CANADA h) 16.3.13.4 Observational program status $iCount = 0; /* The two official formats are defined below. 16.3.13.4 Observational program status In order that users of weather observations can determine if a station is staffed or when the next observation will be, Remarks indicating the status of operation are required. */ if(count($metar_toDecode) > 2 and $metar_toDecode[0] == 'LAST' and substr($metar_toDecode[1], 0, 3) =='OBS') { /* OBSERVER ONLY: At sites with less than a 24-hour observing program and observations not supplemented with an AUTO station, when composing the final METAR for the last observation of the day; enter, in the Remarks section, time of next METAR using a day-time format ending in UTC. Example: The last daily weather observation is issued at 03Z; the next weather observation will be issued at 10Z. METAR CYGK 010300Z 20005KT 15SM SCT090 BKN110 21/17 A2994 RMK AC3AC2 LAST OBS/NEXT 011000UTC SLP138 */ $iCount = 2; optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing " . $manobs[$specification] . ' segments ' . $metar_toDecode[2] . ', ' . $metar_toDecode[3], $file); // Diagnostic on condition match }elseif(count($metar_toDecode) > 3 and $metar_toDecode[0] == 'LAST' and $metar_toDecode[1] == 'STFD' and substr($metar_toDecode[2], 0, 3) == 'OBS') { /* AUTOMATIC AND OBSERVER At sites with a 24-hour program and a staff/machine mix for observations, when composing the METAR for the last staffed observation of the day; enter, in the Remarks section, time of next METAR to be issued by an Observer when staffing resumes, using a day-time format ending in Z. In-between the two day-times appearing in that METAR, further automatic METAR will be issued, but typically with minimal information. Example: The last daily staffed weather observation is issued at 03Z; the next staffed weather observation will be issued at 13Z. METAR CYXH 100300Z 28015G21KT 15SM FEW 270 03/M02 A3001 RMK CI2 LAST STFD OBS/NEXT 101300Z SLP187 */ $iCount = 3; optionalDiagnosticOutput("decode_CAN_remarks() specification iterator=$specification processing " . $manobs[$specification] . ' segments ' . $metar_toDecode[0] . ', ' . $metar_toDecode[1] . ', ' . $metar_toDecode[2], $file); // Diagnostic on condition match } // Note that both the official formats include as a single segment 'OBS/NEXT', but one uses 'Z', the other uses 'UTC' at end of day-time segment. // I don't assume that presence or absence of 'STFD' determines whether 'Z' or 'UTC' will be used if(!$iCount) break; // exit this case if not an 'observational status report' if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Initial array recognised up to element ' . ($iCount - 1) . ' only '); } /* The script above has matched $iCount segments and that is up to where it found 'OBS'. Canadian Observers seem to mess around with where they place spaces, so now have to check that segment again. The following examples are all taken from St. Catharines Airport (station id=CYSN) Ontario, Canada: a) Correct single segment 'OBS/NEXT', but then extra space before 'UTC' 2017/11/24 02:00 METAR CYSN 240200Z 24010KT 15SM OVC070 04/M02 A2994 RMK AC8 LAST STFD OBS/NEXT 241200 UTC SLP143 b) Extra space each side of oblique, yet no space after 'NEXT': 2018/02/10 02:00 CYSN 100200Z 06002KT 3SM -SN SCT015 OVC058 M05/M06 A3014 RMK SC3SC5 LAST STFD OBS / NEXT101200UTC SLP214 c) Extra space each side of oblique, and then extra space before 'UTC' 2017/12/08 02:00 METAR CYSN 080200Z 24010G16KT 15SM FEW080 M03/M08 A2998 RMK AC2 LAST STFD OBS / NEXT 081200 UTC SLP158 d) Correct single segment 'OBS/NEXT', but then extra space between day and time 2017/12/28 02:00 METAR CYSN 280200Z 31010G15KT 15SM SCT038 M12/M19 A3060 RMK SC4 LAST STFD OBS/NEXT 28 1200UTC SLP371 2018/01/26 02:00 METAR CYSN 260200Z 21002KT 15SM OVC033 M03/M07 A3052 RMK SC8 LAST STFD OBS/NEXT 26 1200UTC SLP341 The next part of the script handles these various formats for 'NEXT' */ switch($metar_toDecode[$iCount - 1]) { case 'OBS/NEXT': // a) or d) - correctly formed single segment if($show_diagnostics or $show_diagnosticsR) echo '
' . $metar_toDecode[$iCount - 1] . ' is correctly formed, and will be processed now.
' . $metar_toDecode[$iCount] . ' will be investigated next
'; break; case 'OBS/': // Not seen yet, but test for it so I cover all possibilities if($show_diagnostics or $show_diagnosticsR) echo '
' . $metar_toDecode[$iCount - 1] . ' is NOT correctly formed, and needs to be combined with ' . $metar_toDecode[$iCount] . '
'; $metar_toDecode[$iCount - 1] = 'OBS/NEXT'; // Correct to the conforming single segment if(strlen($metar_toDecode[$iCount]) == 4) // 'NEXT' on its own { // merge before and after original 'NEXT' $metar_toDecode = array_merge(array_slice($metar_toDecode, 0, $iCount), array_slice($metar_toDecode, 1 + $iCount)); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised array after moving just "NEXT" '); } }else{ // b) - 'NEXT' combined with day-time $metar_toDecode[$iCount] = substr($metar_toDecode[$iCount], 4); // Remove "NEXT" leaving just day-time if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised array after moving letters and separating off day-time'); } } break; case 'OBS': if($show_diagnostics or $show_diagnosticsR) echo '
' . $metar_toDecode[$iCount - 1] . ' is NOT correctly formed, and needs to be combined with ' . $metar_toDecode[$iCount] . '
'; switch($metar_toDecode[$iCount]) { case '/': if($show_diagnostics or $show_diagnosticsR) echo 'Append ' . $metar_toDecode[$iCount] . ' then look at ' . $metar_toDecode[1 + $iCount] . '
'; $metar_toDecode[$iCount - 1] = 'OBS/'; // Append the "/" to previous segment // Remove the segment with "/" by merge before and after original '/' $metar_toDecode = array_merge(array_slice($metar_toDecode, 0, $iCount), array_slice($metar_toDecode, $iCount + 1)); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised element ' . ($iCount - 1) . ' and removed former element ' . $iCount . ' after moving "/" '); } $metar_toDecode[$iCount - 1] = 'OBS/NEXT'; // Correct to the conforming single segment if(strlen($metar_toDecode[$iCount]) == 4) // 'NEXT' on its own { // Remove the segment with "NEXT" by merge before and after original 'NEXT' $metar_toDecode = array_merge(array_slice($metar_toDecode, 0, $iCount), array_slice($metar_toDecode, 1 + $iCount)); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised element ' . ($iCount - 1) . ' and removed former element ' . $iCount . ' after moving just "NEXT" '); } }else{ // b) - 'NEXT' combined with day-time $metar_toDecode[$iCount] = substr($metar_toDecode[$iCount], 4); // Remove "NEXT" leaving just day-time if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised elements ' . ($iCount - 1) . ' and ' . $iCount . ' after moving letters and separating off day-time '); } } break; case '/NEXT': // Not seen yet, but test for something like imagined one below so I cover all possibilities // METAR CYSN 140200Z 18005KT 1SM -SN VV011 M08/M09 A2961 RMK SN8 LAST STFD OBS /NEXT 141200 Z SLP034 if($show_diagnostics or $show_diagnosticsR) echo 'Append ' . $metar_toDecode[$iCount] . ' so ' . $metar_toDecode[$iCount - 1] . ' becomes correctly formed.
' . $metar_toDecode[1 + $iCount] . ' will be investigated next<br>'; $metar_toDecode[$iCount - 1] = 'OBS/NEXT'; // Correct to the conforming single segment // merge before and after original 'NEXT' $metar_toDecode = array_merge(array_slice($metar_toDecode, 0, $iCount), array_slice($metar_toDecode, 1 + $iCount)); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised array after moving "/NEXT" '); } break; default: if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'The array at element ' . $iCount . ' has not been recognised'); } } break; } if($show_diagnostics or $show_diagnosticsR) print_array($metar_toDecode, 'Processing elements 0 to ' . ($iCount - 1) . ' of this array '); if($iCount == 2) $output = 'This aerodrome only issues METAR when it an Observer is present.   This is last report of airport operating day - issued ' . $decodeInfo['LOCAL']; else{ $output = 'Last METAR issue by Observer, hourly reports of automatic observations continue (See '; $output .= 'http://www.flightplanning.navcanada.ca/cgi-bin/Fore-obs/metar.cgi?format=raw&Langue=anglais&Region=can&Stations=' . $decodeGroupProcessed['AIG']; $output .= ').
Time of issue ' . substr($decodeInfo['LOCAL'], 0, 32); } $output .= ' [=' . substr($decodeInfo['ISSUED'], 45) . ' (UTC) ' . substr($decodeInfo['ISSUED'], 34, 11) . ']
'; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; if($show_diagnostics or $show_diagnosticsR) print_array($metar_toDecode, 'Processing element 0 onwards of this array '); // Now handle any differences from standard 'ddhhmmUTC' or 'ddhhmmZ' formats if(strlen($metar_toDecode[0]) == 2) // d) in examples above - just day of month in separate segment { if($show_diagnostics or $show_diagnosticsR) echo '
day "' . $metar_toDecode[0] . '" should NOT be separate to time, so needs to be combined with ' . $metar_toDecode[1] . '
'; $metar_toDecode[0] = $metar_toDecode[0] . $metar_toDecode[1]; // Place day with time to get correct format $metar_toDecode = array_merge(array_slice($metar_toDecode, 0, 1), array_slice($metar_toDecode, 2)); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Revised array after combining day of month with time'); } } if(is_numeric($metar_toDecode[0]) and ($metar_toDecode[1] == 'Z' || $metar_toDecode[1] == 'UTC')) { if($show_diagnostics or $show_diagnosticsR) echo '
' . $metar_toDecode[0] . ' is NOT correctly formed, and needs to be combined with ' . $metar_toDecode[1] . '
'; $metar_toDecode[1] = $metar_toDecode[0] . $metar_toDecode[1]; // Add 'Z' or 'UTC' to end $metar_toDecode = array_slice($metar_toDecode, 1); if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'Format corrected in element ' . 0 . ' now'); } } if(1 === preg_match('~^([0-3][0-9])?([012][0-9])([0-5][0-9])~', $metar_toDecode[0], $pieces)) { if($show_diagnostics or $show_diagnosticsR) { echo $metar_toDecode[0] . ' = Correctly formed day '. $pieces[1] . ' and correctly formed time ' . "$pieces[2]" . ':' . $pieces[3] . '
'; } // Pass hour, minute, and day, parts (with any leading zeroes) of day-time segment into function to adjust to local time $nextTime = adjustTime("$pieces[2]" . ':' . "$pieces[3]", "$pieces[1]"); $output = 'Next METAR issue by Observer due local time ' . $nextTime['hour'] . ':' . $nextTime['minute'] . ' on day ' . $nextTime['day'] . ' of month ' . '[= at ' . "$pieces[2]" . ':' . "$pieces[3]" . ' (UTC) on ' . $pieces[1] . ' of month]'; do_output($decodeGroupCount['REMARK'], 1, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $matched = true; break; } if($show_diagnostics or $show_diagnosticsR) { print_array($metar_toDecode, 'The array at element 0 has not been recognised'); } unrecognised($manobs[$specification]); break; case 36: // 16.4.4.10 Volcanic eruption /* 16.4.4.10 Volcanic eruption The occurrence of a volcanic eruption shall be reported by a SPECI observation when observed. Post-eruption volcanic ash clouds should be included in Remarks of METAR and SPECI observations as long as significant. The following data shall be included in Remarks when known: 1) Name of the volcano 2) Direction (16 points, true, of the compass) and approximate distance (statute miles) of the volcano 3) Date/Time (UTC) of eruption 4) Height and direction of movement of ash cloud 5) Other pertinent data My decoder note: Canada uses SPECI rather than METAR for reporting actual eruption of volcanoes e.g. SPECI .... MT ST HELEN VOLCANO 60 MI W NW ERUPTED 091025 ASH CLOUD TO 300 MOVG RPDLY SE but (as explained above in the manual extract) after eruption, the progress of the ash cloud or a continuation in the eruption is reported via a METAR, so code snippet to decode it follows Example: METAR RMK MT ST HELEN VOLCANO 60 MI WNW ERUPTED 091025 ASH CLOUD TO 300 MOVG RPDLY SE */ if(count($metar_toDecode) > 2 and in_array('VOLCANO', $metar_toDecode)) { $output = ""; // initialise the initial 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), substr($metar_toDecode[$iCount + $j], 0, 2)); $output .= "at " . $timeArray['hour'] . ":" . $timeArray['minute'] . " (local time) on day " . $nextTime['day'] . " of month"; do_output($decodeGroupCount['REMARK'], $iCount, $output, $manobs[$specification]); $j = $iCount; $output = ""; // re-initialise ready for further output from the decoder }else // Since don't know how many segments are being used to convey the 'Other pertinent data', can only output all those found after dayTime in second message. $output .= $metar_toDecode[$iCount + $j]; if($iCount < $k and $j != $iCount) { $output .= " "; } } do_output($decodeGroupCount['REMARK'], $iCount - $j, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; $specification = 50; // nothing left to decode $matched = 1; goto exit_here; } break; case 37: // Last one for Canada - "Density altitude" # real example METAR CYEG 011000Z 30015G20KT 15SM SCT039 BKN075 BKN170 09/01 A2974 RMK SC3AC3AC1 SLP084 DENSITY ALT 2400FT= # DONE // 2017/12/05 07:00 METAR CYZR 050700Z AUTO 20019G27KT 9SM SCT039 BKN049 BKN060 OVC072 12/06 A2965 RMK SLP043 DENSITY ALT 600FT if(count($metar_toDecode) >2 and $metar_toDecode[0] == 'DENSITY' and $metar_toDecode[1] == 'ALT' and substr($metar_toDecode[2], -2) == 'FT') { $height = substr($metar_toDecode[2], 0, -2); # if($show_diagnosticsR) echo '
density height = ' . $height . '
'; $output = 'Density altitude (pressure altitude corrected for non-standard temperature) is at ' . number_format(1 * $height) . ' feet
(i.e. at take off, aircraft feels like it is already at ' . number_format(1 * $height) . ' feet = ' . number_format(0.3048 * $height, 1) . ' metres)'; /* The ICAO International Standard Atmosphere standard conditions for zero density altitude are 0 meters (0 feet) altitude (sea level), 15 deg C (59 deg F) air temp, (Temperature decreases about 2 degrees C (or 3.5 degrees F) per 1,000 feet of altitude above sea level.) 1013.25 mb (29.921 in Hg) pressure and 0 % relative humidity (absolute zero dew point). The standard sea level air density is 1.225 kg/m3 (0.002378 slugs/ft3). */ do_output($decodeGroupCount['REMARK'], 3, $output, $manobs[$specification]); $matched = true; $decodeGroupCount['Canada']++; } break; case 38: // Toronto Lester B Pearson International Airport (station id=CYYZ) Ontario (P), Canada // 2017/12/11 17:00 METAR CYYZ 111700Z 00000KT 15SM FEW020 BKN040 OVC110 M06/M11 A2999 RMK SC1SC7AS1 SC TR VIA CYVV SLP166 // Wiarton, Ont., Canada (CYVV) if(count($metar_toDecode) >1 and $metar_toDecode[0] == 'VIA') { $output = 'Another airport has been involved in this report, it has an ICAO identifier of ' . $metar_toDecode[1]; $matched = true; do_output($decodeGroupCount['REMARK'], 2, $output, $manobs[$specification]); $decodeGroupCount['Canada']++; break; } break; case 39: break; } // end Switch if(count($metar_toDecode) < 1) goto exit_here; // make sure exit if nothing left to decode if($specification > 38) { // if there are no more segments left to decode or first pass through this sub-function has not decoded any segments; exit if(count($metar_toDecode) < 1 or !$matched) goto exit_here; if($show_diagnostics or $show_diagnosticsR) { echo "
Reached end of list of specifications, but not end of segments awaiting decoding.
"; } if($passCountCan < 2) { $passCountCan ++; $matched = false; $specification = 1; // Move to first specification again for second and last pass. }else break; }else $specification ++; // Move to next part of specification. } // end While exit_here: if($passCountCan) $matched = true; return $matched; }// end sub-function - Canada //======================================================// // RMK Group: Canada - walk - anonyminised function // // called at start of above Canada sub-function. // // Walks the $remarks_toDecode array, its keys are the // // raw METAR segments, the value tracks whether decoded // //======================================================// function canadaWalk(&$value, $key, &$testArray) { global $metar_toDecode, $decodeInfo, $decodeGroupCount, $decodeGroupProcessed, $remarks_toDecode, $show_diagnostics, $show_diagnosticsR, $dM_Rmk; // shared with other scripts global $file, $cloud_type_array, $cloudAmountBandCode, $precipitationTypes; // declared in main script 'decodeMETAR.php' if(!count($metar_toDecode)) return; // exit from walk if nothing left to be decoded if($value != "Un-matched") return; // exit from walk if current array key has already been matched and decoded if($show_diagnostics or $show_diagnosticsR) echo '
' . $key . ' examined by function canadaWalk()'; ///////////////////////////////////// // RMK Group: Pressure Tendency // ///////////////////////////////////// if(!$testArray['PressureChange'] and substr($key, 0, 4) == 'PRES') { // CANADA 2 e) 16.3.13.2.5 Pressure change Remarks (based on change in last 15 minutes scaled up to hour) /* 2018/01/07 20:31 METAR CYXU 072031Z 19016G22KT 1SM R15/4500VP6000FT/N SN OVC015 M09/M13 A3010 RMK SN4SC4 PRESFR SLP217 Went wrong at (0) → RMK   Not de-coded=PRESFR 2018/01/07 21:00 METAR CYZR 072100Z AUTO 20017G26KT 9SM SCT038 OVC055 M07/M12 A3006 RMK ICG PRESFR SLP194 Went wrong at (1) → RMK_ICG   Not de-coded=PRESFR */ // 2018/02/03 19:00 METAR CYXU 031900Z 21011KT 2 1/2SM -SHSN OVC014 M06/M09 A3008 RMK SN2SC6 CVCTV CLD EMBD PRESFR SLP207 /* 16.3.13.2.5 Pressure change Remarks METAR ...SCT040 RMK PRESRR (1) METAR BKN100 RMK PRESFR (2) Note (1): PRESRR is used when the barograph trace indicates that the station pressure is rising at the rate of 2.0 hPa or more per hour. METAR BKN100 RMK PRESFR (2) Note (2): PRESFR is used when the barograph trace indicates that the station pressure is falling at the rate of 2.0 hPa or more per hour. Note: If the barograph trace shows a steady increase of 0.5 hPa during the last 15 minutes, the rate of increase would be 2.0 hPa per hour and the remark PRESRR would be appropriate. If the barograph trace shows a steady decrease of 0.5 hPa during the last 15 minutes, the rate of decrease would be 2.0 hPa per hour and the remark PRESFR would be appropriate. 'PRESRR' = "pressure rising rapidly (> 2hPa per hour)" or 'PRESFR' = "pressure falling rapidly (> 2hPa per hour)" Related back to cloud cover - lowest level Canada adopts SI unit for pressure of kiloPascals (thousand Pascals) rather than hectoPascals (hundred Pascals), so the output figures are ten times lower than the figures above */ switch($key) { case 'PRESFR': case 'PRESRR': if(array_key_exists('CLOUD-DETAILS', $decodeInfo)) { $output = "(r10) =(Related to front marked by lowest cloud indicated) "; $i = strpos($decodeInfo['CLOUD-DETAILS'], "ft"); $decodeInfo['CLOUD-DETAILS'] = substr($decodeInfo['CLOUD-DETAILS'], 0, 2 + $i) . " (r10) " . substr($decodeInfo['CLOUD-DETAILS'], 2 + $i); }else $output =""; if($show_diagnosticsR) { echo "
Canada Walk begins examining " . $key . " identifying pressure tendency
"; } $output .= 'Air Pressure tendency '; $output .= substr($key, 4, 1) == 'F' ? 'falling ' : 'rising '; $output .= 'rapidly at greater than 0.2 kPa hr-1 (based on change in last 15 minutes)'; walk_output($decodeGroupCount['REMARK'], $key, $output, '16.3.13.2.5 Pressure change'); $testArray['PressureChange'] = true; } // end switch } if(count($metar_toDecode) < 1) goto exitCall; ////////////////////////////////////////////////////////////////////////////////// // CANADA 16.3.13.1 Layer type and amount (oktas) // // RMK Group: Most widely used codes to try - Cloud type and Amount Layers // ////////////////////////////////////////////////////////////////////////////////// /* 16.3.13.1 Layer type and amount (oktas) For each layer reported in the layer aloft section (see 16.3.9), a corresponding cloud (from the list below) and amount shall be recorded. When vertical visibility is observed, the obscuring phenomenon abbreviation and amount shall be recorded. The amount will be a single digit. When surface-based layers (see 1.2.5) are observed to obscure portions of the celestial dome, the obscuring phenomenon abbreviation and amount shall be recorded. Some Canadian METAR examples in this specification, all can be decoded: List of cloud types in Remarks Group (matched to cloud layer definitions in main part of METAR), each one that is present is quoted as an abbreviation followed by amount in oktas. METAR CYJT 010600Z 23012G19KT 220V360 15SM BKN027 09/04 A3010 RMK SC7 SLP195= # single cloud code in segment after RMK Some 2 cloud specification examples - matching Cloud Cover Group and Remarks Group: METAR CYYR 010800Z 23018G27KT 15SM -SHRA SCT043 OVC078 08/02 A2952 RMK SC3AC5 SLP999= # NB this example has lower oktas first in remarks cloud section METAR CYYR 010600Z 23020G28KT 15SM -SHRA BKN057 OVC078 07/02 A2960 RMK SC6AC2 SLP026= # NB this example has higher oktas first, remember order of cloud types is more significant than order of oktas "Stratocumulus" => "SC" is a low cloud "Altocumulus" => "AC" is a high cloud A 3 cloud code specification example - matching Cloud Cover Group and Remarks Group: 2017/11/16 00:00 METAR CYXU 160000Z 19016G22KT 5SM -RA BR BKN007 BKN012 OVC021 07/06 A2970 RMK SF5SF2SC1 CIG VRB 5-9 SLP068 WMO define maximum number of cloud layers as 3, but Canada often defines 4 cloud groups in main Cloud Cover Group and Remarks Group: 2017/11/16 19:00 METAR CYHM 161900Z 28014G19KT 15SM SCT030 BKN042 BKN060 OVC082 05/01 A2987 RMK CU3SC2SC2AC1 SLP120 */ if(!$testArray['Layer'] and (in_array(substr($key, 0, 2), $cloud_type_array) || in_array(substr($key, 0, 3), $cloud_type_array)) and is_numeric(substr($key, -1, 1))) { if($show_diagnostics or $show_diagnosticsR) { echo "
Canada Walk begins examining " . $key . " looking for these
"; print_array($cloud_type_array,'cloud types'); } $testArray['Layer'] = true; re_enter: $CloudRmk = 0; // initialise as no cloud type code found if(in_array(substr($key,0,2), $cloud_type_array) and is_numeric(substr($key, 2, 1))) $CloudRmk = 2; // 2 letter cloud type code // above condition modified to ensure only 2 letters! (ensures CIG is not matched to Cirrus) elseif(in_array(substr($key,0,3), $cloud_type_array) and is_numeric(substr($key, 3, 1))) $CloudRmk = 3; // 3 letter cloud type code found else goto stopRepeat; $pattern_count = preg_match_all('~([A-T][B-U][CU]?)([0-9])~', $key, $pattern_array, PREG_SET_ORDER); // find all cloud types within segment if($CloudRmk > 0 and $pattern_count > 0) { if($show_diagnostics or $show_diagnosticsR) { print_array($pattern_array, '
Found ' . $pattern_count . ' Coded Cloud Types as array
'); } // cloud type found with precise oktas // Montréal Mirabel International Airport|Montreal, Quebec, Canada===== Remarks Group with precise cloud, details for weather, sea level pressure // 2017/10/26 16:00 CYMX 261600Z 21004KT 180V250 25SM FEW070 BKN110 OVC200 11/06 A2976 RMK AC1AC6CI DIST SH S SLP082 for($skyIterator=0; $skyIterator < $pattern_count; $skyIterator++) { $cloud_layer_type = array_search($pattern_array[$skyIterator][1], $cloud_type_array, true); if($cloud_layer_type) { if($show_diagnostics or $show_diagnosticsR) { echo "

sub-function = decode_CAN_remarks - Specification=16.3.13.1 Layer type and amount (oktas)     Iteration " . $skyIterator . "
"; echo "Matched " . $pattern_array[$skyIterator][0] . ", coded cloud type of '" . $pattern_array[$skyIterator][1] . " into '" . $cloud_layer_type . "', and reports " . $pattern_array[$skyIterator][2] . " oktas

"; } $decodeInfo['SKY-DETAILS'][$skyIterator][0] = $cloud_layer_type; $decodeInfo['SKY-DETAILS'][$skyIterator][1] = "" . $pattern_array[$skyIterator][2] . " oktas"; $decodeGroupProcessed['SKY'][1 + $skyIterator] .= " + RMK_" . $pattern_array[$skyIterator][0]; } } // end for $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] = '16.3.13.1 Layer type and amount (oktas)'; $decodeGroupProcessed['VARIABLE'] = 'RMK_' . $done[0]; if($show_diagnostics) echo "

Matched into '16.3.13.1 Layer type and amount (oktas)' the content of " . $key . "

"; // conditional de-bugging section continues # walk_output($decodeGroupCount['REMARK'], $key, $output, ); } } stopRepeat: # re-entry point if no match to conditions to enter repeat code snippet if(count($metar_toDecode) < 1) goto exitCall; ////////////////////////////////////////////// // RMK Group: Wind Direction Variation // // dddVddd - two bearings in one segment // ////////////////////////////////////////////// if(!$testArray['DirectionVariation']) { $partLocal = preg_match('/([0-9]{3})V([0-9]{3})/', $key, $pieces); if ($partLocal === 1) { if($show_diagnostics or $show_diagnosticsR) { echo "
Canada Walk begins examining " . $key . " reporting wind directions
"; } $testArray['DirectionVariation'] = true; $decodeInfo['VARIABLE'] = "Wind direction reported as variable: bearings " . $pieces[1] . "° to " . $pieces[2] . "°"; $decodeInfo['VARIABLE_FROM'] = $pieces[1]; $decodeInfo['VARIABLE_TO'] = $pieces[2]; $decodeInfo['FROM'] = $compass[1 + round($pieces[1] / 22.5) % 16]; $decodeInfo['TO'] = $compass[1 + round($pieces[2] / 22.5) % 16]; $decodeGroupCount['VARIABLE'] = true; $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] = 'RMK Group: Wind Direction Variation'; $decodeGroupProcessed['VARIABLE'] = 'RMK_' . $done[0]; if($show_diagnostics) echo "

Matched into 'Wind Group (variable bearing part)' the content of " . $decodeInfo['VARIABLE'] . "

"; // conditional de-bugging section continues } } if(count($metar_toDecode) < 1) goto exitCall; ///////////////////////////////////// // RMK Group: Specific weather // ///////////////////////////////////// // CANADA c) 16.3.13.2.3 Weather Remarks if($show_diagnostics or $show_diagnosticsR and ($key == 'FROIN' || $key === 'RIME')) { echo "
Canada Walk begins examining " . $key . " reporting frost/ice
"; } switch($key) { case 'FROIN': # (vii) 'FROIN' = "report frost on the ice accretion indicator" - MANOBS para 3.4.2.4 Ice accretion indicator # 201801051100 METAR CYXU 051100Z 26004KT 15SM BKN033 BKN240 M21/M24 A3002 RMK SC5CI1 FROIN SLP193= $output = "Frost observed on the exchangeable 'L-shaped Aluminium strip' either slotted onto Stevenson Screen or similar location 1 m agl"; walk_output($decodeGroupCount['REMARK'], $key, $output, '16.3.13.2.3 Weather Remarks' . " (vii)"); break; case 'RIME': $output = "Rime ice observed on the exchangeable 'L-shaped Aluminium strip' slotted onto Stevenson Screen"; walk_output($decodeGroupCount['REMARK'], $key, $output, '16.3.13.2.3 Weather Remarks' . " (vii)"); break; } if(count($metar_toDecode) < 1) goto exitCall; ////////////////////////////////////////// // RMK Group: Sky Cover 'Contrails' // ////////////////////////////////////////// // CANADA d) 16.3.13.2.4 Sky condition Remarks (i) Sky cover if(strlen($key) > 6 and substr($key, 0, 4) == "CONT") // NB using substring for just partial match as have seen at least 5 different spellings being used as in examples below { /* METAR FEW 250 RMK CONTRAILS e.g. 'CONTRAILS' = "aircraft condensation trails are persisting for at least 15 minutes" Note: Shall be used when middle (CM) or high (CH) cloud consists in whole or in part of persistent (15 minutes or more) condensation trails. Rapidly dissipating condensation trails shall not be reported. 2017/11/26 15:00 METAR CYQG 261500Z 26005KT 15SM FEW030 FEW270 01/M04 A3017 RMK CU1CI2 CU TR CONTAILS SLP226 Note in above example the code is malformed as it has a missing 'R' 2017/11/14 18:00 METAR CYQG 141800Z 27007KT 210V290 10SM SCT027 BKN260 07/01 A3031 RMK CU3CI4 CONTRAILS SLP270 2017/12/01 21:00 METAR CYQG 012100Z 20011KT 15SM BKN320 07/M00 A3023 RMK CI7 CONTRAIL SLP243 2018/01/20 19:00 CYQG 201900Z 21012KT 15SM BKN270 05/M01 A2992 RMK CI7 CONTRAI Suspect in last, truncation of the complete METAR may have occurred. */ if($show_diagnostics or $show_diagnosticsR) { echo "
Canada Walk begins examining " . $key . " reporting Condensation Trails
"; } $output = "(rcw) = Highest reported cloud layer either completely, or partly, consists of persisting (for 15 minutes or more) aircraft condensation trail(s)"; walk_output($decodeGroupCount['REMARK'], $key, $output, '16.3.13.2.4 Sky condition Remarks Sky cover - 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] .= " (rcw)"; elseif(array_key_exists(1, $decodeInfo['SKY-DETAILS'])) $decodeInfo['SKY-DETAILS'][1][0] .= " (rcw)"; else $decodeInfo['SKY-DETAILS'][0][0] .= " (rcw)"; # print_array($decodeInfo['SKY-DETAILS'], "sky-details"); } } if(count($metar_toDecode) < 1) goto exitCall; ///////////////////////////////////// // RMK Group: Sea Level Pressure // ///////////////////////////////////// if($show_diagnostics or $show_diagnosticsR and substr($key, 0, 3) == 'SLP') { echo "
Canada Walk begins examining " . $key . " reporting pressure amount
"; } if(substr($key, 0, 3) == 'SLP') $return = walk_SLP($key, "Canada SLP report"); exitCall: } // end anonymous function - canadaWalk /* Version history =============== 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 the script, breaking it into multiple functions, so have split tests within Remarks Group into main function in "decodeMETAR_Rmk.php" for undetermined country common pressure and cloud decoding, and sub-function for Canada standards. Canada working for those codes currently checked in script, although still not covering all possibilities (partly because sequence can vary so difficult to code some). 0.2.1 11 Nov 2017 Bug fix in Canada sub-function re counting remark segments processed (wrong to count SLP as that not output as a Remarks Group entry, but appended to pressure in main output; and logically wrong statement order in processing obstructing sky as previously counting when not present instead of when processed). Bug fix re cloud decoding arrays, had confused cloud amount and cloud type! 0.2.2 22 Nov 2017 There is still a lot more to be done to the Canada sub-function and to aid this there is a change to the output log for any segments not decoded, it outputs the whole METAR, the segment prior to the one that was not decoded (in case that was processed wrongly) and the segment that is not decoded. The new log is being populated at http://www.komokaweather.com/metar/failed_to_decode_metar.log by Paul's live web page. 0.2.3 27 Nov 2017 Progressing Canada (Convective clouds - spec 1, other layers - spec 2) sub-function. The sub-functions (not array_walks) called from the main functions in this script have all been moved to 'decodeMETAR_sub_funct.php' so all sub-functions are kept together and the length of this script is minimised. As for previous attempts to code following handbook or manual provided by that country, the live METAR contain many codes that do not fit the structure described, and that use abbreviations that are not within list of abbreviations issued by the relevant country. Consequently, a certain amount of guesswork is involved in output generation by this latest version. 0.2.4 2 Dec 2017 Bug correction - missing pair of brackets re Canadian pressure change. 0.2.5 11 Dec 2017 Addressing failed-to-decode errors. There was a bumper crop of these during this month, partly because it is my first encounter with Winter phenomena like snow. Some small changes following from failures to decode various Canadian METAR that are malformed compared to format in official MANOBS specification. I have checked this version against aerodromes from across the world and also against output from "metaf2xml" decoder, so it should be good. 0.2.6 30 Dec 2017 Bug correction, previous version worked better for many METAR, but not for all the variants encountered! Changed the calls from this script to decode Visibility, to make the Visibility sub-function in "decodeMETAR_sub_funct.php" more universal. Changed the logical sequence of trying the country specific specifications. For Canada sub-function, also did some slight renumbering to better reflect actual order and reduce looping back; added code to cope with malformed segments (where observer uses different abbreviations or puts spaces in different places to those specified in guidance manual) and simplified flow by more use of 'elseif'. Now records every time goes into sub-function, so shows for example if both Canada and another country's codes found or if this sub-function entered twice. 0.2.7 6 Jan 2018 Specific changes to Canada sub-function: 1) to exit after first pass if no matches, and 2) to make two passes if a match on first pass, this enables matching of more specifications if one is out of sequence or if same specification appears twice. 0.3.0 26 Jan 2018 Conversion of single segment specifications for Canada into array walk approach, revising of many multiple segment specifications, attempting to make the processing of Remarks Group for Canada both more comprehensive and more efficient through better design of PHP instructions. Removal of hundreds of unprintable characters (can be viewed in FilesCompare tool, and apparently were introduced in an edit using Notepad++). Removal of key of 'Country' from $decodeGroupCount. Replacement by series of new Country-specific keys in same array such as $decodeGroupCount['Canada']. 0.3.1 29 Jan 2018 Added another test to Canada case 3 (Additional Layer information). Corrected index number for call to time function in Canada case 34 (Observational program status). 0.3.2 14 Feb 2018 Now that it appears almost all the possible METAR content in Remarks Group for Canada is being handled successfully, this update focussed on improving the look of outputs. For Canadian Cloud with 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). Improved output format for Canada case 10 (Variable Visibility) by adding units. Added visible moon to Canada sky codes (specification 25). Added ability to cope with more wrong METAR content spacing for Canada case 34 (Observational program status) and changed output format. Corrected conditions for Canada case 32 (Snowfall or rainfall reporting procedures for part-time stations), and made the various content formats clearer. Removal of comments into portable document format paper. 0.3.3 28 Feb 2018 Introduced jump for multiple segment specifications based on content of first segment of multiple segments, this technique only implemented where a single specification had a fixed content in that first segment that was unique to just that specification. Edited wind shift in case 7 to cope with malformed content. Modified output for visibility so consistent with main Visibility Group and quotes figures in both feet and metres. Added more conditions for alternative syntax for Lightning specification in case 17. Corrected cases 19, 20, 22, 23, and 24 that were repeating and should be only processed once. Case 26 about ceiling, rewrote snippet about balloon. Updated cross-references between Mandatory Standard Group output and Remarks Group output. Case 29 re-written to cover two formats of standing wave clouds. Rewrote case 32 regarding rainfall, so simpler conditions and greater flexibility. For case 35 (16.3.13.4 Observational program status) rewrote complex set of 'if' conditions into simpler, more efficient, and more comprehensive 'switch' with 'case' instructions. Case 37 (Density Altitude) added better explanation as well as improving wording of output. Rewrote CanadaWalk function to work better, corrected the pressure change PHP snippet as was not working consistently, and simplified other single segments. 0.3.4 10 Mar 2018 Corrected error in 16.3.13.2.3 Weather Remarks (iv) Qualifying precipitation - occasional (case 21). 0.3.5 13 Mar 2018 Corrected Sector Visibility to make compatible with change in 'decodeMETAR_sub_funct.php' (Canada and USA use different formats) 0.3.6 18 Mar 2018 Corrected some errors in switch that jumps to later segment. */ /* ?> 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