surrounding the output of the script .. change styling to suit. $boxStyle = 'style="border: dashed 1px black; background-color:#FFFFCC; margin: 5px; padding: 0 5px;"'; // $cacheFileDir = './'; // default cache file directory $cacheName = "radar-status.json"; // used to store the file so we don't have to // fetch it each time $refetchSeconds = 60; // refetch every nnnn seconds $showHMSAge = true; // =false for number of seconds, =true for H:M:S age display $showMsgCnt = 2; // show up to 2 most recent messages // end of settings // Constants // don't change $fileName or script may break ;-) $fileName = 'https://api.weather.gov/radar/stations/'; $fileName2 = 'https://api.weather.gov/products/types/FTM/locations/'; // end of constants // --------------------------------------------------------- // overrides from Settings.php if available global $SITE; if (isset($SITE['GR3radar'])) {$myRadar = $SITE['GR3radar'];} if (isset($SITE['tz'])) {$ourTZ = $SITE['tz'];} if (isset($SITE['timeFormat'])) {$timeFormat = $SITE['timeFormat'];} if (isset($SITE['showradarstatus'])) {$noMsgIfActive = ! $SITE['showradarstatus'];} if (isset($SITE['cacheFileDir'])) {$cacheFileDir = $SITE['cacheFileDir']; } // end of overrides from Settings.php if available // ------ start of code ------- if (isset($_REQUEST['sce']) && strtolower($_REQUEST['sce']) == 'view' and strlen($_REQUEST['sce']) == 4) { //--self downloader -- $filenameReal = __FILE__; $download_size = filesize($filenameReal); header('Pragma: public'); header('Cache-Control: private'); header('Cache-Control: no-cache, must-revalidate'); header("Content-type: text/plain"); header("Accept-Ranges: bytes"); header("Content-Length: $download_size"); header('Connection: close'); readfile($filenameReal); exit; } if (isset($_REQUEST['sce'])) { header("HTTP/1.1 403 Forbidden"); print "

Hacking attempt. Denied.

\n"; exit(); } // Check parameters and force defaults/ranges if ( ! isset($_REQUEST['inc']) ) { $_REQUEST['inc']=""; } if (!isset($doIncludeRS) ) { $doIncludeRS = true; } if (isset($doIncludeRS) and $doIncludeRS ) { $includeMode = "Y"; } else { $includeMode = $_REQUEST['inc']; // any nonblank is ok } if ($includeMode) {$includeMode = "Y";} if (isset($_REQUEST['show']) ) { // for testing $noMsgIfActive = (strtolower($_REQUEST['show']) !== 'active'); } if (isset($_REQUEST['nexrad']) ) { // for testing $myRadar = substr(strtoupper($_REQUEST['nexrad']),0,4); } if (isset($statRadar)) { // for include in wxnwsradar script $myRadar = $statRadar; // use current radar in wxnwsradar scripts $includeMode = true; // force include mode $noMsgIfActive = true; // suppress message if active } if (isset($_REQUEST['cache'])) {$refetchSeconds = 1; } $myRadar = strtoupper($myRadar); // omit HTML ... if only tables wanted // --------------- customize HTML if you like ----------------------- if (! $includeMode) { ?> Radar Status for <?php print $myRadar ?> NEXRAD station \n"; if(isset($statRadar)) { print "\n"; } // refresh cached copy of page if needed // fetch/cache code by Tom at carterlake.org $cacheName = $cacheFileDir . $cacheName; $cacheName = str_replace('.json',"-".$myRadar.'.json',$cacheName); $myRadar3 = strtoupper(substr($myRadar,1,3)); $Debug = ''; if (file_exists($cacheName) and filemtime($cacheName) + $refetchSeconds > time()) { print "\n"; $html = implode('', file($cacheName)); } else { print "\n"; list($content1,$RC) = RS_fetchUrlWithoutHanging($fileName.$myRadar); print $Debug; $Debug = ''; if($RC !== 200) { print "

Radar $myRadar station returns no data. RC=$RC

\n"; return(0); } print "\n"; list($content2,$RC2)= RS_fetchUrlWithoutHanging($fileName2.$myRadar3); print $Debug; $Debug = ''; // extract the messages $radarMsgs = array(); // for storing the messages in a 'cleansed' format by Radar key, then date # $radarMsgs[$thisRadar][$thisDate] = $thisMsg; // save away for later lookup /* "@graph": [ { "@id": "https://api.weather.gov/products/f1e89484-b954-43f8-97a4-6450e5aeaf9f", "id": "f1e89484-b954-43f8-97a4-6450e5aeaf9f", "wmoCollectiveId": "NOUS66", "issuingOffice": "KMTR", "issuanceTime": "2025-09-09T01:00:00+00:00", "productCode": "FTM", "productName": "WSR-88D Radar Outage Notification / Free Text Message" }, */ $FTM = json_decode($content2,true); foreach ($FTM['@graph'] as $n => $J) { #print "\n"; list($msg,$RC) = RS_fetchUrlWithoutHanging($J['@id']); /* { "@context": { "@version": "1.1", "@vocab": "https://api.weather.gov/ontology#" }, "@id": "https://api.weather.gov/products/f1e89484-b954-43f8-97a4-6450e5aeaf9f", "id": "f1e89484-b954-43f8-97a4-6450e5aeaf9f", "wmoCollectiveId": "NOUS66", "issuingOffice": "KMTR", "issuanceTime": "2025-09-09T01:00:00+00:00", "productCode": "FTM", "productName": "WSR-88D Radar Outage Notification / Free Text Message", "productText": "\n000\nNOUS66 KMTR 090100\nFTMMUX\nMessage Date: Sep 09 2025 01:00:57\n\nKMUX radar is back up and sending data. \n\n" } */ $T = json_decode($msg,true); $thisDate = strtotime($T['issuanceTime']); $rawMsg = $T['productText']; $mparts = explode("\n",$rawMsg); #print "\n"; foreach ($mparts as $k => $mp) { if(stripos($mp,'message') !== false or stripos($mp,'outage notif') !== false) {$k++; break;} } if($k >= count($mparts)) {$k=5;} $thisMsg = implode(' ',array_slice($mparts,$k)); $radarMsgs[$myRadar][$thisDate] = $thisMsg; // save away for later lookup } $content3 = json_encode($radarMsgs); if(strlen($content1) > 100 and strlen($content2) > 50) { $fp = fopen($cacheName, "w"); if ($fp) { $write = fputs($fp,$content1); $write = fputs($fp,"\n||||||\n"); $write = fputs($fp,$content2."\n"); $write = fputs($fp,"\n||||||\n"); $write = fputs($fp,$content3."\n"); fclose($fp); print "\n"; } else { print "\n"; } $html = $content1."\n||||||\n".$content2."\n||||||\n".$content3."\n"; } else { print "\n"; print "\n"; print "\n"; print "\n"; } } list($content1,$content2,$content3) = explode('||||||',$html); $MAIN = json_decode($content1,true); $FTM = json_decode($content2,true); $radarMsgs = json_decode($content3,true); #print "\n"; #print "\n"; #print "\n\n\n\n"; # Set timezone in PHP5/PHP4 manner if (!function_exists('date_default_timezone_set')) { putenv("TZ=" . $ourTZ); # $Status .= "\n"; } else { date_default_timezone_set("$ourTZ"); # $Status .= "\n"; } if(strlen($html) < 250) { print "\n"; return; } $lastUTCdate = $MAIN['latency']['levelTwoLastReceivedTime']; // print "\n"; $t=strtotime($lastUTCdate); $UTCdate = time(); $LCLdate = date($timeFormat,$UTCdate); print "\n"; $age = $UTCdate - $t; // if ($age < 0) { $age += (60*60*24); } // account for one day extra downtime if need be $ageHMS = gmdate('H:m:s',$age); print "\n"; #preg_match_all('||is',$rec,$matches); $curStatus = $MAIN['rda']['properties']['status']; $statColor = '#33FF33'; # Assume normal color = green if($age >= 5*60) {$statColor = '#FFFF00';} # delayed yellow if($age >= 30*60) {$statColor = '#FF0000';} # inop RED if ($statColor <> '#33FF33') { $curStatus .= ' - Data not recent'; } // Output the status $divStarted = false; if (isset($statColor) and (!$noMsgIfActive or $statColor != '#33FF33') ) { print "
\n"; $divStarted = true; $pAge = ($showHMSAge)?sec2hmsRS($age)." h:m:s":"$age secs"; print "

NEXRAD Radar $myRadar status: $curStatus [last data $pAge ago]
as of $LCLdate

\n"; if (isset($radarMsgs[$myRadar])) { $imsg = 0; foreach ($radarMsgs[$myRadar] as $timestamp => $msg) { $imsg++; if($imsg > $showMsgCnt) {break;} $msg = htmlspecialchars($msg); $msg = preg_replace('|\n|is',"
\n",$msg); print "

Message date: " . date($timeFormat,$timestamp) . "
\n"; print $msg . "

\n"; } } $niceFileName = preg_replace('!&!is','&',$fileName); print "

NWS WSR-88D NEXRADView

\n"; } // end suppress if radar active and $noMsgIfActive == true elseif (isset($statColor) ){ print "\n"; if (isset($radarMsgs[$myRadar])) { foreach ($radarMsgs[$myRadar] as $timestamp => $msg) { $msg = htmlspecialchars($msg); print "\n"; } } } elseif (!isset($statColor) and $age == '') { # no data but found print "\n"; } else { print "

NEXRAD radar $myRadar status not found.

\n"; } if($divStarted) { print "
\n"; } // print footer of page if needed // --------------- customize HTML if you like ----------------------- if (! $includeMode ) { ?> \n"; $ch = curl_init(); // initialize a cURL session curl_setopt($ch, CURLOPT_URL, $theURL); // connect to provided URL curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // don't verify peer certificate curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (radar-status.php - saratoga-weather.org)'); curl_setopt($ch,CURLOPT_HTTPHEADER, // request LD-JSON format array ( "Accept: application/ld+json" )); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $numberOfSeconds); // connection timeout curl_setopt($ch, CURLOPT_TIMEOUT, $numberOfSeconds); // data timeout curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return the data transfer curl_setopt($ch, CURLOPT_NOBODY, false); // set nobody curl_setopt($ch, CURLOPT_HEADER, true); // include header information // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // follow Location: redirect // curl_setopt($ch, CURLOPT_MAXREDIRS, 1); // but only one time if (isset($needCookie[$domain])) { curl_setopt($ch, $needCookie[$domain]); // set the cookie for this request curl_setopt($ch, CURLOPT_COOKIESESSION, true); // and ignore prior cookies $Debug .= "\n"; } $data = curl_exec($ch); // execute session if(curl_error($ch) <> '') { // IF there is an error $Debug .= "\n"; // display error notice } $cinfo = curl_getinfo($ch); // get info on curl exec. /* curl info sample Array ( [url] => http://saratoga-weather.net/clientraw.txt [content_type] => text/plain [http_code] => 200 [header_size] => 266 [request_size] => 141 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 0 [total_time] => 0.125 [namelookup_time] => 0.016 [connect_time] => 0.063 [pretransfer_time] => 0.063 [size_upload] => 0 [size_download] => 758 [speed_download] => 6064 [speed_upload] => 0 [download_content_length] => 758 [upload_content_length] => -1 [starttransfer_time] => 0.125 [redirect_time] => 0 [redirect_url] => [primary_ip] => 74.208.149.102 [certinfo] => Array ( ) [primary_port] => 80 [local_ip] => 192.168.1.104 [local_port] => 54156 ) */ $Debug .= "\n"; //$Debug .= "\n"; curl_close($ch); // close the cURL session //$Debug .= "\n"; $stuff = explode("\r\n\r\n",$data); // maybe we have more than one header due to redirects. $content = (string)array_pop($stuff); // last one is the content $headers = (string)array_pop($stuff); // next-to-last-one is the headers if($cinfo['http_code'] <> '200') { $Debug .= "\n"; } return (array($content,$RC)); // return headers+contents } // end ECF_fetch_URL // ------------------------------------------------------------------ function RS_fetch_microtime() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } //------------------------------------------------------------------------------------------ // function sec2hmsRS ($sec, $padHours = false) { // holds formatted string $hms = ""; if (! is_numeric($sec)) { return($sec); } // there are 3600 seconds in an hour, so if we // divide total seconds by 3600 and throw away // the remainder, we've got the number of hours $hours = intval(intval($sec) / 3600); // add to $hms, with a leading 0 if asked for $hms .= ($padHours) ? str_pad($hours, 2, "0", STR_PAD_LEFT). ':' : $hours. ':'; // dividing the total seconds by 60 will give us // the number of minutes, but we're interested in // minutes past the hour: to get that, we need to // divide by 60 again and keep the remainder $minutes = intval(fmod($sec / 60,60)); // then add to $hms (with a leading 0 if needed) $hms .= str_pad($minutes, 2, "0", STR_PAD_LEFT). ':'; // seconds are simple - just divide the total // seconds by 60 and keep the remainder $seconds = intval($sec % 60); // add to $hms, again with a leading 0 if needed $hms .= str_pad($seconds, 2, "0", STR_PAD_LEFT); // done! return $hms; } // --------------end of functions ---------------------------------------