572 lines
25 KiB
PHP
572 lines
25 KiB
PHP
<?php
|
|
|
|
// --- Error Reporting (Recommended for Development) ---
|
|
ini_set('display_errors', 1);
|
|
ini_set('display_startup_errors', 1);
|
|
error_reporting(E_ALL);
|
|
// In production, turn display_errors off and configure log_errors.
|
|
// ini_set('display_errors', 0);
|
|
// ini_set('log_errors', 1);
|
|
// ini_set('error_log', '/path/to/your/php_error.log');
|
|
|
|
// --- Database Connection ---
|
|
$dbconn = pg_connect("host=localhost dbname=nws user=nws password=nws");
|
|
|
|
if (!$dbconn) {
|
|
error_log('Database connection failed: ' . pg_last_error());
|
|
http_response_code(503);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode(['error' => 'Service temporarily unavailable due to database connection issue.']);
|
|
exit;
|
|
}
|
|
|
|
// --- Helper Functions ---
|
|
|
|
/**
|
|
* Sends a JSON error response and terminates the script.
|
|
* @param int $http_code The HTTP status code.
|
|
* @param string $message The error message for the client.
|
|
* @param ?string $log_message Optional detailed message for the server error log.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Sends a GeoJSON FeatureCollection response and terminates the script.
|
|
* @param array $features An array of GeoJSON Feature objects.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
// --- Main Request Handling ---
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
|
|
error_log("--- New POST Request Received ---");
|
|
$input_data = null;
|
|
$request_type = null;
|
|
$contentType = trim(strtolower($_SERVER['HTTP_CONTENT_TYPE'] ?? $_SERVER['CONTENT_TYPE'] ?? ''));
|
|
error_log("Received Content-Type: " . $contentType);
|
|
|
|
// ***********************************************************
|
|
// ***** START: CRUCIAL JSON INPUT HANDLING BLOCK *****
|
|
// ***********************************************************
|
|
if (strpos($contentType, 'application/json') === 0) {
|
|
error_log("Content-Type identified as JSON.");
|
|
$raw_post_data = file_get_contents('php://input');
|
|
error_log("Raw php://input length: " . strlen($raw_post_data));
|
|
|
|
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.");
|
|
}
|
|
|
|
// Decode JSON into an associative array
|
|
$input_data = json_decode($raw_post_data, true); // Use 'true' for array
|
|
|
|
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 {
|
|
error_log("JSON Decode Successful.");
|
|
// ** GET request_type FROM THE DECODED ARRAY **
|
|
$request_type = $input_data['request_type'] ?? null;
|
|
error_log("Extracted request_type from JSON: " . ($request_type ?? 'null'));
|
|
}
|
|
} else {
|
|
// If JSON is strictly required, reject other types
|
|
send_error(415, 'Unsupported Media Type. This endpoint requires application/json.', "Unsupported Media Type Received: " . $contentType);
|
|
}
|
|
// ***********************************************************
|
|
// ***** END: CRUCIAL JSON INPUT HANDLING BLOCK *****
|
|
// ***********************************************************
|
|
|
|
|
|
// --- Final Check and Routing ---
|
|
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 {
|
|
error_log("Routing check reached but request_type is null without prior exit.");
|
|
send_error(400, 'Missing required parameter: request_type (or processing error).');
|
|
}
|
|
}
|
|
|
|
error_log("Routing request for type: " . $request_type);
|
|
switch ($request_type) {
|
|
case 'ohgo':
|
|
// ** Pass the $input_data array **
|
|
handle_ohgo_request($dbconn, $input_data);
|
|
break;
|
|
case 'ohgonopoly':
|
|
// ** Pass the $input_data array **
|
|
handle_ohgo_request_no_poly($dbconn, $input_data);
|
|
break;
|
|
case 'power':
|
|
// ** Pass the $input_data array **
|
|
handle_power_request($dbconn, $input_data);
|
|
break;
|
|
case 'powernopoly':
|
|
// ** Pass the $input_data array **
|
|
handle_power_request_no_poly($dbconn, $input_data);
|
|
break;
|
|
case 'wupoly':
|
|
// ** Pass the $input_data array **
|
|
handle_wu_request_poly($dbconn, $input_data);
|
|
break;
|
|
case 'campoly':
|
|
// ** Pass the $input_data array **
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Request Handler Functions ---
|
|
|
|
|
|
|
|
function handle_cam_request($dbconn, array $data): void {
|
|
error_log("Handling 'camera image' request.");
|
|
|
|
// --- 1. Get Data from the $data array ---
|
|
$start_time_str = $data['start_time'] ?? null;
|
|
$end_time_str = $data['end_time'] ?? null;
|
|
$geojson_str = $data['area_geojson'] ?? null;
|
|
|
|
// --- 2. Validation ---
|
|
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');
|
|
}
|
|
|
|
// Validate Timestamps (basic check, can be more robust)
|
|
// Consider using DateTime objects for more rigorous validation if needed
|
|
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.');
|
|
}
|
|
// Ensure start is before end? Optional, depends on requirements.
|
|
// if (strtotime($start_time_str) >= strtotime($end_time_str)) {
|
|
// send_error(400, 'start_time must be before end_time.');
|
|
// }
|
|
|
|
|
|
// Validate GeoJSON
|
|
$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.');
|
|
}
|
|
|
|
// --- 3. Prepare and Execute Query ---
|
|
// This query finds active cameras within the GeoJSON area,
|
|
// then LEFT JOINs aggregated image data from camdb within the time range.
|
|
// We use jsonb_agg for efficiency and COALESCE to return an empty array []
|
|
// for cameras with no images in the range, instead of NULL.
|
|
// NOTE: Selecting c.* assumes 'geom' is not excessively large or problematic
|
|
// when fetched directly. If it is, list all columns except 'geom'.
|
|
// We explicitly fetch ST_AsGeoJSON for the geometry representation.
|
|
$query = "
|
|
SELECT
|
|
c.*, -- Select all columns from cams
|
|
ST_AsGeoJSON(c.geom) as geometry_geojson, -- Get geometry as GeoJSON string
|
|
COALESCE(img_agg.images, '[]'::jsonb) AS images -- Get aggregated images or empty JSON array
|
|
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));
|
|
}
|
|
|
|
// --- 4. Process Results ---
|
|
$cameras_output = [];
|
|
while ($row = pg_fetch_assoc($result)) {
|
|
// Decode the geometry GeoJSON string into a PHP object/array
|
|
$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());
|
|
// Decide how to handle: skip camera, set geometry to null, etc.
|
|
$geometry = null; // Example: Set to null on error
|
|
}
|
|
|
|
// Decode the images JSON string (from jsonb_agg) into a PHP array
|
|
$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());
|
|
// Decide how to handle: skip camera, set images to empty array, etc.
|
|
$images = []; // Example: Set to empty array on error
|
|
}
|
|
|
|
// Prepare the output structure for this camera
|
|
$camera_data = $row; // Start with all columns fetched via c.*
|
|
|
|
// Replace/remove raw JSON strings and potentially the original binary geom
|
|
unset($camera_data['geometry_geojson']); // Remove the raw GeoJSON string
|
|
unset($camera_data['geom']); // Remove the raw binary geometry if it was fetched by c.*
|
|
$camera_data['geometry'] = $geometry; // Add the decoded geometry object/array
|
|
$camera_data['images'] = $images; // Add the decoded images array
|
|
|
|
$cameras_output[] = $camera_data;
|
|
}
|
|
pg_free_result($result);
|
|
error_log("Found " . count($cameras_output) . " cameras matching criteria.");
|
|
|
|
// --- 5. Send Response ---
|
|
// Use a function like send_json defined above, or inline the logic:
|
|
header('Content-Type: application/json');
|
|
echo json_encode($cameras_output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
exit; // Important to stop script execution here
|
|
// Alternatively, if you have the helper:
|
|
// send_json($cameras_output);
|
|
}
|
|
|
|
function handle_wu_request_poly($dbconn, array $data): void { // Takes $data array
|
|
|
|
$polygons = $data['polygons'] ?? []; // Array of WKT polygons, e.g., ['POLYGON((...))', 'POLYGON((...))']
|
|
$start_time = $data['start_time'] ?? '2025-01-01 00:00:00'; // e.g., '2025-01-01 00:00:00'
|
|
$end_time = $data['end_time'] ?? '2025-01-02 00:00:00'; // e.g., '2025-01-02 00:00:00'
|
|
|
|
if (empty($polygons)) {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'No polygons provided']);
|
|
pg_close($dbconn);
|
|
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)]);
|
|
pg_close($dbconn);
|
|
exit;
|
|
}
|
|
|
|
// Fetch results
|
|
$results = [];
|
|
while ($row = pg_fetch_assoc($result)) {
|
|
$results[] = $row;
|
|
}
|
|
|
|
// Free result and close connection
|
|
pg_free_result($result);
|
|
|
|
// Output results as JSON
|
|
header('Content-Type: application/json');
|
|
echo json_encode($results);
|
|
}
|
|
|
|
/**
|
|
* Handles the 'ohgo' data request.
|
|
* @param resource $dbconn The active database connection resource.
|
|
* @param array $data The associative array of input parameters (from JSON).
|
|
*/
|
|
function handle_ohgo_request($dbconn, array $data): void { // Takes $data array
|
|
error_log("Handling 'ohgo' request.");
|
|
// --- 1. Get Data from the $data array ---
|
|
$start = $data['start_time'] ?? null; // Use $data, use correct key
|
|
$geojson_str = $data['area_geojson'] ?? null; // Use $data, not $_POST
|
|
$end = $data['end_time'] ?? null; // Use $data, use correct key
|
|
|
|
// --- 2. Validation ---
|
|
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.'); }
|
|
|
|
// --- 3. Prepare and Execute Query ---
|
|
$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)); }
|
|
|
|
// --- 4. Process Results ---
|
|
$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.");
|
|
|
|
// --- 5. Send Response ---
|
|
send_geojson($features);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the 'power' data request.
|
|
* @param resource $dbconn The active database connection resource.
|
|
* @param array $data The associative array of input parameters (from JSON).
|
|
*/
|
|
function handle_power_request($dbconn, array $data): void { // Takes $data array
|
|
error_log("Handling 'power' request.");
|
|
// --- 1. Get Data from the $data array ---
|
|
// ** Match keys from your fetch request body: start_time, area_geojson, etc. **
|
|
$start = $data['start_time'] ?? null; // Use $data, use correct key
|
|
$geojson_str = $data['area_geojson'] ?? null; // Use $data, use correct key
|
|
$end = $data['end_time'] ?? null; // Use $data, use correct key
|
|
$buffer_hours = $data['buffer'] ?? 0;// Use $data, use correct key
|
|
|
|
// --- 2. Validation ---
|
|
if ($start === null || $geojson_str === null || $end === null || $buffer_hours === null) {
|
|
// Update error message to reflect the actual keys expected from JSON
|
|
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); // Decode the *string* value from the JSON input
|
|
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.'); }
|
|
// ** Crucial Fix: Use the decoded $geojson_str for the query parameter, not $geojson_obj **
|
|
|
|
// --- 3. Prepare and Execute Query ---
|
|
// ** VERIFY TABLE/COLUMN NAMES FOR POWER TABLE **
|
|
$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)); }
|
|
|
|
// --- 4. Process Results ---
|
|
$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.");
|
|
|
|
// --- 5. Send Response ---
|
|
send_geojson($features);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the 'ohgo' data request.
|
|
* @param resource $dbconn The active database connection resource.
|
|
* @param array $data The associative array of input parameters (from JSON).
|
|
*/
|
|
function handle_ohgo_request_no_poly($dbconn, array $data): void { // Takes $data array
|
|
error_log("Handling 'ohgo' request.");
|
|
// --- 1. Get Data from the $data array ---
|
|
$start = $data['start_time'] ?? null; // Use $data, use correct key
|
|
|
|
$end = $data['end_time'] ?? null; // Use $data, use correct key
|
|
|
|
// --- 2. Validation ---
|
|
if ($start === null || $end === null) { send_error(400, 'Missing required parameters for ohgo request: start, geojson, end'); }
|
|
|
|
// --- 3. Prepare and Execute Query ---
|
|
$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)); }
|
|
|
|
// --- 4. Process Results ---
|
|
$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.");
|
|
|
|
// --- 5. Send Response ---
|
|
send_geojson($features);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the 'power' data request.
|
|
* @param resource $dbconn The active database connection resource.
|
|
* @param array $data The associative array of input parameters (from JSON).
|
|
*/
|
|
function handle_power_request_no_poly($dbconn, array $data): void { // Takes $data array
|
|
error_log("Handling 'power' request.");
|
|
// --- 1. Get Data from the $data array ---
|
|
// ** Match keys from your fetch request body: start_time, area_geojson, etc. **
|
|
$start = $data['start_time'] ?? null; // Use $data, use correct key
|
|
$end = $data['end_time'] ?? null; // Use $data, use correct key
|
|
$outage_threshold = $data['outage_threshold'] ?? 9;
|
|
$buffer_hours = $data['buffer'] ?? 0;// Use $data, use correct key
|
|
|
|
// --- 2. Validation ---
|
|
if ($start === null || $end === null || $buffer_hours === null) {
|
|
// Update error message to reflect the actual keys expected from JSON
|
|
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;
|
|
$outage_thresh = (float)$outage_threshold;
|
|
|
|
|
|
|
|
|
|
// --- 3. Prepare and Execute Query ---
|
|
// ** VERIFY TABLE/COLUMN NAMES FOR POWER TABLE **
|
|
$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)); }
|
|
|
|
// --- 4. Process Results ---
|
|
$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.");
|
|
|
|
// --- 5. Send Response ---
|
|
send_geojson($features);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Close Database Connection ---
|
|
if ($dbconn) {
|
|
pg_close($dbconn);
|
|
error_log("Database connection closed.");
|
|
}
|
|
|
|
?>
|