false AND lastsuccess IS NOT NULL AND (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < (interval + 20) order by elevation desc"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in HTML $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { // Ensure hydro is a proper boolean $line['hydro'] = ($line['hydro'] === 't' || $line['hydro'] === true); // Ensure airport is a proper boolean $line['airport'] = ($line['airport'] === 't' || $line['airport'] === true); $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } // Service: camapi - Returns camera API endpoints with filtering elseif ($service === 'camapi') { $dbconn = getDBConnection(); //cams endpoint if (isset($_GET['cams'])) { if($_GET['lat1']) { $lat1 = getParam('lat1'); if($_GET['lon1']) { $lon1 = getParam('lon1'); if($_GET['lat2']) { $lat2 = getParam('lat2'); if($_GET['lon2']) { $lon2 = getParam('lon2'); if($_GET['elevbottom']) { $elevbottom = getParam('elevbottom'); if($_GET['elevtop']) { $elevtop = getParam('elevtop'); $result = pg_query_params($dbconn, "select camid,url,description from cams where method = 'rtsp' and active = true and cwa = 'RLX' and elevation > $5 and elevation < $6 and (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < (interval + 20) and lat < $1 and lat > $2 and lon < $3 and lon > $4 order by elevation desc", array($lat1,$lat2,$lon1,$lon2,$elevbottom,$elevtop)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } } } } } } } // camstatic endpoint if (isset($_GET['camstatic'])) { if($_GET['lat1']) { $lat1 = getParam('lat1'); if($_GET['lon1']) { $lon1 = getParam('lon1'); if($_GET['radius']) { $radius = getParam('radius'); $rad = $radius / 70; $lat1 = floatval($lat1); $lon1 = floatval($lon1); $radius = floatval($rad); $query = "select * from cams where method = 'rtsp' and active = true and cwa = 'RLX' and (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < (interval + 20) and st_dwithin(geom, ST_SetSRID(ST_Point(" . strval($lon1) . ", " . strval($lat1) . "), 4326)," . strval($radius) . ") order by elevation desc"; $result = pg_query($dbconn,$query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } } } } // camdb endpoint if (isset($_GET['camdb'])) { $result = pg_query($dbconn, "SELECT COUNT(*) FROM camdb") or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } } // Service: camlist - Returns camera list with active status elseif ($service === 'camlist') { header('Content-Type: application/json; charset=utf-8'); // Initialize response array $response = []; // Database connection try { $dbconn = getDBConnection(); // Performing SQL query $query = "SELECT url, lat, lon, elevation, county, state, active, aspect, bloomsky, source, method FROM cams where active = true"; $result = pg_query($dbconn, $query); if (!$result) { throw new Exception('Query failed: ' . pg_last_error()); } // Fetch results $data = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { // Ensure numeric values are properly typed $line['lat'] = floatval($line['lat']); $line['lon'] = floatval($line['lon']); $line['elevation'] = floatval($line['elevation']); $line['active'] = $line['active'] === 't' ? true : false; // Convert PostgreSQL boolean $data[] = $line; } $response = [ 'status' => 'success', 'data' => $data, 'count' => count($data) ]; // Free resultset pg_free_result($result); } catch (Exception $e) { http_response_code(500); $response = [ 'status' => 'error', 'message' => $e->getMessage() ]; } // Output JSON echo json_encode($response, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK); } // Service: admin - Admin operations for cameras elseif ($service === 'admin') { $dbconn = getDBConnection(); $action = $_GET['action']; if ($action == 'checkurl' ) { $url = $_POST['url']; $query = "SELECT exists (SELECT 1 FROM cams WHERE url = '{$url}')"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } if ($action == 'newcam' ) { $url = $_POST['url']; $lat = $_POST['lat']; $lon = $_POST['lon']; $desc = $_POST['description']; $method = $_POST['method']; $permission = $_POST['permission']; $owner = $_POST['owner']; $email = $_POST['email']; $query = "INSERT into cams (url,lat,lon,description,interval,method,active,permission,owner,email,keephours) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)"; $result = pg_query_params($query, Array($url,$lat,$lon,$desc,'10',$method,'t',$permission,$owner,$email,'240')) or die('Query failed: ' . pg_last_error()); $status = pg_result_status($result); echo json_encode($status); pg_free_result($result); shell_exec('python3 /var/www/html/work/runallgeom.py'); } } // Service: camcircle - Returns camera coverage circles elseif ($service === 'camcircle') { $dbconn = getDBConnection(); // Performing SQL query $query = "WITH subquery_points AS ( SELECT geom AS point_geometry FROM cams WHERE active = true and lastsuccess IS NOT NULL AND (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < 60 ) SELECT jsonb_build_object( 'type', 'FeatureCollection', 'features', jsonb_agg(jsonb_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(ST_Buffer(point_geometry::geography, 8000))::jsonb )) ) AS feature_collection FROM subquery_points"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in HTML while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } print_r($array[0]['feature_collection']); // Free resultset pg_free_result($result); } // Service: db - Returns weather station data elseif ($service === 'db') { $dbconn = getDBConnection(); //no gets, current point outage info if (empty(array_diff_key($_GET, array('service' => '')))) { try { $query = " SELECT stationid, lat, lon, tempf, dewpt, preciptotal, winddir, windspd, windgust, elev, adm1, adm2, neighborhood, maxt, mint, pressure, lastob, county, rain24, rain3, rain6, windmax, cwa FROM ( SELECT DISTINCT ON (stationid) * FROM wusites WHERE active = TRUE AND cwa = 'RLX' AND lastob BETWEEN timezone('utc', NOW()) - INTERVAL '0.5 hours' AND timezone('utc', NOW()) ) p ORDER BY lastob DESC "; $result = pg_query($dbconn, $query); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => $e->getMessage()]); exit; } } if (isset($_GET['outsideold'])) { $query = "SELECT stationid, lat, lon, tempf, dewpt,preciptotal,winddir,windspd,windgust,elev,adm1,adm2,neighborhood,maxt,mint,pressure,lastob,county,rain24,rain3,rain6,windmax,cwa FROM (SELECT DISTINCT ON (stationid) * FROM wusites WHERE (active = TRUE) and lastob BETWEEN timezone('utc', now()) - INTERVAL '.5 HOURS'AND timezone('utc', now())) p ORDER BY lastob desc;"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in HTML while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } if (isset($_GET['outside'])) { try { $query = " SELECT stationid, lat, lon, tempf, dewpt, preciptotal, winddir, windspd, windgust, elev, adm1, adm2, neighborhood, maxt, mint, pressure, lastob, county, rain24, rain3, rain6, windmax, cwa FROM ( SELECT DISTINCT ON (stationid) * FROM wusites WHERE active = TRUE AND lastob BETWEEN timezone('utc', NOW()) - INTERVAL '0.5 hours' AND timezone('utc', NOW()) ) p ORDER BY lastob DESC "; $result = pg_query($dbconn, $query); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => $e->getMessage()]); exit; } } } // Service: fire - Returns fire information elseif ($service === 'fire') { $dbconn = getDBConnection(); //no gets, current point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(geom)::json,'properties',json_build_object('incname',incname,'discovery',discovery,'modified',modified,'age',age,'dailyacres',dailyacres,'type',type,'contained',contained,'personnel',personnel))order by modified asc)) FROM fire where type = $1 and contained <> 100 and modified > now() - interval '36 hours'", array('WF')) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } // Service: individualcam - Returns individual camera images elseif ($service === 'individualcam') { $dbconn = getDBConnection(); $camid = getParam('camid'); if(getParam('dtg')){ $endtime = getParam('dtg'); if(isset($_GET['camimages'])){ $camimages = $_GET['camimages']; } if(!isset($_GET['camimages'])){ $camimages = 20; } $query = "SELECT camid, filepath, date_trunc('second', dateutc) as dateutc FROM camdb where camid = '{$camid}' and dateutc < '{$endtime}' order by dateutc desc limit '{$camimages}'"; } $camimages = 20; //if(!isset($_GET['dtg'])) { if (!isset($_GET['dtg'])) { if(isset($_GET['camimages'])){ $camimages = $_GET['camimages']; } $query = "SELECT camid, filepath, date_trunc('second', dateutc) as dateutc FROM camdb where camid = '{$camid}' order by dateutc desc limit '{$camimages}'"; } $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } // Service: lsr - Returns local storm reports elseif ($service === 'lsr') { $dbconn = getDBConnection(); if (isset($_GET['ohgo'])) { $query = "SELECT jsonb_build_object( 'type', 'FeatureCollection', 'features', jsonb_agg( jsonb_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(geom)::jsonb, 'properties', jsonb_build_object( 'lat', lat, 'lon', lon, 'description', description, 'roadstatus', roadstatus, 'start', start, 'lastupdate', lastupdate ) ) ) ) as geojson FROM public.ohgo WHERE endtime IS NULL AND lastupdate > NOW() - INTERVAL '2 hours'"; // Prepare and execute the query $result = pg_query($dbconn, $query); if (!$result) { header('Content-Type: application/json'); echo json_encode(['error' => 'Query failed: ' . pg_last_error()]); exit; } // Fetch the result $resultArray = pg_fetch_all($result); // Check if we got results if ($resultArray && isset($resultArray[0]['geojson'])) { header('Content-Type: application/json'); echo $resultArray[0]['geojson']; // Direct output since it's already JSON from jsonb_build_object } else { header('Content-Type: application/json'); echo json_encode(['error' => 'No results found']); } // Free result and close connection pg_free_result($result); } if (isset($_GET['ohgotable'])) { // Performing SQL query $query = "SELECT CASE WHEN COALESCE(lsr, FALSE) THEN 'true' ELSE 'false' END AS lsr, CASE WHEN COALESCE(hide, FALSE) THEN 'true' ELSE 'false' END AS hide, ROUND(ST_Y(geom)::numeric, 3) AS lat, ROUND(ST_X(geom)::numeric, 3) AS lon, id, category, roadstatus, cwa, county, state, location, routename, description, TO_CHAR(start, 'YYYY-MM-DD HH24:MI') AS start, TO_CHAR(endtime, 'YYYY-MM-DD HH24:MI') AS endtime, TO_CHAR(lastupdate, 'YYYY-MM-DD HH24:MI') AS lastupdate FROM ohgo WHERE (endtime IS NULL OR endtime > NOW() - INTERVAL '48 hours') and start > now() - interval '144 hours' ORDER BY start ASC "; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in JSON $array = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } if (isset($_GET['lsrohgo'])) { if ($_GET['lsrohgo'] == 'true') { $lsrflag = 'true'; } else { $lsrflag = 'false'; } $id = (int) $_GET['id']; $query = "UPDATE ohgo SET lsr = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($lsrflag, $id)) or die('Query failed: ' . pg_last_error()); pg_free_result($result); } if (isset($_GET['ohgohide'])) { if ($_GET['ohgohide'] == 'true') { $lsrflag = 'true'; } else { $lsrflag = 'false'; } $id = (int) $_GET['id']; $query = "UPDATE ohgo SET hide = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($lsrflag, $id)) or die('Query failed: ' . pg_last_error()); pg_free_result($result); } //take vtec, return start, end, polygon, outages in polygon, outages in buffer, warntype, polygon pop if (isset($_GET['vtec'])) { $vtec = $_GET['vtec']; $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json, 'properties', json_build_object( 'id', warnindex, 'issue', issue, 'endtime', endtime, 'warntype', warntype, 'issue', issue, 'outagesvalid', outagesvalid, 'outagesbuffer', outagesbuffer, 'polygonpop', polygonpop, 'lat', st_y(st_centroid(nwspoly)), 'lon', st_x(st_centroid(nwspoly)), 'vtec', vtec ) ) ) ) FROM svr WHERE vtec = $1; "; // Prepare and execute the query using pg_query_params $result = pg_query_params($dbconn, $query, array($vtec)) or die('Query failed: ' . pg_last_error()); // Fetch the result $resultArray = pg_fetch_all($result); // Output the JSON object echo($resultArray[0]['json_build_object']); // Free result pg_free_result($result); } //Get reports pre-flagged with the vtec if (isset($_GET['preflagreports'])) { $vtec = $_GET['preflagreports']; $query = "SELECT * from reports WHERE severe = $1"; $result = pg_query_params($dbconn, $query, array($vtec)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } //Get reports within polygon for arbitary time after end time if (isset($_GET['reports'])) { $vtec = $_GET['reports']; if (isset($_GET['hours'])) { $hours = $_GET['hours']; } else { $hours = 6; } //echo $hours; $query = "SELECT * from reports,svr where ST_Contains(svr.nwspoly, reports.geom) and vtec = $1 and reports.initialdtg AT TIME ZONE 'America/New_York' > svr.issue AND reports.initialdtg AT TIME ZONE 'America/New_York' < svr.issue + (INTERVAL '1 h' * $2)"; $result = pg_query_params($dbconn, $query, array($vtec,$hours)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } //Get point power outages within polygon + arb time, default 60 minutes if (isset($_GET['outages'])) { $vtec = $_GET['outages']; if (isset($_GET['hours'])) { $hours = $_GET['hours']; } else { $hours = 1; } //echo $hours; $query = "SELECT power.lat,power.lon,power.peakoutage,power.cause,power.derivedstart,power.lastchange from power,svr where ST_Contains(svr.nwspoly, power.realgeom) and vtec = $1 and derivedstart > svr.issue AND derivedstart < svr.issue + (INTERVAL '1 h' * $2)"; $result = pg_query_params($dbconn, $query, array($vtec,$hours)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } //no gets, current point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(geom)::json,'properties',json_build_object('id',id,'time',initialdtg,'county',county,'state',state,'issue',issue,'rawemail',rawemail,'place',place,'comments',comments)) order by initialdtg desc)) FROM reports where initialdtg > $1 ", array('2024-06-07')) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); pg_free_result($result); } //For real time mapping if(isset($_GET['verify'])) { $query = "select id, lat::Numeric(16,3), lon::Numeric(16,3),issue,to_char(initialdtg, 'yyyy/mm/dd hh24:mi') as initialdtg,rawemail,concat(county,' ',state,'\n',place) as place,comments,lsr::text,severe from reports where severe is not null and processed = true order by initialdtg desc"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } //no gets, current point outage info if (isset($_GET['rtcad'])) { if (isset($_GET['hours'])) { $hours = $_GET['hours']; } else { $hours = 6; } $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(geom)::json, 'properties', json_build_object( 'id', id, 'time', utcinitialdtg, 'county', county, 'state', state, 'issue', issue, 'rawemail', rawemail, 'place', place, 'comments', comments ) ) ORDER BY initialdtg DESC ) ) FROM reports WHERE lat is not null and utcinitialdtg >= NOW() - INTERVAL '1 hour' * $1; "; // Prepare and execute the query using pg_query_params $result = pg_query_params($dbconn, $query, array($hours)) or die('Query failed: ' . pg_last_error()); // Fetch the result $resultArray = pg_fetch_all($result); // Output the JSON object echo($resultArray[0]['json_build_object']); // Free result pg_free_result($result); } //Stats if (isset($_GET['stats'])) { $query = "SELECT county, state, MAX(emailtime), count(*) FROM reports where county is not null and (state = 'WV' or state = 'VA' or state = 'KY' or state = 'OH') GROUP BY county, state"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } //Get METAR Array for Jelly Bean if (isset($_GET['metars'])) { if (isset($_GET['start'])) { $start = $_GET['start']; } if (isset($_GET['end'])) { $end = $_GET['end']; } $query = "SELECT icao,temp,dewp,wx,precip1,precip3,precip6,raw,obtime,stationname,lat,lon from metars where obtime - interval '45 minutes' > $1 and obtime < $2 order by lon asc"; $result = pg_query_params($dbconn, $query, array($start,$end)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } //echo($array); echo json_encode($array); pg_free_result($result); } if (isset($_GET['news'])) { //$query = "SELECT headline, summary, imageurl, source, storylink, updated from news where notrelevant is not true"; $query = " SELECT *, CASE WHEN concat(summary, ' ', headline) ILIKE ANY (ARRAY[ '%weather%', '%flood%', '%fire%', '%fog%', '%snow%', '%emergency%' '%wind%', '%ice%', '%rain%', '%power%', '%explosion%', '%drown%', '%stream%', '%river%', '%air%', '%wind%', '%river%', '%ice%', '%creek%', '%crash%', '%thunder%', '%fog%', '%spill%', '%pileup%', '%pile-up%', '%gust%', '%fatal%', '%injury%', '%sleet%', '%injured%', '%frost%', '%culvert%', '%slippery%', '%wildfire%', '%tornado%', '%thunderstorm%', '%downburst%', '%microburst%', '%crash%', '%heatstroke%', '%derecho%' '%lightning%', '%hypothermia%', '%slide%', '%flow%', '%ski%', '%water%', '%innundation%' ]) THEN 2 WHEN concat(summary, ' ', headline) ILIKE ANY (ARRAY[ '%legislative%','%history%','%budget%','%birthday%','%banning%','%academic%','%tuna%','%Service Forecast%', '%DOGE%','%demonstrators%','%forum%','%health%','%fraud%','%birthday%', '%egg%', '%eggs%', '%collector%', '%church%', ' %crypto%' ]) THEN 0 ELSE 1 END AS relevance_level FROM news WHERE timeutc > NOW() - INTERVAL '18 hours' ORDER BY relevance_level DESC, timeutc DESC "; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } if (isset($_GET['news2'])) { $query = " SELECT *, (SELECT COUNT(*) FROM unnest(ARRAY[ 'weather', 'flood', 'fire', 'fog', 'snow', 'emergency', 'wind', 'ice', 'rain', 'power', 'explosion', 'warmer', 'colder', 'drown', 'stream', 'river', 'air', 'wind', 'destroyed', 'rime', 'glaze', 'river', 'ice', 'creek', 'crash', 'thunder', 'spinup', 'black ice', 'aircraft', 'fog', 'spill', 'pileup', 'pile-up', 'gust', 'frozen', 'funnel', 'rainfall', 'fatal', 'injury', 'sleet', 'injured', 'frost', 'dead', 'death', 'landslide', 'culvert', 'slippery', 'wildfire', 'tornado', 'blizzard', 'creek', 'hail', 'thunderstorm', 'downburst', 'microburst', 'crash', 'heatstroke', 'derecho', 'lightning', 'hypothermia', 'slide', 'flow', 'ski', 'water', 'inundation', 'victim', 'victims', 'flooding','flooded','snowing','freezing rain','clouds','cloud','storm' ]) AS pattern WHERE concat(summary, ' ', headline) ~* ('\y' || pattern || '\y')) AS match_count FROM news WHERE timeutc > NOW() - INTERVAL '18 hours' ORDER BY nlpscore DESC, timeutc DESC "; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } if (isset($_GET['news3old'])) { $query = " SELECT * FROM news WHERE (timeutc > NOW() - INTERVAL '24 hours' and nlpscore > 0.1) or (timeutc > NOW() - INTERVAL '6 hours') ORDER BY nlpscore DESC, timeutc DESC"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } if (isset($_GET['news3'])) { // Corrected query with NULLS LAST $query = " SELECT * FROM news WHERE (timeutc > NOW() - INTERVAL '24 hours' AND impact_score > 25) OR (timeutc > NOW() - INTERVAL '6 hours' and impact_score > 0) ORDER BY impact_score DESC NULLS LAST, timeutc DESC limit 50"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); $array = []; // It's good practice to initialize the array while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } // --- FIXED SECTION: newsarchive --- if (isset($_GET['newsarchive'])) { // Initialize variables $start = isset($_GET['start']) ? $_GET['start'] : null; $end = isset($_GET['end']) ? $_GET['end'] : null; $keys = isset($_GET['key']) ? $_GET['key'] : []; // Convert keys to an array if it's a string if (is_string($keys)) { $keys = explode(',', $keys); } $patterns = array_map(function($term) { return trim($term); }, $keys); // Handle case with no search terms if (empty($patterns)) { $query = "SELECT * FROM news"; $params = []; } else { // Build parameter placeholders $placeholders = []; for ($i = 1; $i <= count($patterns); $i++) { // FIXED: Use concatenation to create "$1::text" $placeholders[] = '$' . $i . '::text'; } $placeholder_string = implode(',', $placeholders); $query = " SELECT n.*, ( SELECT COUNT(*) FROM unnest(ARRAY[{$placeholder_string}]::text[]) AS pattern WHERE concat(n.summary, ' ', n.headline) ILIKE pattern ) AS match_count FROM news n WHERE concat(summary, ' ', headline) ILIKE ANY (ARRAY[{$placeholder_string}]::text[]) "; $params = array_map(function($term) { return "%{$term}%"; }, $patterns); } // Add date filters if provided $param_count = count($patterns); if ($start) { $param_count++; // FIXED: Use concatenation instead of deprecated interpolation $query .= " AND timeutc >= $" . $param_count; $params[] = $start; } if ($end) { $param_count++; // FIXED: Use concatenation instead of deprecated interpolation $query .= " AND timeutc <= $" . $param_count; $params[] = $end; } $query .= " ORDER BY match_count DESC, timeutc desc"; // Execute query $result = pg_query_params($query, $params) or die('Query failed: ' . pg_last_error()); $array = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); } if (isset($_GET['wv511'])) { $query = "SELECT jsonb_build_object( 'type', 'FeatureCollection', 'features', jsonb_agg( jsonb_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(geom)::jsonb, 'properties', jsonb_build_object( 'type', name, 'reported', first_seen, 'end', last_seen_in_feed, 'county', county, 'state', st, 'remark', latest_description, 'lat', st_y(st_centroid(geom)), 'lon', st_x(st_centroid(geom)) ) ) ) ) as geojson FROM public.wv511 WHERE last_updated > NOW() - INTERVAL '2 hours'"; // Prepare and execute the query $result = pg_query($dbconn, $query); if (!$result) { header('Content-Type: application/json'); echo json_encode(['error' => 'Query failed: ' . pg_last_error()]); exit; } // Fetch the result $resultArray = pg_fetch_all($result); // Check if we got results if ($resultArray && isset($resultArray[0]['geojson'])) { header('Content-Type: application/json'); echo $resultArray[0]['geojson']; // Direct output since it's already JSON from jsonb_build_object } else { header('Content-Type: application/json'); echo json_encode(['error' => 'No results found']); } // Free result and close connection pg_free_result($result); } if (isset($_GET['ky511'])) { $query = "SELECT jsonb_build_object( 'type', 'FeatureCollection', 'features', jsonb_agg( jsonb_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(geom)::jsonb, 'properties', jsonb_build_object( 'reported', first_seen, 'end', last_seen_in_feed, 'county', county, 'state', st, 'remark', latest_description, 'lat', st_y(st_centroid(geom)), 'lon', st_x(st_centroid(geom)) ) ) ) ) as geojson FROM ky511.ky511 WHERE last_updated > NOW() - INTERVAL '2 hours'"; // Prepare and execute the query $result = pg_query($dbconn, $query); if (!$result) { header('Content-Type: application/json'); echo json_encode(['error' => 'Query failed: ' . pg_last_error()]); exit; } // Fetch the result $resultArray = pg_fetch_all($result); // Check if we got results if ($resultArray && isset($resultArray[0]['geojson'])) { header('Content-Type: application/json'); echo $resultArray[0]['geojson']; // Direct output since it's already JSON from jsonb_build_object } else { header('Content-Type: application/json'); echo json_encode(['error' => 'No results found']); } // Free result and close connection pg_free_result($result); } if (isset($_GET['getCombinedTable'])) { // Combined SQL query using UNION ALL with CAST for 'id' $query = " SELECT * FROM ( -- OHGO Query SELECT 'ohgo' AS source, CASE WHEN COALESCE(lsr, FALSE) THEN 'true' ELSE 'false' END AS lsr, CASE WHEN COALESCE(hide, FALSE) THEN 'true' ELSE 'false' END AS hide, ROUND(ST_Y(geom)::numeric, 3) AS lat, ROUND(ST_X(geom)::numeric, 3) AS lon, CAST(id AS TEXT) AS id, -- Cast id to TEXT category, roadstatus, cwa, county, state, location, routename, description, TO_CHAR(start, 'YYYY-MM-DD HH24:MI') AS start, TO_CHAR(endtime, 'YYYY-MM-DD HH24:MI') AS endtime, TO_CHAR(lastupdate, 'YYYY-MM-DD HH24:MI') AS lastupdate FROM ohgo WHERE (endtime IS NULL OR endtime > NOW() - INTERVAL '24 hours') AND start > now() - interval '144 hours' UNION ALL -- WV511 Query SELECT 'wv511' AS source, CASE WHEN COALESCE(lsr, FALSE) THEN 'true' ELSE 'false' END AS lsr, CASE WHEN COALESCE(hide, FALSE) THEN 'true' ELSE 'false' END AS hide, ROUND(ST_Y(geom)::numeric, 3) AS lat, ROUND(ST_X(geom)::numeric, 3) AS lon, CAST(id AS TEXT) AS id, -- Cast id to TEXT wv511.name as category, NULL AS roadstatus, cwa, county, st as state, 'Map Link' AS location, NULL AS routename, latest_description as description, TO_CHAR(first_seen, 'YYYY-MM-DD HH24:MI') AS start, TO_CHAR(last_seen_in_feed, 'YYYY-MM-DD HH24:MI') AS endtime, TO_CHAR(last_updated, 'YYYY-MM-DD HH24:MI') AS lastupdate FROM wv511 WHERE (last_seen_in_feed IS NULL OR last_seen_in_feed > NOW() - INTERVAL '24 hours') AND first_seen > now() - interval '144 hours' and wv511.name !~ 'Crash' and wv511.name !~ 'Vehicle' and wv511.name !~ 'Dead Animal' and wv511.name !~ 'Debris in Roadway' and wv511.name !~ 'Congestion-Delay' and wv511.name !~ 'Pot hole' and wv511.name !~ 'Debris On Bridge' and wv511.name !~ 'Attenuator' and wv511.name !~ 'Pedestrian' and wv511.name !~ 'Bridge Closed' and wv511.name !~ 'Truck on escape' and wv511.name !~ 'Bridge Incident' and wv511.name !~ 'Escape Ramp' AND wv511.name !~ 'Signal' UNION ALL -- KY511 Query SELECT 'ky511.ky511' AS source, CASE WHEN COALESCE(lsr, FALSE) THEN 'true' ELSE 'false' END AS lsr, CASE WHEN COALESCE(hide, FALSE) THEN 'true' ELSE 'false' END AS hide, ROUND(ST_Y(geom)::numeric, 3) AS lat, ROUND(ST_X(geom)::numeric, 3) AS lon, CAST(id AS TEXT) AS id, -- Cast id to TEXT 'Weather' as category, NULL AS roadstatus, cwa, county, st as state, 'Map Link' AS location, NULL AS routename, latest_description as description, TO_CHAR(first_seen, 'YYYY-MM-DD HH24:MI') AS start, TO_CHAR(last_seen_in_feed, 'YYYY-MM-DD HH24:MI') AS endtime, TO_CHAR(last_updated, 'YYYY-MM-DD HH24:MI') AS lastupdate FROM ky511.ky511 WHERE (last_seen_in_feed IS NULL OR last_seen_in_feed > NOW() - INTERVAL '24 hours') AND first_seen > now() - interval '144 hours' ) AS combined_data ORDER BY start ASC; "; // Execute the query $result = pg_query($dbconn, $query); // Set header before any output header('Content-Type: application/json'); if (!$result) { // Output error as JSON echo json_encode(['error' => 'Combined query failed: ' . pg_last_error($dbconn)]); // Close connection if needed // pg_close($dbconn); exit; } // Fetch results into an array $dataArray = []; while ($row = pg_fetch_assoc($result)) { $dataArray[] = $row; } if ($dataArray === false) { echo json_encode(['error' => 'Failed to fetch results.']); pg_free_result($result); // pg_close($dbconn); exit; } // Output the combined results as JSON echo json_encode($dataArray); // Free result memory pg_free_result($result); // Optionally close the connection // pg_close($dbconn); exit; // Stop script execution } if (isset($_GET['updater'])) { $allowedTables = ['wv511', 'ky511.ky511', 'ohgo']; if (isset($_GET['lsr']) && isset($_GET['id']) && isset($_GET['table'])) { // --- Handle LSR Update --- $requestedTable = $_GET['table']; $lsrInput = $_GET['lsr']; $idInput = $_GET['id']; // ID validation missing here, see note below if (!in_array($requestedTable, $allowedTables)) { http_response_code(400); die('Error (LSR): Invalid table specified.'); } $lsrflag = ($lsrInput === 'true') ? 'true' : 'false'; $id = $idInput; // WARNING: ID is not validated/sanitized here for LSR! $tableNameEscaped = $requestedTable; // Note: {$tableNameEscaped} is VALID complex syntax. The issue was with ${var} $query = "UPDATE {$tableNameEscaped} SET lsr = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($lsrflag, $id)); if ($result) { $affectedRows = pg_affected_rows($result); echo "LSR Update successful for table '{$requestedTable}'. {$affectedRows} row(s) affected for ID {$id}."; } else { http_response_code(500); error_log("LSR Query failed for table '{$requestedTable}', ID {$id}: " . pg_last_error($dbconn)); die('Error: The LSR update query failed.'); } } else if (isset($_GET['hide']) && isset($_GET['id']) && isset($_GET['table'])) { // --- Handle Hide Update --- $requestedTable = $_GET['table']; $hideInput = $_GET['hide']; $idInput = $_GET['id']; if (!in_array($requestedTable, $allowedTables)) { http_response_code(400); die('Error (Hide): Invalid table specified.'); } $hideflag = ($hideInput === 'true') ? 'true' : 'false'; // Use INT validation for ID here - make sure this matches your DB column type $id = $idInput; $tableNameEscaped = $requestedTable; $query = "UPDATE {$tableNameEscaped} SET hide = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($hideflag, $id)); if ($result) { $affectedRows = pg_affected_rows($result); echo "Hide Update successful for table '{$requestedTable}'. {$affectedRows} row(s) affected for ID {$id}."; } else { http_response_code(500); error_log("Hide Query failed for table '{$requestedTable}', ID {$id}: " . pg_last_error($dbconn)); die('Error: The Hide update query failed.'); } } else { // --- Handle Missing Parameters Error --- // Neither 'lsr' nor 'hide' (along with id and table) were provided correctly. http_response_code(400); // Bad Request // Check which parameters *are* present to give a potentially more helpful error $missing = []; if (!isset($_GET['id'])) $missing[] = '"id"'; if (!isset($_GET['table'])) $missing[] = '"table"'; if (!isset($_GET['lsr']) && !isset($_GET['hide'])) { $missing[] = 'action ("lsr" or "hide")'; } else if (isset($_GET['lsr']) && (!isset($_GET['id']) || !isset($_GET['table']))) { // LSR was specified, but others missing } else if (isset($_GET['hide']) && (!isset($_GET['id']) || !isset($_GET['table']))) { // Hide was specified, but others missing } if (!empty($missing)) { die('Error: Missing required parameter(s): ' . implode(', ', $missing) . '. Please provide a valid action ("lsr" or "hide"), "id", and "table".'); } else { // Should ideally not happen with the logic above, but as a fallback: die('Error: Invalid request parameters. Please provide action ("lsr" or "hide"), "id", and "table".'); } } } } // Service: nws - Returns NWS personnel stats elseif ($service === 'nws') { $dbconn = getDBConnection(); //no gets, current point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $query = "SELECT * FROM nws order by lastupdate asc"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } if (isset($_GET['officestats11'])) { // Get all unique lastupdate dates from the database $date_query = "SELECT DISTINCT DATE(lastupdate) as unique_date FROM nws WHERE status = 'active' AND office ~ 'WFO' ORDER BY unique_date ASC"; // Changed from DESC to ASC $date_result = pg_query($dbconn, $date_query) or die('Date query failed: ' . pg_last_error()); $datetime_points = []; while ($row = pg_fetch_array($date_result, null, PGSQL_ASSOC)) { $dt = DateTime::createFromFormat('Y-m-d', $row['unique_date']); $dt->setTime(23, 59, 59); $datetime_points[] = $dt->format('Y-m-d H:i:s'); } pg_free_result($date_result); if (empty($datetime_points)) { echo json_encode(['error' => 'No valid datetime points found in database']); exit; } // Debug: Log the datetime points error_log("Processed datetime points: " . implode(', ', $datetime_points)); $query = "WITH latest_records AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY personid ORDER BY ABS(EXTRACT(EPOCH FROM (lastupdate - CAST($1 AS TIMESTAMP)))) ASC) AS rn FROM nws WHERE status = 'active' AND lastupdate <= CAST($1 AS TIMESTAMP) + INTERVAL '1 day' AND lastupdate >= CAST($1 AS TIMESTAMP) - INTERVAL '3 days' AND office ~ 'WFO' ), otitle_counts AS ( SELECT office, otitle, COUNT(*) AS otitle_count FROM latest_records WHERE rn = 1 GROUP BY office, otitle ) SELECT lr.office, COUNT(DISTINCT lr.personid) AS unique_person_count, (SELECT ARRAY_AGG(json_obj ORDER BY json_obj->>'otitle' ASC) FROM (SELECT DISTINCT jsonb_build_object( 'otitle', tc2.otitle, 'count', tc2.otitle_count ) AS json_obj FROM otitle_counts tc2 WHERE tc2.office = lr.office) AS subquery ) AS title_counts_array FROM latest_records lr WHERE lr.rn = 1 GROUP BY lr.office ORDER BY unique_person_count DESC"; $results_array = []; // Execute query for each unique datetime foreach ($datetime_points as $datetime) { $result = pg_query_params($dbconn, $query, array($datetime)) or die('Query failed: ' . pg_last_error()); $office_data = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $office_data[] = $line; } // Store results with the datetime used $results_array[] = [ 'provided_datetime' => $datetime, 'data' => $office_data ]; pg_free_result($result); } // Debug: Log before output error_log("Final results: " . json_encode($results_array)); // Return JSON encoded results echo json_encode($results_array); } if (isset($_GET['officestats'])) { if (isset($_GET['datetime'])) { // Expecting datetime as comma-separated dates or JSON array $input_dates = is_array($_GET['datetime']) ? $_GET['datetime'] : explode(',', $_GET['datetime']); // Process each date and set to end of day $input_dates = array_unique($input_dates); $input_dates = array_values($input_dates); $datetime_points = []; foreach ($input_dates as $date) { $dt = DateTime::createFromFormat('m-d-Y', trim($date)); if ($dt === false) { error_log("Invalid date skipped: " . trim($date)); continue; } $dt->setTime(23, 59, 59); $datetime_points[] = $dt->format('Y-m-d H:i:s'); } // Ensure uniqueness and reindex $datetime_points = array_unique($datetime_points); $datetime_points = array_values($datetime_points); // Debug: Log the datetime points error_log("Processed datetime points: " . implode(', ', $datetime_points)); if (empty($datetime_points)) { echo json_encode(['error' => 'No valid datetime points provided']); exit; } $query = "WITH latest_records AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY personid ORDER BY ABS(EXTRACT(EPOCH FROM (lastupdate - CAST($1 AS TIMESTAMP)))) ASC) AS rn FROM nws WHERE status = 'active' AND lastupdate <= CAST($1 AS TIMESTAMP) + INTERVAL '1 day' AND lastupdate >= CAST($1 AS TIMESTAMP) - INTERVAL '3 days' AND office ~ 'WFO' ), otitle_counts AS ( SELECT office, otitle, COUNT(*) AS otitle_count FROM latest_records WHERE rn = 1 GROUP BY office, otitle ) SELECT lr.office, COUNT(DISTINCT lr.personid) AS unique_person_count, (SELECT ARRAY_AGG(json_obj ORDER BY json_obj->>'otitle' ASC) FROM (SELECT DISTINCT jsonb_build_object( 'otitle', tc2.otitle, 'count', tc2.otitle_count ) AS json_obj FROM otitle_counts tc2 WHERE tc2.office = lr.office) AS subquery ) AS title_counts_array FROM latest_records lr WHERE lr.rn = 1 GROUP BY lr.office ORDER BY unique_person_count DESC"; $results_array = []; // Execute query for each provided datetime foreach ($datetime_points as $datetime) { $result = pg_query_params($dbconn, $query, array($datetime)) or die('Query failed: ' . pg_last_error()); $office_data = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $office_data[] = $line; } // Store results with the datetime used $results_array[] = [ 'provided_datetime' => $datetime, 'data' => $office_data ]; pg_free_result($result); } // Debug: Log before output error_log("Final results: " . json_encode($results_array)); // Return JSON encoded results echo json_encode($results_array); } } if (isset($_GET['regionstats'])) { if (isset($_GET['datetime'])) { // Expecting datetime as comma-separated dates or JSON array $input_dates = is_array($_GET['datetime']) ? $_GET['datetime'] : explode(',', $_GET['datetime']); // Process each date and set to end of day $input_dates = array_unique($input_dates); $input_dates = array_values($input_dates); $datetime_points = []; foreach ($input_dates as $date) { $dt = DateTime::createFromFormat('m-d-Y', trim($date)); if ($dt === false) { error_log("Invalid date skipped: " . trim($date)); continue; } $dt->setTime(23, 59, 59); $datetime_points[] = $dt->format('Y-m-d H:i:s'); } // Ensure uniqueness and reindex $datetime_points = array_unique($datetime_points); $datetime_points = array_values($datetime_points); // Debug: Log the datetime points error_log("Processed datetime points: " . implode(', ', $datetime_points)); if (empty($datetime_points)) { echo json_encode(['error' => 'No valid datetime points provided']); exit; } $query = "WITH latest_records AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY personid ORDER BY ABS(EXTRACT(EPOCH FROM (lastupdate - CAST($1 AS TIMESTAMP)))) ASC) AS rn, SUBSTRING(office FROM 'NWS/([EWPASC]R)') AS region FROM nws WHERE lastupdate <= CAST($1 AS TIMESTAMP) + INTERVAL '1 day' - INTERVAL '1 second' AND lastupdate >= CAST($1 AS TIMESTAMP) - INTERVAL '3 days' AND office ~ 'NWS/[EWPASC]R' and status = 'active' ), otitle_counts AS ( SELECT region, otitle, COUNT(*) AS otitle_count FROM latest_records WHERE rn = 1 GROUP BY region, otitle ) SELECT lr.region, COUNT(DISTINCT lr.personid) AS unique_person_count, (SELECT ARRAY_AGG(json_obj ORDER BY json_obj->>'otitle' ASC) FROM (SELECT DISTINCT jsonb_build_object( 'otitle', tc2.otitle, 'count', tc2.otitle_count ) AS json_obj FROM otitle_counts tc2 WHERE tc2.region = lr.region) AS subquery ) AS title_counts_array FROM latest_records lr WHERE lr.rn = 1 GROUP BY lr.region ORDER BY unique_person_count DESC"; $results_array = []; // Execute query for each provided datetime foreach ($datetime_points as $datetime) { $result = pg_query_params($dbconn, $query, array($datetime)) or die('Query failed: ' . pg_last_error()); $office_data = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $office_data[] = $line; } // Store results with the datetime used $results_array[] = [ 'provided_datetime' => $datetime, 'data' => $office_data ]; pg_free_result($result); } // Debug: Log before output //error_log("Final results: " . json_encode($results_array)); // Return JSON encoded results echo json_encode($results_array); } } if (isset($_GET['drilldown'])) { if (isset($_GET['datetime'])) { // Expecting datetime as comma-separated dates or JSON array $input_dates = is_array($_GET['datetime']) ? $_GET['datetime'] : explode(',', $_GET['datetime']); // Process each date and set to end of day $datetime_points = []; foreach ($input_dates as $date) { // Specify the exact format of your input date string $dt = DateTime::createFromFormat('m-d-Y', trim($date)); // Adjust format as needed if ($dt === false) { // Handle invalid date continue; } $dt->setTime(23, 59, 59); $datetime_points[] = $dt->format('Y-m-d H:i:s'); } $datetime_points = array_unique($datetime_points); $datetime_points = array_values($datetime_points); $query = "WITH latest_records AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY personid ORDER BY lastupdate DESC) AS rn FROM nws WHERE status = 'active' AND lastupdate <= $1 ), otitle_counts AS ( SELECT office, otitle, COUNT(*) AS otitle_count FROM latest_records WHERE rn = 1 GROUP BY office, otitle ) SELECT lr.office, COUNT(DISTINCT lr.personid) AS unique_person_count, (SELECT ARRAY_AGG(json_obj ORDER BY json_obj->>'otitle' ASC) FROM (SELECT DISTINCT jsonb_build_object( 'otitle', tc2.otitle, 'count', tc2.otitle_count ) AS json_obj FROM otitle_counts tc2 WHERE tc2.office = lr.office) AS subquery ) AS title_counts_array FROM latest_records lr WHERE lr.rn = 1 GROUP BY lr.office ORDER BY unique_person_count DESC"; $results_array = []; // Execute query for each provided datetime foreach ($datetime_points as $datetime) { $result = pg_query_params($dbconn, $query, array($datetime)) or die('Query failed: ' . pg_last_error()); $office_data = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $office_data[] = $line; } // Store results with the datetime used $results_array[] = [ 'provided_datetime' => $datetime, 'data' => $office_data ]; pg_free_result($result); } // Return JSON encoded results echo json_encode($results_array); } } } // Service: powerapi - Returns power outage information elseif ($service === 'powerapi') { $dbconn = getDBConnection(); //no gets, current point outage info if (empty(array_diff_key($_GET, array('service' => '')))) { try { $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(realgeom)::json, 'properties', json_build_object( 'time', startguess, 'county', county, 'state', state, 'outage', outagen, 'lastchange', lastchange, 'cause', cause, 'area_geometry', ST_AsGeoJSON(COALESCE(realareageom, realgeom))::json ) ) ORDER BY startguess ASC ) ) FROM power WHERE cwa = $1 AND active = true "; $result = pg_query_params($dbconn, $query, array('RLX')); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $resultArray = pg_fetch_all($result); // Check if we got results if ($resultArray && isset($resultArray[0]['json_build_object'])) { header('Content-Type: application/json'); echo $resultArray[0]['json_build_object']; } else { echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { http_response_code(500); die('Query execution failed: ' . $e->getMessage()); } } if (isset($_GET['states'])) { try { $query = " SELECT jsonb_build_object( 'type', 'FeatureCollection', 'features', jsonb_agg(features.feature) ) FROM ( SELECT jsonb_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb, 'properties', to_jsonb(properties) - 'geom' ) AS feature FROM ( SELECT * FROM states WHERE state IN ('WV', 'VA', 'KY', 'MD', 'PA', 'OH') ) AS properties ) AS features "; $result = pg_query($dbconn, $query); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $resultArray = pg_fetch_all($result); // Set proper JSON header and handle output header('Content-Type: application/json'); if ($resultArray && isset($resultArray[0]['jsonb_build_object'])) { echo $resultArray[0]['jsonb_build_object']; } else { echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { http_response_code(500); header('Content-Type: application/json'); echo json_encode(['error' => 'Query execution failed: ' . $e->getMessage()]); exit; } } if (isset($_GET['max'])) { if (isset($_GET['start']) && isset($_GET['end'])) { try { $starttime = getParam('start'); $endtime = getParam('end'); $query = " SELECT DISTINCT ON (county, state) max(outage) as max_outage, county, state FROM ( SELECT DISTINCT ON (county, state, update) county, state, SUM(outages) as outage, update as time, SUM(served) as served FROM countyoutages WHERE update > $2 AND update < $3 AND cwa = $1 GROUP BY county, state, update ) as subquery GROUP BY county, state "; $result = pg_query_params( $dbconn, $query, ['RLX', $starttime, $endtime] ); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => 'Query execution failed: ' . $e->getMessage()]); exit; } } else { header('Content-Type: application/json'); http_response_code(400); echo json_encode(['error' => 'Both start and end parameters are required']); } } if (isset($_GET['county'])) { try { $query = " WITH latest_update AS ( SELECT MAX(update) as max_update FROM countyoutages ) SELECT county, state, SUM(outages) as outage, MAX(update) as time, SUM(served) as served, ROUND( CASE WHEN SUM(served) > 0 THEN (SUM(outages)::FLOAT / SUM(served)) * 100 ELSE 0 END::NUMERIC, 2 ) as perout FROM countyoutages JOIN latest_update ON countyoutages.update = latest_update.max_update WHERE cwa = $1 GROUP BY county, state ORDER BY county, state; "; $result = pg_query_params($dbconn, $query, ['RLX']); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => 'Query execution failed: ' . $e->getMessage()]); exit; } } if (isset($_GET['countyarchive'])) { if (isset($_GET['start']) && isset($_GET['end'])) { try { $starttime = getParam('start'); $endtime = getParam('end'); $query = " SELECT DISTINCT ON (county, state, update) county, state, SUM(outages) as outage, update as time, SUM(served) as served FROM countyoutages WHERE update > $2 AND update < $3 AND cwa = $1 GROUP BY county, state, update "; $result = pg_query_params($dbconn, $query, ['RLX', $starttime, $endtime]); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => 'Query execution failed: ' . $e->getMessage()]); if (isset($result)) { pg_free_result($result); } exit; } } else { header('Content-Type: application/json'); http_response_code(400); echo json_encode(['error' => 'Both start and end parameters are required']); } } if (isset($_GET['archivepoint'])) { try { if (!isset($_GET['start']) || !isset($_GET['end'])) { throw new Exception('Both start and end parameters are required'); } $starttime = getParam('start'); $endtime = getParam('end'); $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(realgeom)::json, 'properties', json_build_object( 'time', startguess, 'county', county, 'state', state, 'outage', outagen, 'lastchange', lastchange, 'cause', cause ) ) ORDER BY startguess ASC ) ) FROM power WHERE cwa = $1 AND startguess > $2 AND lastchange < $3 "; $result = pg_query_params($dbconn, $query, ['RLX', $starttime, $endtime]); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $resultArray = pg_fetch_all($result); header('Content-Type: application/json'); if ($resultArray && isset($resultArray[0]['json_build_object'])) { echo $resultArray[0]['json_build_object']; } else { echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { header('Content-Type: application/json'); $statusCode = strpos($e->getMessage(), 'required') !== false ? 400 : 500; http_response_code($statusCode); echo json_encode(['error' => $e->getMessage()]); if (isset($result)) { pg_free_result($result); } exit; } } if(@$_GET['svr'] =='current') { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue < now() and endtime > now()") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); pg_free_result($result); } if (isset($_GET['svr']) && $_GET['svr'] === 'archive') { try { $result = null; if (isset($_GET['start']) && isset($_GET['end'])) { $starttime = getParam('start'); $endtime = getParam('end'); $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json, 'properties', json_build_object( 'issue', issue, 'end', endtime, 'vtec', vtec, 'type', warntype ) ) ) ) FROM svr WHERE issue > $1 AND endtime < $2 "; $result = pg_query_params($dbconn, $query, [$starttime, $endtime]); } elseif (!isset($_GET['start']) && !isset($_GET['end'])) { $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json, 'properties', json_build_object( 'issue', issue, 'end', endtime, 'vtec', vtec, 'type', warntype ) ) ) ) FROM svr WHERE issue < NOW() - INTERVAL '24 hours' AND endtime > NOW() - INTERVAL '24 hours' "; $result = pg_query($dbconn, $query); } else { throw new Exception('Both start and end parameters are required together'); } if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $resultArray = pg_fetch_all($result); header('Content-Type: application/json'); if ($resultArray && isset($resultArray[0]['json_build_object'])) { echo $resultArray[0]['json_build_object']; } else { echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); $statusCode = strpos($e->getMessage(), 'required') !== false ? 400 : 500; http_response_code($statusCode); echo json_encode(['error' => $e->getMessage()]); exit; } } if (isset($_GET['svrpolys'])) { try { $query = " SELECT vtec, outagesvalid, polygonpop, outagesbuffer, lsrids FROM svr WHERE EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - endtime)) / 60 / 60 / 24 < 60 "; $result = pg_query($dbconn, $query); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => $e->getMessage()]); exit; } } if (isset($_GET['powerids'])) { try { $powerids = $_GET['powerids']; // Validate input exists and isn't empty if (empty($powerids)) { throw new Exception('No power IDs provided'); } // Convert comma-separated string to array and sanitize $poweridArray = explode(',', $powerids); $sanitizedIds = array_filter(array_map('intval', $poweridArray)); if (empty($sanitizedIds)) { throw new Exception('Invalid power ID format'); } // Prepare placeholders for the query $placeholders = implode(',', array_map(function($i) { return '$' . $i; }, range(1, count($sanitizedIds)))); $query = " SELECT lat, lon, lastchange, startguess, peakoutage, cause, lsrtime, lsrref, (lsrtime AT TIME ZONE 'America/New_York')::timestamp as lsrlocal FROM power WHERE id IN ($placeholders) "; $result = pg_query_params($dbconn, $query, $sanitizedIds); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = pg_fetch_all($result) ?: []; header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); $statusCode = strpos($e->getMessage(), 'Invalid') !== false ? 400 : 500; http_response_code($statusCode); echo json_encode(['error' => $e->getMessage()]); exit; } } if (isset($_GET['poweridsgeojson'])) { try { $powerids = $_GET['poweridsgeojson']; if (empty($powerids)) { throw new Exception('No power IDs provided'); } // Convert and sanitize power IDs $poweridArray = explode(',', $powerids); $sanitizedIds = array_filter(array_map('intval', $poweridArray)); if (empty($sanitizedIds)) { throw new Exception('Invalid power ID format'); } // Prepare placeholders $placeholders = implode(',', array_map(function($i) { return '$' . $i; }, range(1, count($sanitizedIds)))); $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(realgeom)::json, 'properties', json_build_object( 'id', id, 'time', (startguess AT TIME ZONE 'UTC')::timestamp, 'county', county, 'state', state, 'cause', cause, 'outage', peakoutage, 'lsrtime', (lsrtime AT TIME ZONE 'UTC')::timestamp ) ) ORDER BY startguess ASC ) ) FROM power WHERE id IN ($placeholders) "; $result = pg_query_params($dbconn, $query, $sanitizedIds); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $resultArray = pg_fetch_all($result); header('Content-Type: application/json'); if ($resultArray && isset($resultArray[0]['json_build_object'])) { echo $resultArray[0]['json_build_object']; } else { echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { if (isset($result)) { pg_free_result($result); } header('Content-Type: application/json'); $statusCode = strpos($e->getMessage(), 'Invalid') !== false ? 400 : 500; http_response_code($statusCode); echo json_encode(['error' => $e->getMessage()]); exit; } } if (isset($_GET['polygongeojson'])) { $result = null; // Initialize result to null for catch block safety try { $polygonGeoJsonString = $_GET['polygongeojson']; if (empty($polygonGeoJsonString)) { throw new Exception('No GeoJSON polygon provided', 400); // Use exception code for status } // 1. Validate if the input is valid JSON // We decode here primarily to check JSON validity. // We'll pass the *original string* to PostGIS's ST_GeomFromGeoJSON for robustness. $polygonGeoJson = json_decode($polygonGeoJsonString); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception('Invalid JSON format: ' . json_last_error_msg(), 400); } // 2. Optional: Basic structural validation (can rely on PostGIS for full validation) if (!is_object($polygonGeoJson) || !isset($polygonGeoJson->type) || !in_array($polygonGeoJson->type, ['MultiPolygon', 'Polygon'])) { // Allow both Polygon and MultiPolygon for flexibility? Or stick to MultiPolygon? // Let's allow Polygon too, as ST_Within works with both. // If you strictly need *only* MultiPolygon, change the check. throw new Exception('Input GeoJSON must be of type Polygon or MultiPolygon.', 400); } if (!isset($polygonGeoJson->coordinates) || !is_array($polygonGeoJson->coordinates)) { throw new Exception('Input GeoJSON must have a coordinates array.', 400); } // 3. Prepare the PostgreSQL Query using PostGIS functions // - ST_GeomFromGeoJSON($1): Parses the input GeoJSON string. // - ST_SetSRID(..., 4326): Assigns the WGS84 SRID (standard for GeoJSON). Adjust if your data uses a different SRID. // - ST_Within(realgeom, ...): Checks if the power outage geometry is within the provided polygon geometry. // - Ensure your 'realgeom' column has a spatial index for performance! $query = " SELECT json_build_object( 'type', 'FeatureCollection', 'features', json_agg( json_build_object( 'type', 'Feature', 'geometry', ST_AsGeoJSON(realgeom)::json, 'properties', json_build_object( 'id', id, 'time', (startguess AT TIME ZONE 'UTC')::timestamp, 'county', county, 'state', state, 'cause', cause, 'outage', peakoutage, 'lsrtime', (lsrtime AT TIME ZONE 'UTC')::timestamp ) ) ORDER BY startguess ASC -- Optional ordering ) ) FROM power WHERE ST_Within(realgeom, ST_SetSRID(ST_GeomFromGeoJSON($1), 4326)) "; // Note: If 'realgeom' might be NULL, you might add "AND realgeom IS NOT NULL" // 4. Execute the query with the GeoJSON string as a parameter $params = [$polygonGeoJsonString]; $result = pg_query_params($dbconn, $query, $params); if ($result === false) { // Check for specific PostGIS errors related to invalid GeoJSON input $pgError = pg_last_error($dbconn); if (strpos($pgError, 'invalid GeoJSON representation') !== false || strpos($pgError, 'ParseException') !== false || strpos($pgError, 'Invalid polygon') !== false) { throw new Exception('Invalid GeoJSON geometry data provided: ' . $pgError, 400); } else { // Throw a generic server error for other query failures throw new Exception('Query failed: ' . $pgError, 500); } } // 5. Fetch and Output Results $resultArray = pg_fetch_all($result); header('Content-Type: application/json'); if ($resultArray && isset($resultArray[0]['json_build_object'])) { // Ensure null result from json_agg (no features found) returns empty array $outputJson = $resultArray[0]['json_build_object']; $outputData = json_decode($outputJson, true); if (isset($outputData['features']) && $outputData['features'] === null) { $outputData['features'] = []; echo json_encode($outputData); } else { echo $outputJson; // Output the JSON directly from Postgres } } else { // Should ideally be handled by the check above, but as a fallback echo json_encode(['type' => 'FeatureCollection', 'features' => []]); } pg_free_result($result); } catch (Exception $e) { // 6. Error Handling if (isset($result) && is_resource($result)) { // Check if $result is a valid resource before freeing pg_free_result($result); } header('Content-Type: application/json'); // Use exception code for status if provided (>=400), default to 500 $statusCode = ($e->getCode() >= 400 && $e->getCode() < 600) ? $e->getCode() : 500; http_response_code($statusCode); echo json_encode(['error' => $e->getMessage()]); exit; // Stop script execution after error } } } // Service: searchapi - Returns search results for power outages elseif ($service === 'searchapi') { $dbconn = getDBConnection(); //no gets, current point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(realgeom)::json,'properties',json_build_object('time',startguess,'county',county,'state',state,'outage',outagen,'lastchange',lastchange,'cause',cause))order by startguess asc)) FROM power WHERE cwa = $1 and active = true", array('RLX')) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } //county current if(isset($_GET['county'])) { $result = pg_query_params($dbconn, "SELECT distinct on (county,state) update as time, county, state, outages as outage,served FROM countyoutages where cwa = $1 order by county,state,update desc", array('RLX')) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); } //county archive if(isset($_GET['countyarchive'])) { if(isset($_GET['start'])) { $starttime = getParam('start'); if(isset($_GET['end'])) { $endtime = getParam('end'); $result = pg_query_params($dbconn, "SELECT county,state, update as time, county, state, outages as outage,served FROM countyoutages where cwa = $1 and update > $2 and update < $3 order by update asc", array('RLX',$starttime,$endtime)) or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); } } } //Archive point data if(isset($_GET['archivepoint'])) { if(isset($_GET['start'])) { $starttime = getParam('start'); if(isset($_GET['end'])) { $endtime = getParam('end'); $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(realgeom)::json,'properties',json_build_object('time',startguess,'county',county,'state',state,'outage',outagen,'lastchange',lastchange,'cause',cause))order by startguess asc)) FROM power WHERE cwa = $1 and startguess > $2 and lastchange < $3" ,array('RLX',$starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } } if(isset($_GET['svr']) && $_GET['svr'] === 'current') { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue < now() and endtime > now()") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } if(isset($_GET['svr']) && $_GET['svr'] === 'archive') { if(isset($_GET['start'])) { $starttime = getParam('start'); if(isset($_GET['end'])) { $endtime = getParam('end'); $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue > $1 and endtime < $2" ,array($starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } if(!isset($_GET['start']) && !isset($_GET['end'])) { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue < now() - interval '24 hours' and endtime > now() - interval '24 hours'") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } pg_free_result($result); } // Service: ohgo - Returns Ohio traffic information elseif ($service === 'ohgo') { $dbconn = getDBConnection(); // Performing SQL query $query = "SELECT lat,lon,id,category,roadstatus,cwa,county,state,location,routename,description,lsr,date_trunc('minute', start) as start, date_trunc('minute', endtime) as endtime,date_trunc('minute', lastupdate) as lastupdate from ohgo where endtime is null or endtime > now() - interval '48 hours' order by start asc;"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in HTML while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } // Service: power - Returns power outage information elseif ($service === 'power') { $dbconn = getDBConnection(); // Performing SQL query $query = "SELECT lat,lon,outagen FROM power WHERE active = true and cwa = 'RLX'"; $result = pg_query($query) or die('Query failed: ' . pg_last_error()); // Printing results in HTML while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); // Free resultset pg_free_result($result); } // Service: stormdata - Returns storm data based on POST request elseif ($service === 'stormdata') { $dbconn = getDBConnection(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $input_data = null; $request_type = null; $contentType = trim(strtolower($_SERVER['HTTP_CONTENT_TYPE'] ?? $_SERVER['CONTENT_TYPE'] ?? '')); if (strpos($contentType, 'application/json') === 0) { $raw_post_data = file_get_contents('php://input'); if ($raw_post_data === false || $raw_post_data === '') { send_error(400, 'Received empty request body or could not read input.', "Error: Could not read php://input or it was empty."); } $input_data = json_decode($raw_post_data, true); if (json_last_error() !== JSON_ERROR_NONE) { send_error(400, 'Invalid JSON payload received.', 'JSON Decode Error: ' . json_last_error_msg() . " | Raw data snippet: " . substr($raw_post_data, 0, 100)); } elseif (!is_array($input_data)) { send_error(400, 'Invalid JSON payload: Expected a JSON object.', "JSON Decode Warning: Result is not an array. Data: " . print_r($input_data, true)); } else { $request_type = $input_data['request_type'] ?? null; } } else { send_error(415, 'Unsupported Media Type. This endpoint requires application/json.', "Unsupported Media Type Received: " . $contentType); } if ($request_type === null) { if (is_array($input_data) && !isset($input_data['request_type'])) { send_error(400, 'Missing "request_type" field within the request payload.'); } else { send_error(400, 'Missing required parameter: request_type (or processing error).'); } } switch ($request_type) { case 'ohgo': handle_ohgo_request($dbconn, $input_data); break; case 'ohgonopoly': handle_ohgo_request_no_poly($dbconn, $input_data); break; case 'power': handle_power_request($dbconn, $input_data); break; case 'powernopoly': handle_power_request_no_poly($dbconn, $input_data); break; case 'wupoly': handle_wu_request_poly($dbconn, $input_data); break; case 'campoly': handle_cam_request($dbconn, $input_data); break; default: send_error(400, 'Invalid request_type specified: ' . htmlspecialchars($request_type)); break; } } else { http_response_code(405); header('Allow: POST'); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['error' => 'Invalid request method. Only POST is allowed.']); exit; } } // Service: warntrack - Returns warning tracking data elseif ($service === 'warntrack') { $dbconn = getDBConnection(); //no gets, curent point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'endtime',endtime,'warntype',warntype,'etin',etin,'followups',followups,'followup',followup,'canexp',canexp,'warnexpired',warnexpired,'vtectext',vtectext,'office',office))order by issue desc)) FROM warntracker WHERE office = 'KRLX' and svstype = 'NEW' and EXTRACT(EPOCH FROM (current_timestamp - endtime ))/60 < 2400") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } pg_free_result($result); } // Service: ver - Returns version information elseif ($service === 'ver') { $dbconn = getDBConnection(); //no gets, curent point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query($dbconn, "SELECT jsonb_build_object('type', 'FeatureCollection','features', jsonb_agg(features.feature)) FROM (SELECT jsonb_build_object('type', 'Feature','geometry', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb,'properties', to_jsonb(properties) - 'geom') AS feature FROM (SELECT *FROM pzone where cwa ='RLX') AS properties) AS features") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); header('Content-Type: application/json; charset=utf-8'); echo($resultArray[0]['jsonb_build_object']); pg_free_result($result); } if (isset($_GET['lsrslist'])) { $result = pg_query($dbconn,"SELECT * from simplever") or die('Query failed: ' . pg_last_error()); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } if (isset($_GET['reset'])) { $result = pg_query($dbconn,"truncate simplever") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray); } if (isset($_GET['lsrs'])) { if (isset($_GET['zone'])) { $zone = $_GET['zone']; if (isset($_GET['lsr'])) { $lsr = (int) $_GET['lsr']; } else { $lsr = 1; } if (isset($_GET['lsrs'])) { $dir = $_GET['dir']; } if ($dir == 1) { $result = pg_query_params($dbconn,"INSERT into simplever (zone,lsr) values ($1,$2) on conflict (zone) do update set lsr = (simplever.lsr + 1) where simplever.zone = $1", array($zone,$lsr)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); } else { $result = pg_query_params($dbconn,"INSERT into simplever (zone,lsr) values ($1,$2) on conflict (zone) do update set lsr = 0 where simplever.zone = $1", array($zone,$lsr)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); } pg_free_result($result); } } if (isset($_GET['inc'])) { if ($_GET['inc'] == 'true') { $hideflag = 'true'; } else { $hideflag = 'false'; } $id = (int) $_GET['id']; $query = "UPDATE reports SET hide = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($hideflag, $id)) or die('Query failed: ' . pg_last_error()); pg_free_result($result); } if (isset($_GET['hide'])) { if ($_GET['hide'] == 'true') { $hideflag = 'true'; } else { $hideflag = 'false'; } $id = (int) $_GET['id']; $query = "UPDATE reports SET hide = $1 WHERE id = $2"; $result = pg_query_params($dbconn, $query, array($hideflag, $id)) or die('Query failed: ' . pg_last_error()); pg_free_result($result); } } // Service: update_field - Updates table fields elseif ($service === 'update_field') { $dbconn = getDBConnection(); // Get POST data $camid = $_POST['camid']; $field = $_POST['field']; $value = $_POST['value']; // Validate inputs if (empty($camid) || empty($field)) { echo json_encode(['success' => false, 'message' => 'Invalid input']); exit; } // Check if the field is valid if (!in_array($field, ['hydro', 'airport'])) { echo json_encode(['success' => false, 'message' => 'Invalid field']); exit; } // Convert to proper boolean for PostgreSQL // JavaScript sends true/false as strings 'true' or 'false' $value_bool = ($value === 'true'); // Update the field value in the database - use boolean directly // PostgreSQL accepts 't'/'f' for boolean values $query = "UPDATE cams SET $field = $1 WHERE camid = $2"; $result = pg_query_params($dbconn, $query, array($value_bool ? 't' : 'f', $camid)); if ($result) { echo json_encode(['success' => true]); } else { $error = pg_last_error($dbconn); echo json_encode(['success' => false, 'message' => $error]); } // Closing connection } // Service: mp4 - Generates MP4/gif from images elseif ($service === 'mp4') { // This service expects POST data with specific parameters $elements = $_POST['data']; $numimages = $_POST['images']; $delay = $_POST['delay']; $lastdelay = $_POST['lastdelay']; $maxh = $_POST['maxh']; $maxv = $_POST['maxv']; if (! is_numeric($maxh)) { $maxh = 500; } if (! is_numeric($maxv)) { $maxv = 400; } $numimages = $numimages - 1; $inputfiles = ""; foreach ($elements as $value) { if ($value != $elements[array_key_last($elements)]) { $inputfiles = $inputfiles . " -delay {$delay} {$value}"; } if ($value == $elements[array_key_last($elements)]) { $inputfiles = $inputfiles . " -delay {$lastdelay} {$value}"; } } $gif = shell_exec("convert {$inputfiles} -resize {$maxh}x{$maxv}\> -layers Optimize gif:-"); echo base64_encode($gif); exit; } // Service: camobs - Camera observation queries with radius and bbox filtering elseif ($service === 'camobs') { $dbconn = getDBConnection(); if($_GET['camstatic'] ?? null) { if ($_GET['camstatic'] == 'radius') { if($_GET['lat1'] ?? null) { $lat1 = getParam('lat1'); if($_GET['lon1'] ?? null) { $lon1 = getParam('lon1'); if($_GET['radius'] ?? null) { $radius = getParam('radius'); $rad = $radius / 70; $lat1 = floatval($lat1); $lon1 = floatval($lon1); $radius = floatval($rad); $query = "select * from cams where active = true and cwa = 'RLX' and (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < (interval + 20) and st_dwithin(geom, ST_SetSRID(ST_Point(" . strval($lon1) . ", " . strval($lat1) . "), 4326)," . strval($radius) . ") order by elevation desc"; $result = pg_query($dbconn,$query) or die('Query failed: ' . pg_last_error()); $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } } } } } if($_GET['camstatic'] ?? null) { if ($_GET['camstatic'] == 'bbox') { if($_GET['lat1'] ?? null) { $lat1 = getParam('lat1'); if($_GET['lon1'] ?? null) { $lon1 = getParam('lon1'); if($_GET['lat2'] ?? null) { $lat2 = getParam('lat2'); if($_GET['lon2'] ?? null) { $lon2 = getParam('lon2'); if($_GET['elevbottom'] ?? null) { $elevbottom = getParam('elevbottom'); if($_GET['elevtop'] ?? null) { $elevtop = getParam('elevtop'); $result = pg_query_params($dbconn, "select * from cams where active = true and cwa = 'RLX' and elevation > $5 and elevation < $6 and (EXTRACT(EPOCH FROM (current_timestamp - lastsuccess ))/60) < (interval + 20) and lat < $1 and lat > $2 and lon < $3 and lon > $4 order by elevation desc", array($lat1,$lat2,$lon1,$lon2,$elevbottom,$elevtop)) or die('Query failed: ' . pg_last_error()); $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); pg_free_result($result); } } } } } } } } } // Service: single - Returns single camera information by camid elseif ($service === 'single') { header('Content-Type: application/json'); $dbconn = getDBConnection(); // Check if camid is actually set to avoid warnings if (!isset($_GET['camid'])) { echo json_encode(array("error" => "No camid specified")); exit; } $camid = $_GET['camid']; // Performing SQL query $query = "SELECT *, COALESCE(hydro, false) as hydro, COALESCE(airport, false) as airport FROM cams WHERE camid = $1"; // Use pg_query_params to safely bind the $camid variable $result = pg_query_params($dbconn, $query, array($camid)) or die('Query failed: ' . pg_last_error()); // Processing results $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { // Ensure hydro is a proper boolean $line['hydro'] = ($line['hydro'] === 't' || $line['hydro'] === true); // Ensure airport is a proper boolean $line['airport'] = ($line['airport'] === 't' || $line['airport'] === true); $array[] = $line; } // Output the ORIGINAL full array (including errorcode) to the client echo json_encode($array); // Free resultset pg_free_result($result); } // Service: powerapitest - Extended power outage testing API elseif ($service === 'powerapitest') { $dbconn = getDBConnection(); //no gets, current point outage info if(empty(array_diff_key($_GET, array('service' => '')))) { $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(realgeom)::json,'properties',json_build_object('time',startguess,'county',county,'state',state,'outage',outagen,'lastchange',lastchange,'cause',cause))order by startguess asc)) FROM power WHERE cwa = $1 and active = true", array('RLX')) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } //county/state max if($_GET['max'] ?? null) { if($_GET['start'] ?? null) { $starttime = pg_escape_string($_GET['start']); if($_GET['end'] ?? null) { $endtime = pg_escape_string($_GET['end']); $result = pg_query_params($dbconn, "select distinct on (county,state) max(outage),county,state from (select distinct on (county,state,update) county,state,sum(outages) as outage, update as time, sum(served) as served from countyoutages where update > $2 and update < $3 and cwa = $1 group by county,state,update) as potato group by county,state", array('RLX',$starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); } } } if (isset($_GET['county'])) { try { $query = " SELECT DISTINCT ON (county, state) county, state, SUM(outages) as outage, update as time, SUM(served) as served, ROUND( CAST( CASE WHEN SUM(served) = 0 THEN NULL ELSE (SUM(outages)::FLOAT / SUM(served)) * 100 END AS NUMERIC ), 2 ) as perout FROM countyoutages WHERE update = (SELECT MAX(update) FROM countyoutages) AND (cwa = $1 OR cwa = $2 OR cwa = $3 OR cwa = $4 OR cwa = $5 OR cwa = $6 OR cwa = $7) GROUP BY county, state, update "; $result = pg_query_params($dbconn, $query, ['RLX','JKL','ILN','PBZ','MRX','LWX','RNK']); if ($result === false) { throw new Exception('Query failed: ' . pg_last_error()); } $results = []; while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $results[] = $line; } header('Content-Type: application/json'); echo json_encode($results); pg_free_result($result); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode(['error' => 'Query execution failed: ' . $e->getMessage()]); exit; } } //county archive if($_GET['countyarchive'] ?? null) { if($_GET['start'] ?? null) { $starttime = pg_escape_string($_GET['start']); if($_GET['end'] ?? null) { $endtime = pg_escape_string($_GET['end']); $result = pg_query_params($dbconn, "select distinct on (county,state,update) county,state,sum(outages) as outage, update as time, sum(served) as served from countyoutages where update > $9 and update < $10 and (cwa = $1 or cwa = $2 or cwa = $3 or cwa = $4 or cwa = $5 or cwa = $6 or cwa = $7 or cwa = $8) group by county,state,update", array('RLX','JKL','ILN','PBZ','MRX','LWX','RNK','CTP',$starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $array = array(); while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { $array[] = $line; } echo json_encode($array); } } } //Archive point data if($_GET['archivepoint'] ?? null) { $starttime = pg_escape_string($_GET['start']); $endtime = pg_escape_string($_GET['end']); $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(realgeom)::json,'properties',json_build_object('time',startguess,'county',county,'state',state,'outage',outagen,'lastchange',lastchange,'cause',cause))order by startguess asc)) FROM power WHERE cwa = $1 and startguess > $2 and lastchange < $3", array('RLX',$starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } // Svr related functionality if($_GET['svr'] ?? null =='current') { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue < now() and endtime > now()") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } if($_GET['svr'] ?? null == 'archive') { if($_GET['start'] ?? null) { $starttime = pg_escape_string($_GET['start']); if($_GET['end'] ?? null) { $endtime = pg_escape_string($_GET['end']); $result = pg_query_params($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue > $1 and endtime < $2" ,array($starttime,$endtime)) or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } if(!isset($_GET['start']) && !isset($_GET['end'])) { $result = pg_query($dbconn, "SELECT json_build_object('type', 'FeatureCollection','features', json_agg(json_build_object('type','Feature', 'geometry', ST_AsGeoJSON(nwspoly)::json,'properties',json_build_object('issue',issue,'end',endtime,'vtec',vtec,'type',warntype)))) FROM svr where issue < now() - interval '24 hours' and endtime > now() - interval '24 hours'") or die('Query failed: ' . pg_last_error()); $resultArray = pg_fetch_all($result); echo($resultArray[0]['json_build_object']); } } pg_free_result($result); } // Default behavior - show available services if no valid service provided else { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'error' => 'Invalid service parameter. Please provide a valid service.', 'available_services' => [ 'cams', 'camapi', 'camlist', 'admin', 'camcircle', 'db', 'fire', 'individualcam', 'lsr', 'nws', 'powerapi', 'searchapi', 'ohgo', 'power', 'stormdata', 'warntrack', 'ver', 'update_field', 'mp4', 'camobs', 'single', 'powerapitest' ], 'documentation' => 'See main.php file for detailed documentation on each service.' ]); } // Helper functions for stormdata service function send_error(int $http_code, string $message, ?string $log_message = null): void { if ($log_message) { error_log($log_message); } elseif ($http_code >= 500) { error_log("Server Error (" . $http_code . "): " . $message); } http_response_code($http_code); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['error' => $message]); exit; } function handle_cam_request($dbconn, array $data): void { error_log("Handling 'camera image' request."); $start_time_str = $data['start_time'] ?? null; $end_time_str = $data['end_time'] ?? null; $geojson_str = $data['area_geojson'] ?? null; if ($start_time_str === null || $end_time_str === null || $geojson_str === null) { send_error(400, 'Missing required parameters for camera request: start_time, end_time, area_geojson'); } if (strtotime($start_time_str) === false) { send_error(400, 'Invalid start_time format.'); } if (strtotime($end_time_str) === false) { send_error(400, 'Invalid end_time format.'); } $geojson_obj = json_decode($geojson_str); if (json_last_error() !== JSON_ERROR_NONE) { send_error(400, 'Invalid area_geojson provided: Contains invalid JSON string.', 'GeoJSON Decode Error: ' . json_last_error_msg()); } if (!is_object($geojson_obj) || !isset($geojson_obj->type) || !in_array($geojson_obj->type, ['Polygon', 'MultiPolygon'])) { send_error(400, 'Invalid area_geojson provided: Decoded JSON must be a Polygon or MultiPolygon object.'); } $query = " SELECT c.*, ST_AsGeoJSON(c.geom) as geometry_geojson, COALESCE(img_agg.images, '[]'::jsonb) AS images FROM cams c LEFT JOIN ( SELECT camid, jsonb_agg( jsonb_build_object( 'timestamp', dateutc, 'url', filepath -- Assuming filepath is the relative URL path ) ORDER BY dateutc ASC -- Order images chronologically ) AS images FROM camdb WHERE dateutc >= $1::timestamp -- start_time AND dateutc <= $2::timestamp -- end_time GROUP BY camid ) AS img_agg ON c.camid = img_agg.camid WHERE c.active = TRUE -- Only active cameras AND ST_Within(c.geom, ST_GeomFromGeoJSON($3)) -- Camera location within area ORDER BY c.camid; -- Optional: Order cameras by ID "; $params = array( $start_time_str, // $1: start_time $end_time_str, // $2: end_time $geojson_str // $3: area_geojson string ); $result = pg_query_params($dbconn, $query, $params); if (!$result) { send_error(500, 'Database query failed for camera data.', 'Camera Query Failed: ' . pg_last_error($dbconn) . " | Query: " . $query . " | Params: " . print_r($params, true)); } $cameras_output = []; while ($row = pg_fetch_assoc($result)) { $geometry = json_decode($row['geometry_geojson']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode geometry for camid ' . ($row['camid'] ?? 'N/A') . ': ' . json_last_error_msg()); $geometry = null; } $images = json_decode($row['images']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode images JSON for camid ' . ($row['camid'] ?? 'N/A') . ': ' . json_last_error_msg()); $images = []; } $camera_data = $row; unset($camera_data['geometry_geojson']); unset($camera_data['geom']); $camera_data['geometry'] = $geometry; $camera_data['images'] = $images; $cameras_output[] = $camera_data; } pg_free_result($result); error_log("Found " . count($cameras_output) . " cameras matching criteria."); header('Content-Type: application/json'); echo json_encode($cameras_output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit; } function handle_wu_request_poly($dbconn, array $data): void { $polygons = $data['polygons'] ?? []; $start_time = $data['start_time'] ?? '2025-01-01 00:00:00'; $end_time = $data['end_time'] ?? '2025-01-02 00:00:00'; if (empty($polygons)) { http_response_code(500); echo json_encode(['error' => 'No polygons provided']); exit; } $polygon_placeholders = []; $params = []; $param_index = 1; foreach ($polygons as $polygon) { $polygon_placeholders[] = "ST_GeomFromText(\$$param_index, 4326)"; $params[] = $polygon; $param_index++; } $params[] = $start_time; $params[] = $end_time; $start_time_placeholder = "\$$param_index"; $param_index++; $end_time_placeholder = "\$$param_index"; $polygon_sql = implode(', ', $polygon_placeholders); $sql = " SELECT wo.* FROM wuobs wo JOIN wusites ws ON wo.stationid = ws.stationid WHERE ws.geom && ST_Union(ARRAY[$polygon_sql])::geometry AND ST_Within(ws.geom, ST_Union(ARRAY[$polygon_sql])::geometry) AND wo.observation_time BETWEEN $start_time_placeholder AND $end_time_placeholder "; $result = pg_query_params($dbconn, $sql, $params); if ($result === false) { http_response_code(500); echo json_encode(['error' => pg_last_error($dbconn)]); exit; } $results = []; while ($row = pg_fetch_assoc($result)) { $results[] = $row; } pg_free_result($result); header('Content-Type: application/json'); echo json_encode($results); } function handle_ohgo_request($dbconn, array $data): void { error_log("Handling 'ohgo' request."); $start = $data['start_time'] ?? null; $geojson_str = $data['area_geojson'] ?? null; $end = $data['end_time'] ?? null; if ($start === null || $geojson_str === null || $end === null) { send_error(400, 'Missing required parameters for ohgo request: start, geojson, end'); } $geojson_obj = json_decode($geojson_str); if (json_last_error() !== JSON_ERROR_NONE) { send_error(400, 'Invalid GeoJSON provided: Not valid JSON.', 'GeoJSON Decode Error: ' . json_last_error_msg()); } if (!isset($geojson_obj->type) || !in_array($geojson_obj->type, ['Polygon', 'MultiPolygon'])) { send_error(400, 'Invalid GeoJSON provided: Type must be Polygon or MultiPolygon.'); } $query = "SELECT ST_AsGeoJSON(geom) AS geometry, category, roadstatus, county, state, location, routename, description, start AS start_timestamp, endtime AS end_timestamp, lastupdate FROM ohgo WHERE start > $1::timestamp AND start < $3::timestamp AND ST_Within(geom, ST_GeomFromGeoJSON($2)) ORDER BY start ASC"; $params = array($start, $geojson_str, $end); $result = pg_query_params($dbconn, $query, $params); if (!$result) { send_error(500, 'Database query failed for ohgo data.', 'OHGO Query Failed: ' . pg_last_error($dbconn)); } $features = []; while ($line = pg_fetch_assoc($result)) { $geometry = json_decode($line['geometry']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode geometry for ohgo row: ' . json_last_error_msg()); continue; } $properties = $line; unset($properties['geometry']); $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; } pg_free_result($result); error_log("Found " . count($features) . " features for ohgo request."); send_geojson($features); } function handle_power_request($dbconn, array $data): void { error_log("Handling 'power' request."); $start = $data['start_time'] ?? null; $geojson_str = $data['area_geojson'] ?? null; $end = $data['end_time'] ?? null; $buffer_hours = $data['buffer'] ?? 0; if ($start === null || $geojson_str === null || $end === null || $buffer_hours === null) { send_error(400, 'Missing required parameters for power request: start_time, area_geojson, end_time, buffer_hours'); } if (!is_numeric($buffer_hours) || ($buffer_hours_float = floatval($buffer_hours)) < 0) { send_error(400, 'Invalid buffer_hours provided: Must be a non-negative number.'); } $buffer_hours_int = (int)$buffer_hours_float; $geojson_obj = json_decode($geojson_str); if (json_last_error() !== JSON_ERROR_NONE) { send_error(400, 'Invalid area_geojson provided: Contains invalid JSON string.', 'GeoJSON Decode Error: ' . json_last_error_msg()); } if (!is_object($geojson_obj) || !isset($geojson_obj->type) || !in_array($geojson_obj->type, ['Polygon', 'MultiPolygon'])) { send_error(400, 'Invalid area_geojson provided: Decoded JSON must be a Polygon or MultiPolygon object.'); } $query = "SELECT ST_AsGeoJSON(realgeom) AS geometry, derivedstart AS start_timestamp, cause, peakoutage, lastchange AS end_timestamp FROM power WHERE derivedstart >= $1::timestamp AND derivedstart < ($3::timestamp + make_interval(hours => $4::integer)) AND ST_Within(realgeom, ST_GeomFromGeoJSON($2)) ORDER BY derivedstart ASC"; $params = array( $start, // $1: start_time from JSON $geojson_str, // $2: area_geojson STRING from JSON $end, // $3: end_time from JSON $buffer_hours_int // $4: buffer_hours from JSON (as integer) ); $result = pg_query_params($dbconn, $query, $params); if (!$result) { send_error(500, 'Database query failed for power data.', 'Power Query Failed: ' . pg_last_error($dbconn) . " | Query: " . $query . " | Params: " . print_r($params, true)); } $features = []; while ($line = pg_fetch_assoc($result)) { $geometry = json_decode($line['geometry']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode geometry for power row: ' . json_last_error_msg()); continue; } $properties = $line; unset($properties['geometry']); $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; } pg_free_result($result); error_log("Found " . count($features) . " features for power request."); send_geojson($features); } function handle_ohgo_request_no_poly($dbconn, array $data): void { error_log("Handling 'ohgo' request no poly."); $start = $data['start_time'] ?? null; $end = $data['end_time'] ?? null; if ($start === null || $end === null) { send_error(400, 'Missing required parameters for ohgo request: start, end'); } $query = "SELECT ST_AsGeoJSON(geom) AS geometry, county, state AS st, location, routename AS city, upper(cwa) AS wfo, 'FLOOD' AS typetext, 'Department of Highways' AS source, description AS remark, TO_CHAR(start, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS valid FROM ohgo WHERE start > $1::timestamp AND start < $2::timestamp AND cwa = 'RLX' ORDER BY start ASC"; $params = array($start, $end); $result = pg_query_params($dbconn, $query, $params); if (!$result) { send_error(500, 'Database query failed for ohgo data.', 'OHGO Query Failed: ' . pg_last_error($dbconn)); } $features = []; while ($line = pg_fetch_assoc($result)) { $geometry = json_decode($line['geometry']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode geometry for ohgo row: ' . json_last_error_msg()); continue; } $properties = $line; unset($properties['geometry']); $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; } pg_free_result($result); error_log("Found " . count($features) . " features for ohgo request."); send_geojson($features); } function handle_power_request_no_poly($dbconn, array $data): void { error_log("Handling 'power' request no poly."); $start = $data['start_time'] ?? null; $end = $data['end_time'] ?? null; $outage_threshold = $data['outage_threshold'] ?? 9; $buffer_hours = $data['buffer'] ?? 0; if ($start === null || $end === null || $buffer_hours === null) { send_error(400, 'Missing required parameters for power request: start_time, end_time, buffer_hours'); } if (!is_numeric($buffer_hours) || ($buffer_hours_float = floatval($buffer_hours)) < 0) { send_error(400, 'Invalid buffer_hours provided: Must be a non-negative number.'); } $buffer_hours_int = (int)$buffer_hours_float; $outage_thresh = (float)$outage_threshold; $query = "SELECT ST_AsGeoJSON(realgeom) AS geometry, TO_CHAR(derivedstart, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS valid, ('Power Outage affecting ' || peakoutage || ' customers caused by ' || COALESCE(cause, 'unknown')) AS remark, 'Utility Company' as source, 'POWER OUTAGE' as typetext, 'U' as type, (ROUND(ST_Y(realgeom)::numeric, 3) || ', ' || ROUND(ST_X(realgeom)::numeric, 3)) AS city, county as county, state as state, state as st FROM power WHERE derivedstart >= $1::timestamp AND derivedstart < ($2::timestamp + make_interval(hours => $3::integer)) and peakoutage > $4 AND ST_Within(realgeom, (SELECT geom FROM public.cwa WHERE cwa = 'RLX')) ORDER BY derivedstart ASC"; $params = array( $start, // $1: start_time from JSON $end, // $2: end_time from JSON $buffer_hours_int, // $3: buffer_hours from JSON (as integer) $outage_thresh // $4 ); $result = pg_query_params($dbconn, $query, $params); if (!$result) { send_error(500, 'Database query failed for power data.', 'Power Query Failed: ' . pg_last_error($dbconn) . " | Query: " . $query . " | Params: " . print_r($params, true)); } $features = []; while ($line = pg_fetch_assoc($result)) { $geometry = json_decode($line['geometry']); if (json_last_error() !== JSON_ERROR_NONE) { error_log('Failed to decode geometry for power row: ' . json_last_error_msg()); continue; } $properties = $line; unset($properties['geometry']); $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; } pg_free_result($result); error_log("Found " . count($features) . " features for power request."); send_geojson($features); } function send_geojson(array $features): void { $geojson_output = ['type' => 'FeatureCollection', 'features' => $features]; header('Content-Type: application/geo+json; charset=utf-8'); echo json_encode($geojson_output); exit; } // Close database connection when needed if (isset($dbconn)) { pg_close($dbconn); } ?>