From 3399dda125992506cf416f9776f8dfa6ba8721e2 Mon Sep 17 00:00:00 2001 From: John Peck Date: Mon, 8 Dec 2025 22:25:45 +0000 Subject: [PATCH] consolidate --- API_DOCUMENTATION.md | 596 ++++++++++++++++ coco.py | 7 + main.php | 1571 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2174 insertions(+) create mode 100644 API_DOCUMENTATION.md create mode 100644 main.php diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..7fca711 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,596 @@ +# NWS API Documentation - Integrated Main Endpoint + +This document describes all available endpoints in the integrated `main.php` file, which consolidates functionality from multiple separate PHP scripts. + +## Base URL +``` +/main.php +``` + +## General Information +- **Content-Type**: `application/json` (unless otherwise specified) +- **CORS**: Enabled with `Access-Control-Allow-Origin: *` +- **Methods**: GET, POST, OPTIONS +- **Database**: PostgreSQL (connection: `host=localhost dbname=nws user=nws password=nws`) + +## Utility Functions (No Direct Access) + +The following utility functions are included at the top of the file for compatibility: +- `connect_db()` - Database connection +- `send_error($code, $message)` - JSON error response +- `send_json($data)` - JSON success response +- `send_geojson($features)` - GeoJSON response + +## GET Endpoints + +### 1. Single Camera Information +**Endpoint**: `?camid={id}` + +**Description**: Get detailed information for a single camera. + +**Parameters**: +- `camid` (string, required): Camera ID + +**Response**: Camera object with hydro/airport boolean fields + +**Example**: `/main.php?camid=123` + +--- + +### 2. Active Cameras +**Endpoint**: `?cams` or no parameters + +**Description**: Get all active cameras with recent successful connections. + +**Response**: Array of camera objects + +**Example**: `/main.php?cams` + +--- + +### 3. Camera List +**Endpoint**: `?camlist` + +**Description**: Get comprehensive list of all active cameras. + +**Response**: +```json +{ + "status": "success", + "data": [...], + "count": 150 +} +``` + +**Example**: `/main.php?camlist` + +--- + +### 4. Camera API with Filters +**Endpoint**: `?cams` with bounding box parameters + +**Parameters**: +- `lat1`, `lon1`, `lat2`, `lon2` (float, required): Bounding box coordinates +- `elevbottom`, `elevtop` (float, required): Elevation range + +**Response**: Filtered camera array + +**Example**: `/main.php?cams&lat1=38.0&lon1=-82.0&lat2=39.0&lon2=-81.0&elevbottom=100&elevtop=1000` + +--- + +### 5. Camera Static Search +**Endpoint**: `?camstatic` + +**Parameters**: +- `lat1`, `lon1`, `radius` (float, required): Center point and radius for radius search +- `camstatic=bbox` with `lat1`, `lon1`, `lat2`, `lon2`, `elevbottom`, `elevtop`: Bounding box search + +**Response**: Cameras within specified area + +**Example**: `/main.php?camstatic=radius&lat1=38.5&lon1=-81.5&radius=50` + +--- + +### 6. Camera Database Count +**Endpoint**: `?camdb` + +**Response**: Total count of camera records + +**Example**: `/main.php?camdb` + +--- + +### 7. Camera Circles +**Endpoint**: `?camcircle` + +**Description**: Get camera locations as buffer circles (8000m radius). + +**Response**: GeoJSON FeatureCollection with circular buffers + +**Example**: `/main.php?camcircle` + +--- + +### 8. Weather Station Data +**Endpoint**: `?outside` or `?outsideold` + +**Description**: Get current weather station observations. + +**Response**: Array of weather station data + +**Example**: `/main.php?outside` + +--- + +### 9. Fire Data +**Endpoint**: `?fire` + +**Description**: Get active wildfire information. + +**Response**: GeoJSON FeatureCollection of fire data + +**Example**: `/main.php?fire` + +--- + +### 10. Individual Camera Images +**Endpoint**: `?camid={id}&dtg={datetime}&camimages={count}` + +**Parameters**: +- `camid` (string, required): Camera ID +- `dtg` (string, optional): End time for historical images (YYYY-MM-DD HH:MM UTC) +- `camimages` (int, optional): Number of images (default: 20) + +**Response**: Array of camera image records + +**Example**: `/main.php?camid=123&camimages=10` + +--- + +### 11. OHGO Data +**Endpoint**: `?ohgo` + +**Description**: Get Ohio Department of Transportation road information. + +**Response**: Array of OHGO road status data + +**Example**: `/main.php?ohgo` + +--- + +### 12. Power Outage Data +**Endpoint**: `?power` + +**Description**: Get current power outage information for RLX CWA. + +**Response**: Array of power outage points + +**Example**: `/main.php?power` + +--- + +### 13. Power API (Advanced) +**Endpoint**: Various parameters for power data + +**Parameters**: +- `states`: Get state boundaries GeoJSON +- `county`: Get county outage summary +- `max` with `start`, `end`: Get maximum outage by county for date range +- `countyarchive` with `start`, `end`: County archive data +- `archivepoint` with `start`, `end`: Point outage archive +- `svr=current`: Current severe weather warnings +- `svrpolys`: Warning polygon metadata +- `powerids={ids}`: Specific outage details +- `poweridsgeojson={ids}`: Specific outage GeoJSON +- `polygongeojson={geojson}`: Outages within polygon + +**Response**: Varies by endpoint (JSON or GeoJSON) + +**Example**: `/main.php?states` + +--- + +### 14. Local Storm Reports (LSR) +**Endpoint**: Various LSR-related parameters + +**Parameters**: +- `ohgo`: OHGO data as GeoJSON +- `ohgotable`: OHGO tabular data +- `vtec={code}`: Warning polygon by VTEC code +- `reports={code}`: Reports within warning polygon +- `outages={code}`: Power outages within warning polygon +- `verify`: Verification table data +- `stats`: Report statistics by county +- `metars` with `start`, `end`: METAR data +- `news`, `news2`, `news3`: News data with various filtering +- `newsarchive` with search parameters: Archived news +- `wv511`: West Virginia 511 data +- `ky511`: Kentucky 511 data +- `getCombinedTable`: Combined road incident table +- `updater` with `table`, `id`, `lsr`/`hide`: Update incident flags + +**Response**: Varies by endpoint + +**Example**: `/main.php?ohgo` + +--- + +### 15. NWS Staff Data +**Endpoint**: `?officestats`, `?regionstats`, `?drilldown` + +**Parameters**: +- `datetime`: Date for statistics (m-d-Y format) + +**Response**: NWS office staffing statistics + +**Example**: `/main.php?officestats` + +--- + +### 16. Verification Data +**Endpoint**: Various verification parameters + +**Parameters**: +- `lsrslist`: Simple verification list +- `reset`: Clear verification table +- `lsrs` with `zone`, `lsr`, `dir`: Update verification counts +- `inc`/`hide` with `id`, `true`/`false`: Update report visibility + +**Response**: Verification operation results + +**Example**: `/main.php?lsrslist` + +--- + +### 17. Warning Tracking +**Endpoint**: `?warntrack` + +**Description**: Get active warning tracking information for KRLX. + +**Response**: GeoJSON FeatureCollection of warning tracks + +**Example**: `/main.php?warntrack` + +--- + +## POST Endpoints + +### 1. MP4/GIF Creation +**Endpoint**: POST with form data + +**Parameters**: +- `data` (array, required): Array of image file paths +- `images` (int, required): Number of images +- `delay` (int, required): Frame delay in milliseconds +- `lastdelay` (int, required): Last frame delay +- `maxh`, `maxv` (int, optional): Maximum dimensions + +**Response**: Base64-encoded GIF data + +**Content-Type**: Not JSON (binary/gif) + +--- + +### 2. Update Camera Field +**Endpoint**: POST with form data + +**Parameters**: +- `camid` (string, required): Camera ID +- `field` (string, required): Field name ('hydro' or 'airport') +- `value` (string, required): New value ('true' or 'false') + +**Response**: +```json +{ + "success": true +} +``` + +--- + +### 3. Admin Functions +**Endpoint**: POST with form data + +**Parameters**: +- `action` (string, required): Action type + - `checkurl`: Check if URL exists + - `newcam`: Add new camera + +**For `checkurl`**: +- `url` (string, required): URL to check + +**For `newcam`**: +- `url`, `lat`, `lon`, `description`, `method`, `permission`, `owner`, `email` (all required) + +**Response**: Operation result + +--- + +### 4. Storm Data API +**Endpoint**: POST with JSON data + +**Content-Type**: `application/json` + +**Request Body**: +```json +{ + "request_type": "ohgo|ohgonopoly|power|powernopoly|wupoly|campoly", + "start_time": "2025-01-01 00:00:00", + "end_time": "2025-01-02 00:00:00", + "area_geojson": {...}, + "buffer": 2, + "outage_threshold": 10, + "polygons": ["POLYGON(...)"], + "camimages": 20 +} +``` + +**Request Types**: +- `ohgo`: OHGO data within GeoJSON polygon +- `ohgonopoly`: OHGO data without polygon filter +- `power`: Power outages within GeoJSON polygon +- `powernopoly`: Power outages without polygon filter +- `wupoly`: Weather Underground data within polygons +- `campoly`: Camera images within GeoJSON polygon + +**Response**: GeoJSON FeatureCollection + +--- + +## Error Handling + +All endpoints return consistent error responses: + +```json +{ + "error": "Error message" +} +``` + +Common HTTP status codes: +- `200`: Success +- `400`: Bad Request (missing/invalid parameters) +- `404`: Not Found +- `500`: Internal Server Error (database/query issues) + +## Migration Guide for Existing Callers + +### Simple URL Changes +Most existing callers only need to update the filename in their requests: + +**Before:** +``` +https://your-server/single.php?camid=123 +``` + +**After:** +``` +https://your-server/main.php?camid=123 +``` + +### Specific Migration Instructions + +#### 1. single.php → main.php +**No parameter changes needed** +- Existing calls: `single.php?camid={id}` +- New calls: `main.php?camid={id}` + +#### 2. cam.php → main.php +**No parameter changes needed** +- Existing calls: `cam.php` or `cam.php?cams` +- New calls: `main.php` or `main.php?cams` + +#### 3. camlist.php → main.php +**Parameter change required** +- Existing calls: `camlist.php` +- New calls: `main.php?camlist` + +#### 4. camapi.php → main.php +**No parameter changes needed** +- Existing calls: `camapi.php?cams=1&lat1=...` +- New calls: `main.php?cams=1&lat1=...` + +#### 5. camcircle.php → main.php +**Parameter change required** +- Existing calls: `camcircle.php` +- New calls: `main.php?camcircle` + +#### 6. camobs.php → main.php +**No parameter changes needed** +- Existing calls: `camobs.php?camstatic=radius&lat1=...` +- New calls: `main.php?camstatic=radius&lat1=...` + +#### 7. db.php → main.php +**No parameter changes needed** +- Existing calls: `db.php?outside` +- New calls: `main.php?outside` +- Existing calls: `db.php?outsideold` +- New calls: `main.php?outsideold` + +#### 8. fire.php → main.php +**Parameter change required** +- Existing calls: `fire.php` +- New calls: `main.php?fire` + +#### 9. individualcam.php → main.php +**No parameter changes needed** +- Existing calls: `individualcam.php?camid=123&dtg=...` +- New calls: `main.php?camid=123&dtg=...` + +#### 10. lsr.php → main.php +**No parameter changes needed** +- Existing calls: `lsr.php?ohgo` +- New calls: `main.php?ohgo` +- Existing calls: `lsr.php?vtec=...` +- New calls: `main.php?vtec=...` + +#### 11. mp4.php → main.php +**No parameter changes needed (POST data remains the same)** +- Existing calls: POST to `mp4.php` +- New calls: POST to `main.php` + +#### 12. nws.php → main.php +**No parameter changes needed** +- Existing calls: `nws.php?officestats` +- New calls: `main.php?officestats` + +#### 13. ohgo.php → main.php +**Parameter change required** +- Existing calls: `ohgo.php` +- New calls: `main.php?ohgo` + +#### 14. power.php → main.php +**Parameter change required** +- Existing calls: `power.php` +- New calls: `main.php?power` + +#### 15. powerapi.php → main.php +**No parameter changes needed** +- Existing calls: `powerapi.php?states` +- New calls: `main.php?states` +- Existing calls: `powerapi.php?county` +- New calls: `main.php?county` + +#### 16. searchapi.php → main.php +**No parameter changes needed** +- Existing calls: `searchapi.php?county` +- New calls: `main.php?county` + +#### 17. stormdata.php → main.php +**No parameter changes needed (POST JSON remains the same)** +- Existing calls: POST to `stormdata.php` +- New calls: POST to `main.php` + +#### 18. update_field.php → main.php +**No parameter changes needed (POST data remains the same)** +- Existing calls: POST to `update_field.php` +- New calls: POST to `main.php` + +#### 19. ver.php → main.php +**No parameter changes needed** +- Existing calls: `ver.php?lsrslist` +- New calls: `main.php?lsrslist` + +#### 20. warntrack.php → main.php +**Parameter change required** +- Existing calls: `warntrack.php` +- New calls: `main.php?warntrack` + +### Files NOT Requiring Changes + +#### one.php +This file contains HTML/JavaScript interface and should remain as-is. It makes calls to other endpoints that have been integrated. + +### Bulk Update Script + +For systems with many hardcoded references, use this sed command pattern: + +```bash +# Update all .php files to use main.php +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/single\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/cam\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/camapi\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/camlist\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/camobs\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/db\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/fire\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/individualcam\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/lsr\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/mp4\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/nws\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/ohgo\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/power\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/powerapi\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/searchapi\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/stormdata\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/update_field\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/ver\.php/main.php/g' {} \; +find /path/to/your/code -name "*.php" -type f -exec sed -i 's/warntrack\.php/main.php/g' {} \; +``` + +### Testing Migration + +After updating references, test key endpoints: + +```bash +# Test basic camera functionality +curl "http://your-server/main.php?camid=123" + +# Test admin functions +curl -X POST "http://your-server/main.php" -d "action=checkurl&url=http://example.com" + +# Test storm data API +curl -X POST "http://your-server/main.php" \ + -H "Content-Type: application/json" \ + -d '{"request_type":"power","start_time":"2025-01-01","area_geojson":"{\"type\":\"Polygon\",\"coordinates\":[[[-82,38],[-81,38],[-81,39],[-82,39],[-82,38]]}"}' +``` + +## Original Files Consolidated + +This integrated file consolidates the following original scripts: +- `admin.php` - Admin functions +- `cam.php` - Active cameras +- `camapi.php` - Camera API with filters +- `camcircle.php` - Camera buffer circles +- `camlist.php` - Camera list +- `camobs.php` - Camera observations +- `db.php` - Weather station data +- `fire.php` - Fire data +- `individualcam.php` - Individual camera images +- `lsr.php` - Local storm reports +- `mp4.php` - GIF creation +- `nws.php` - NWS staff data +- `ohgo.php` - OHGO road data +- `one.php` - HTML interface (not integrated) +- `power.php` - Power outages +- `powerapi.php` - Advanced power API +- `powerapitest.php` - Power API test +- `searchapi.php` - Search API +- `single.php` - Single camera info +- `stormdata.php` - Storm data API +- `update_field.php` - Camera field updates +- `ver.php` - Verification data +- `warntrack.php` - Warning tracking + +## Usage Examples + +### Get all active cameras: +```bash +curl "http://your-server/main.php?cams" +``` + +### Get camera within bounding box: +```bash +curl "http://your-server/main.php?cams&lat1=38.0&lon1=-82.0&lat2=39.0&lon2=-81.0&elevbottom=100&elevtop=1000" +``` + +### Update camera hydro status: +```bash +curl -X POST "http://your-server/main.php" \ + -d "camid=123&field=hydro&value=true" +``` + +### Query storm data with polygon: +```bash +curl -X POST "http://your-server/main.php" \ + -H "Content-Type: application/json" \ + -d '{ + "request_type": "power", + "start_time": "2025-01-01 00:00:00", + "end_time": "2025-01-02 00:00:00", + "area_geojson": { + "type": "Polygon", + "coordinates": [[[-82, 38], [-81, 38], [-81, 39], [-82, 39], [-82, 38]]] + }, + "buffer": 2 + }' +``` + +## Notes + +1. The `one.php` file contains HTML/JavaScript interface and is not integrated into this API endpoint. +2. All database connections use the same credentials and should be pooled in production. +3. Geographic data uses SRID 4326 (WGS84) unless otherwise specified. +4. Time-based queries typically use UTC unless converted to local time zones. +5. The integrated file maintains backward compatibility with existing callers by preserving all original functionality. \ No newline at end of file diff --git a/coco.py b/coco.py index d32826f..4830343 100644 --- a/coco.py +++ b/coco.py @@ -81,6 +81,10 @@ def create_dict(precipitation,snowfall,snowdepth,maxt,mint): case "T": snowfall = 0.0 snowfalltrace = True + case "0.0001": + snowfall = 0.0 + snowfalltrace = True + case _: snowfall = round(float(snowfall),1) snowfalltrace = False @@ -92,6 +96,9 @@ def create_dict(precipitation,snowfall,snowdepth,maxt,mint): case "T": snowdepth = 0.0 snowdepthtrace = True + case "0.0001": + snowdepth = 0.0 + snowdepthtrace = True case _: snowdepth = round(float(snowdepth),1) snowdepthtrace = False diff --git a/main.php b/main.php new file mode 100644 index 0000000..1f5385d --- /dev/null +++ b/main.php @@ -0,0 +1,1571 @@ + $message]); + exit; +} + +/** + * Send JSON success response + */ +function send_json($data) { + header('Content-Type: application/json'); + echo json_encode($data); + exit; +} + +/** + * Send GeoJSON response + */ +function send_geojson($features) { + $geojson_output = ['type' => 'FeatureCollection', 'features' => $features]; + header('Content-Type: application/geo+json'); + echo json_encode($geojson_output); + exit; +} + +// ============================================================================ +// MAIN ROUTING AND REQUEST HANDLING +// ============================================================================ + +// Set common headers +header('Content-Type: application/json'); +header('Access-Control-Allow-Origin: *'); +header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); +header('Access-Control-Allow-Headers: Content-Type'); + +// Handle OPTIONS request for CORS +if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { + exit(0); +} + +// Connect to database +$dbconn = connect_db(); + +// ============================================================================ +// POST REQUEST HANDLING +// ============================================================================ + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // Handle mp4.php POST request for GIF creation + if (isset($_POST['data']) && isset($_POST['images'])) { + handle_mp4_request(); + } + + // Handle update_field.php POST request + if (isset($_POST['camid']) && isset($_POST['field'])) { + handle_update_field(); + } + + // Handle admin.php POST requests + if (isset($_POST['action'])) { + handle_admin_post(); + } + + // Handle stormdata.php POST requests + $input_data = json_decode(file_get_contents('php://input'), true); + if ($input_data && isset($input_data['request_type'])) { + handle_stormdata_post($input_data); + } + + exit; +} + +// ============================================================================ +// GET REQUEST HANDLING +// ============================================================================ + +// Get all query parameters +$action = $_GET['action'] ?? ''; +$camid = $_GET['camid'] ?? ''; +$endpoint = $_GET; + +// Route based on parameters +if (empty($_GET)) { + // Default behavior - could serve a basic API info + send_json([ + 'message' => 'NWS API Main Endpoint', + 'version' => '1.0', + 'endpoints' => [ + 'single' => '?camid={id}', + 'cam' => 'Get active cameras', + 'camlist' => 'Get camera list', + 'power' => 'Get power outages', + 'admin' => '?action={checkurl|newcam}', + // Add more endpoints as needed + ] + ]); +} + +// ============================================================================ +// SPECIFIC ENDPOINT HANDLERS +// ============================================================================ + +// single.php - Get single camera info +if (isset($_GET['camid']) && !isset($_GET['action'])) { + handle_single_camera(); +} + +// admin.php - Admin functions +if (isset($_GET['action'])) { + handle_admin_get(); +} + +// cam.php - Get active cameras +if (isset($_GET['cams']) || (empty($_GET) && count($_GET) === 0)) { + handle_active_cameras(); +} + +// camlist.php - Get camera list +if (isset($_GET['camlist'])) { + handle_camera_list(); +} + +// camapi.php - Camera API with various filters +if (isset($_GET['cams']) || isset($_GET['camstatic']) || isset($_GET['camdb'])) { + handle_camera_api(); +} + +// camcircle.php - Camera circles +if (isset($_GET['camcircle'])) { + handle_camera_circles(); +} + +// camobs.php - Camera observations +if (isset($_GET['camstatic'])) { + handle_camera_observations(); +} + +// db.php - Database queries +if (isset($_GET['outside']) || isset($_GET['outsideold'])) { + handle_db_queries(); +} + +// fire.php - Fire data +if (isset($_GET['fire']) || (empty($_GET) && in_array('fire.php', get_included_files()))) { + handle_fire_data(); +} + +// individualcam.php - Individual camera images +if (isset($_GET['camid']) && (isset($_GET['dtg']) || isset($_GET['camimages']))) { + handle_individual_camera(); +} + +// lsr.php - Local storm reports +if (isset($_GET['ohgo']) || isset($_GET['ohgotable']) || isset($_GET['vtec']) || + isset($_GET['reports']) || isset($_GET['outages']) || isset($_GET['verify']) || + isset($_GET['stats']) || isset($_GET['metars']) || isset($_GET['news']) || + isset($_GET['news2']) || isset($_GET['news3']) || isset($_GET['newsarchive']) || + isset($_GET['wv511']) || isset($_GET['ky511']) || isset($_GET['getCombinedTable']) || + isset($_GET['updater'])) { + handle_lsr_requests(); +} + +// nws.php - NWS data +if (isset($_GET['officestats']) || isset($_GET['regionstats']) || isset($_GET['drilldown'])) { + handle_nws_data(); +} + +// ohgo.php - OHGO data +if (isset($_GET['ohgo']) && !isset($_GET['action'])) { + handle_ohgo_data(); +} + +// power.php - Power data +if (isset($_GET['power']) && !isset($_GET['action'])) { + handle_power_data(); +} + +// powerapi.php - Power API +if (isset($_GET['states']) || isset($_GET['max']) || isset($_GET['county']) || + isset($_GET['countyarchive']) || isset($_GET['archivepoint']) || isset($_GET['svr']) || + isset($_GET['svrpolys']) || isset($_GET['powerids']) || isset($_GET['poweridsgeojson']) || + isset($_GET['polygongeojson'])) { + handle_power_api(); +} + +// searchapi.php - Search API +if (isset($_GET['county']) || isset($_GET['countyarchive']) || isset($_GET['archivepoint']) || isset($_GET['svr'])) { + handle_search_api(); +} + +// stormdata.php - Storm data (POST handled above) +if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['storm'])) { + handle_stormdata_get(); +} + +// ver.php - Verification data +if (isset($_GET['lsrslist']) || isset($_GET['reset']) || isset($_GET['lsrs']) || + isset($_GET['inc']) || isset($_GET['hide'])) { + handle_verification_data(); +} + +// warntrack.php - Warning tracking +if (isset($_GET['warntrack'])) { + handle_warning_tracking(); +} + +// Close database connection +pg_close($dbconn); + +// ============================================================================ +// FUNCTION IMPLEMENTATIONS +// ============================================================================ + +/** + * Handle single camera request (from single.php) + */ +function handle_single_camera() { + global $dbconn; + + $camid = $_GET['camid']; + + $query = "SELECT *, COALESCE(hydro, false) as hydro, COALESCE(airport, false) as airport FROM cams WHERE camid = $1"; + $result = pg_query_params($dbconn, $query, array($camid)) + or die('Query failed: ' . pg_last_error()); + + $array = array(); + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $line['hydro'] = ($line['hydro'] === 't' || $line['hydro'] === true); + $line['airport'] = ($line['airport'] === 't' || $line['airport'] === true); + $array[] = $line; + } + + pg_free_result($result); + send_json($array); +} + +/** + * Handle active cameras request (from cam.php) + */ +function handle_active_cameras() { + global $dbconn; + + $query = "SELECT cwa,lat,lon,lastimage,county,elevation,camid,state,description,hydro,airport FROM cams WHERE active <> 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()); + + $array = array(); + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $line['hydro'] = ($line['hydro'] === 't' || $line['hydro'] === true); + $line['airport'] = ($line['airport'] === 't' || $line['airport'] === true); + $array[] = $line; + } + + pg_free_result($result); + send_json($array); +} + +/** + * Handle camera list request (from camlist.php) + */ +function handle_camera_list() { + global $dbconn; + + $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) { + send_error(500, 'Query failed: ' . pg_last_error()); + } + + $data = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $line['lat'] = floatval($line['lat']); + $line['lon'] = floatval($line['lon']); + $line['elevation'] = floatval($line['elevation']); + $line['active'] = $line['active'] === 't' ? true : false; + $data[] = $line; + } + + $response = [ + 'status' => 'success', + 'data' => $data, + 'count' => count($data) + ]; + + pg_free_result($result); + send_json($response); +} + +/** + * Handle camera API request (from camapi.php) + */ +function handle_camera_api() { + global $dbconn; + + if (isset($_GET['cams'])) { + // Handle camera bounding box query + if(isset($_GET['lat1']) && isset($_GET['lon1']) && isset($_GET['lat2']) && + isset($_GET['lon2']) && isset($_GET['elevbottom']) && isset($_GET['elevtop'])) { + + $lat1 = pg_escape_string($_GET['lat1']); + $lon1 = pg_escape_string($_GET['lon1']); + $lat2 = pg_escape_string($_GET['lat2']); + $lon2 = pg_escape_string($_GET['lon2']); + $elevbottom = pg_escape_string($_GET['elevbottom']); + $elevtop = pg_escape_string($_GET['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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + } + + if (isset($_GET['camstatic'])) { + if(isset($_GET['lat1']) && isset($_GET['lon1']) && isset($_GET['radius'])) { + $lat1 = pg_escape_string($_GET['lat1']); + $lon1 = pg_escape_string($_GET['lon1']); + $radius = pg_escape_string($_GET['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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + } + + if (isset($_GET['camdb'])) { + $result = pg_query($dbconn, "SELECT COUNT(*) FROM camdb") or die('Query failed: ' . pg_last_error()); + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } +} + +/** + * Handle camera circles request (from camcircle.php) + */ +function handle_camera_circles() { + global $dbconn; + + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + if (isset($array[0]['feature_collection'])) { + echo $array[0]['feature_collection']; + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } +} + +/** + * Handle camera observations request (from camobs.php) + */ +function handle_camera_observations() { + global $dbconn; + + if($_GET['camstatic'] == 'radius') { + if(isset($_GET['lat1']) && isset($_GET['lon1']) && isset($_GET['radius'])) { + $lat1 = pg_escape_string($_GET['lat1']); + $lon1 = pg_escape_string($_GET['lon1']); + $radius = pg_escape_string($_GET['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 = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + } + + if($_GET['camstatic'] == 'bbox') { + if(isset($_GET['lat1']) && isset($_GET['lon1']) && isset($_GET['lat2']) && + isset($_GET['lon2']) && isset($_GET['elevbottom']) && isset($_GET['elevtop'])) { + + $lat1 = pg_escape_string($_GET['lat1']); + $lon1 = pg_escape_string($_GET['lon1']); + $lat2 = pg_escape_string($_GET['lat2']); + $lon2 = pg_escape_string($_GET['lon2']); + $elevbottom = pg_escape_string($_GET['elevbottom']); + $elevtop = pg_escape_string($_GET['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 = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + } +} + +/** + * Handle database queries (from db.php) + */ +function handle_db_queries() { + global $dbconn; + + 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; + } + + pg_free_result($result); + send_json($results); + } catch (Exception $e) { + send_error(500, $e->getMessage()); + } + } + + 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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } +} + +/** + * Handle fire data request (from fire.php) + */ +function handle_fire_data() { + global $dbconn; + + $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); + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } +} + +/** + * Handle individual camera images request (from individualcam.php) + */ +function handle_individual_camera() { + global $dbconn; + + $camid = pg_escape_string($_GET['camid'] ?? null); + $camimages = $_GET['camimages'] ?? 20; + + if(isset($_GET['dtg'])) { + $endtime = pg_escape_string($_GET['dtg']); + $query = "SELECT camid, filepath, date_trunc('second', dateutc) as dateutc FROM camdb where camid = '{$camid}' and dateutc < '{$endtime}' order by dateutc desc limit '{$camimages}'"; + } else { + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); +} + +/** + * Handle MP4/GIF creation request (from mp4.php) + */ +function handle_mp4_request() { + ini_set('display_errors', 'off'); + + $elements = $_POST['data']; + $numimages = $_POST['images']; + $delay = $_POST['delay']; + $lastdelay = $_POST['lastdelay']; + $maxh = $_POST['maxh'] ?? 500; + $maxv = $_POST['maxv'] ?? 400; + + 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; +} + +/** + * Handle update field request (from update_field.php) + */ +function handle_update_field() { + global $dbconn; + + $camid = $_POST['camid']; + $field = $_POST['field']; + $value = $_POST['value']; + + // Validate inputs + if (empty($camid) || empty($field)) { + send_json(['success' => false, 'message' => 'Invalid input']); + } + + // Check if field is valid + if (!in_array($field, ['hydro', 'airport'])) { + send_json(['success' => false, 'message' => 'Invalid field']); + } + + // Convert to proper boolean for PostgreSQL + $value_bool = ($value === 'true'); + + // Update field value in database + $query = "UPDATE cams SET $field = $1 WHERE camid = $2"; + $result = pg_query_params($dbconn, $query, array($value_bool ? 't' : 'f', $camid)); + + if ($result) { + send_json(['success' => true]); + } else { + $error = pg_last_error($dbconn); + send_json(['success' => false, 'message' => $error]); + } +} + +/** + * Handle admin GET requests (from admin.php) + */ +function handle_admin_get() { + global $dbconn; + + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } +} + +/** + * Handle admin POST requests (from admin.php) + */ +function handle_admin_post() { + global $dbconn; + + $action = $_POST['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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + + 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); + pg_free_result($result); + + shell_exec('python3 /var/www/html/work/runallgeom.py'); + + send_json($status); + } +} + +/** + * Handle LSR requests (from lsr.php) - simplified version + */ +function handle_lsr_requests() { + global $dbconn; + + // Handle OHGO GeoJSON + 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'"; + + $result = pg_query($dbconn); + if (!$result) { + send_error(500, 'Query failed: ' . pg_last_error()); + } + + $resultArray = pg_fetch_all($result); + + if ($resultArray && isset($resultArray[0]['geojson'])) { + echo $resultArray[0]['geojson']; + } else { + send_json(['error' => 'No results found']); + } + + pg_free_result($result); + exit; + } + + // Handle OHGO table + if (isset($_GET['ohgotable'])) { + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + + // Handle VTEC query + 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; + "; + + $result = pg_query_params($dbconn, $query, array($vtec)) + or die('Query failed: ' . pg_last_error()); + + $resultArray = pg_fetch_all($result); + + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } + + // Handle reports query + if (isset($_GET['reports'])) { + $vtec = $_GET['reports']; + $hours = $_GET['hours'] ?? 6; + + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + + // Handle current reports (no GET parameters) + if (empty($_GET) || (count($_GET) === 1 && isset($_GET['reports']))) { + $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); + + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } +} + +/** + * Handle NWS data requests (from nws.php) - simplified version + */ +function handle_nws_data() { + global $dbconn; + + if (isset($_GET['officestats'])) { + $query = "SELECT * FROM nws order by lastupdate asc"; + $result = pg_query($query) or die('Query failed: ' . pg_last_error()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } +} + +/** + * Handle OHGO data request (from ohgo.php) + */ +function handle_ohgo_data() { + global $dbconn; + + $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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); +} + +/** + * Handle power data request (from power.php) + */ +function handle_power_data() { + global $dbconn; + + $query = "SELECT lat,lon,outagen FROM power WHERE active = true and cwa = 'RLX'"; + $result = pg_query($query) or die('Query failed: ' . pg_last_error()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); +} + +/** + * Handle power API requests (from powerapi.php) - simplified version + */ +function handle_power_api() { + global $dbconn; + + // Handle default power outage request + if (empty($_GET) || (count($_GET) === 1 && isset($_GET['power']))) { + $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) { + send_error(500, 'Query failed: ' . pg_last_error()); + } + + $resultArray = pg_fetch_all($result); + + if ($resultArray && isset($resultArray[0]['json_build_object'])) { + echo $resultArray[0]['json_build_object']; + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } + + // Handle states request + if (isset($_GET['states'])) { + $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); + if ($result === false) { + send_error(500, 'Query failed: ' . pg_last_error()); + } + + $resultArray = pg_fetch_all($result); + + if ($resultArray && isset($resultArray[0]['jsonb_build_object'])) { + echo $resultArray[0]['jsonb_build_object']; + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } + + // Handle county request + if (isset($_GET['county'])) { + $query = " + SELECT DISTINCT ON (county, state) + county, + state, + SUM(outages) as outage, + update as time, + SUM(served) as served, + ROUND(CAST((SUM(outages)::FLOAT / SUM(served)) * 100 AS NUMERIC), 2) as perout + FROM countyoutages + WHERE update = (SELECT MAX(update) FROM countyoutages) + AND cwa = $1 + GROUP BY county, state, update + "; + + $result = pg_query_params($dbconn, $query, ['RLX']); + if ($result === false) { + send_error(500, 'Query failed: ' . pg_last_error()); + } + + $results = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $results[] = $line; + } + + pg_free_result($result); + send_json($results); + } + + // Handle current SVR warnings + 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); + + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } +} + +/** + * Handle search API requests (from searchapi.php) + */ +function handle_search_api() { + global $dbconn; + + // Handle 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()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + + // Handle SVR current + 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); + + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } +} + +/** + * Handle storm data POST requests (from stormdata.php) + */ +function handle_stormdata_post($input_data) { + global $dbconn; + + $request_type = $input_data['request_type']; + + 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; + } +} + +/** + * Handle verification data requests (from ver.php) + */ +function handle_verification_data() { + global $dbconn; + + // Handle default request + if (empty($_GET) || (count($_GET) === 1 && isset($_GET['ver']))) { + $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); + + if (isset($resultArray[0]['jsonb_build_object'])) { + echo($resultArray[0]['jsonb_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); + exit; + } + + // Handle lsrslist + if (isset($_GET['lsrslist'])) { + $result = pg_query($dbconn,"SELECT * from simplever") or die('Query failed: ' . pg_last_error()); + + $array = []; + while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) { + $array[] = $line; + } + + pg_free_result($result); + send_json($array); + } + + // Handle reset + if (isset($_GET['reset'])) { + $result = pg_query($dbconn,"truncate simplever") or die('Query failed: ' . pg_last_error()); + + $resultArray = pg_fetch_all($result); + send_json($resultArray); + } + + // Handle lsrs + if (isset($_GET['lsrs'])) { + if (isset($_GET['zone'])) { + $zone = $_GET['zone']; + $lsr = isset($_GET['lsr']) ? (int) $_GET['lsr'] : 1; + $dir = $_GET['dir'] ?? 1; + + 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()); + } 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); + send_json($resultArray); + } + } + + // Handle inc/hide + if (isset($_GET['inc']) || isset($_GET['hide'])) { + $field = isset($_GET['inc']) ? 'inc' : 'hide'; + $value = $_GET[$field] ?? 'false'; + $hideflag = ($value == 'true') ? 'true' : 'false'; + $id = (int) $_GET['id']; + $query = "UPDATE reports SET $field = $1 WHERE id = $2"; + $result = pg_query_params($dbconn, $query, array($hideflag, $id)) or die('Query failed: ' . pg_last_error()); + + pg_free_result($result); + send_json(['success' => true]); + } +} + +/** + * Handle warning tracking requests (from warntrack.php) + */ +function handle_warning_tracking() { + global $dbconn; + + $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); + + if (isset($resultArray[0]['json_build_object'])) { + echo($resultArray[0]['json_build_object']); + } else { + send_json(['type' => 'FeatureCollection', 'features' => []]); + } + + pg_free_result($result); +} + +// ============================================================================ +// STORMDATA HELPER FUNCTIONS (from stormdata.php) +// ============================================================================ + +function handle_ohgo_request($dbconn, array $data): void { + $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.'); + } + + 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.'); + } + + $features = []; + while ($line = pg_fetch_assoc($result)) { + $geometry = json_decode($line['geometry']); + if (json_last_error() !== JSON_ERROR_NONE) { continue; } + $properties = $line; unset($properties['geometry']); + $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; + } + pg_free_result($result); + + send_geojson($features); +} + +function handle_ohgo_request_no_poly($dbconn, array $data): void { + $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.'); + } + + $features = []; + while ($line = pg_fetch_assoc($result)) { + $geometry = json_decode($line['geometry']); + if (json_last_error() !== JSON_ERROR_NONE) { continue; } + $properties = $line; unset($properties['geometry']); + $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; + } + pg_free_result($result); + + send_geojson($features); +} + +function handle_power_request($dbconn, array $data): void { + $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.'); + } + + 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, + $geojson_str, + $end, + $buffer_hours_int + ); + $result = pg_query_params($dbconn, $query, $params); + if (!$result) { + send_error(500, 'Database query failed for power data.'); + } + + $features = []; + while ($line = pg_fetch_assoc($result)) { + $geometry = json_decode($line['geometry']); + if (json_last_error() !== JSON_ERROR_NONE) { continue; } + $properties = $line; unset($properties['geometry']); + $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; + } + pg_free_result($result); + + send_geojson($features); +} + +function handle_power_request_no_poly($dbconn, array $data): void { + $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, + $end, + $buffer_hours_int, + $outage_thresh + ); + $result = pg_query_params($dbconn, $query, $params); + if (!$result) { + send_error(500, 'Database query failed for power data.'); + } + + $features = []; + while ($line = pg_fetch_assoc($result)) { + $geometry = json_decode($line['geometry']); + if (json_last_error() !== JSON_ERROR_NONE) { continue; } + $properties = $line; unset($properties['geometry']); + $features[] = ['type' => 'Feature', 'geometry' => $geometry, 'properties' => $properties]; + } + pg_free_result($result); + + send_geojson($features); +} + +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)) { + send_error(500, 'No polygons provided'); + } + + $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) { + send_error(500, pg_last_error($dbconn)); + } + + $results = []; + while ($row = pg_fetch_assoc($result)) { + $results[] = $row; + } + + pg_free_result($result); + send_json($results); +} + +function handle_cam_request($dbconn, array $data): void { + $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.'); + } + + 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 + ) ORDER BY dateutc ASC + ) AS images + FROM + camdb + WHERE + dateutc >= $1::timestamp + AND dateutc <= $2::timestamp + GROUP BY + camid + ) AS img_agg ON c.camid = img_agg.camid + WHERE + c.active = TRUE + AND ST_Within(c.geom, ST_GeomFromGeoJSON($3)) + ORDER BY + c.camid; + "; + + $params = array( + $start_time_str, + $end_time_str, + $geojson_str + ); + + $result = pg_query_params($dbconn, $query, $params); + + if (!$result) { + send_error(500, 'Database query failed for camera data.'); + } + + $cameras_output = []; + while ($row = pg_fetch_assoc($result)) { + $geometry = json_decode($row['geometry_geojson']); + if (json_last_error() !== JSON_ERROR_NONE) { + $geometry = null; + } + + $images = json_decode($row['images']); + if (json_last_error() !== JSON_ERROR_NONE) { + $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); + + header('Content-Type: application/json'); + echo json_encode($cameras_output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + exit; +} + +?> \ No newline at end of file