$y) != ($yj > $y)) && ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi); if ($intersect) $inside = !$inside; } return $inside; } function haversineDistance($lat1, $lon1, $lat2, $lon2) { $earthRadius = 6371; // km $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2); $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); return $earthRadius * $c; } // --- Risk Calculation Functions --- function getWildfireRisk($lat, $lon) { try { $wildfireUrl = 'https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS_Interagency_Fire_Perimeters_Current/FeatureServer/0/query?where=1%3D1&outFields=poly_IncidentName,poly_GISAcres,attr_InitialLatitude,attr_InitialLongitude&outSR=4326&f=json'; $context = stream_context_create(['http' => ['timeout' => 10]]); $wildfireDataJson = @file_get_contents($wildfireUrl, false, $context); if (!$wildfireDataJson) throw new Exception('Could not fetch wildfire data from source.'); $wildfireData = json_decode($wildfireDataJson, true); if (!$wildfireData || !isset($wildfireData['features'])) return ['score' => 0, 'details' => 'No active wildfires found or data invalid.']; $risk = 0; $closest_distance = PHP_INT_MAX; $closest_fire = null; foreach ($wildfireData['features'] as $fire) { $fireLat = $fire['attributes']['attr_InitialLatitude']; $fireLon = $fire['attributes']['attr_InitialLongitude']; if ($fireLat && $fireLon) { $distance = haversineDistance($lat, $lon, $fireLat, $fireLon); if ($distance < $closest_distance) { $closest_distance = $distance; $closest_fire = $fire['attributes']['poly_IncidentName']; } } } if ($closest_distance < 50) { // 50km threshold $risk = 100 - ($closest_distance / 50) * 100; } return ['score' => round($risk), 'details' => $closest_fire ? "Closest fire: {$closest_fire} (" . round($closest_distance) . " km away)" : "No fires within 50km."]; } catch (Exception $e) { error_log("Wildfire Risk Error: " . $e->getMessage()); return ['score' => 0, 'details' => 'Error calculating wildfire risk.']; } } function getHurricaneRisk($lat, $lon) { try { $localApiUrl = 'http://localhost/api/hurricanes.php'; $context = stream_context_create(['http' => ['timeout' => 15]]); $geoJsonData = @file_get_contents($localApiUrl, false, $context); if (!$geoJsonData) throw new Exception('Could not fetch local hurricane data.'); $hurricaneData = json_decode($geoJsonData, true); if (!$hurricaneData || empty($hurricaneData['features'])) return ['score' => 0, 'details' => 'No active hurricane data found.']; $risk = 0; $details = []; $point = [(float)$lon, (float)$lat]; foreach ($hurricaneData['features'] as $feature) { if (isset($feature['properties']['layer']) && $feature['properties']['layer'] === 'cone' && $feature['geometry']['type'] === 'Polygon') { $polygon = $feature['geometry']['coordinates'][0]; if (pointInPolygon($point, $polygon)) { $risk = 100; $stormName = $feature['properties']['STORMNAME'] ?? 'Unnamed Storm'; $details[] = "Inside the forecast cone for " . $stormName; break; } } } return ['score' => $risk, 'details' => !empty($details) ? implode(', ', $details) : 'Not in any active hurricane forecast cones.']; } catch (Exception $e) { error_log("Hurricane Risk Error: " . $e->getMessage()); return ['score' => 0, 'details' => 'Error calculating hurricane risk.']; } } function getHistoricalHurricaneRisk($lat, $lon) { try { $pdo = db(); if (!$pdo) return ['score' => 0, 'details' => 'Database connection failed.']; $radius = 1.0; $stmt = $pdo->prepare("SELECT COUNT(DISTINCT storm_id) as storm_count FROM hurricanes WHERE ABS(lat - :lat) < :radius AND ABS(lon - :lon) < :radius"); $stmt->execute(['lat' => $lat, 'lon' => $lon, 'radius' => $radius]); $result = $stmt->fetch(PDO::FETCH_ASSOC); $count = $result['storm_count'] ?? 0; $score = min($count * 10, 100); return ['score' => $score, 'details' => "{$count} historical storms passed within ~111km."]; } catch (Exception $e) { error_log("Historical Hurricane Risk Error: " . $e->getMessage()); return ['score' => 0, 'details' => 'Error calculating historical hurricane risk.']; } } function calculateRiskScore($lat, $lon) { $wildfireRisk = getWildfireRisk((float)$lat, (float)$lon); $hurricaneRisk = getHurricaneRisk((float)$lat, (float)$lon); $historicalRisk = getHistoricalHurricaneRisk((float)$lat, (float)$lon); $totalScore = ($wildfireRisk['score'] * 0.4) + ($hurricaneRisk['score'] * 0.4) + ($historicalRisk['score'] * 0.2); return round($totalScore); } try { $pdo = db(); $method = $_SERVER['REQUEST_METHOD']; if ($method === 'GET') { $stmt = $pdo->query('SELECT id, name, latitude, longitude, city, state FROM facilities ORDER BY created_at DESC'); $allFacilities = $stmt->fetchAll(PDO::FETCH_ASSOC); $sampleSize = ceil(count($allFacilities) * 0.1); if ($sampleSize < 1 && count($allFacilities) > 0) $sampleSize = 1; shuffle($allFacilities); $facilitiesSample = array_slice($allFacilities, 0, $sampleSize); $facilitiesWithRisk = []; foreach ($facilitiesSample as $facility) { $facility['risk'] = calculateRiskScore($facility['latitude'], $facility['longitude']); $facilitiesWithRisk[] = $facility; } echo json_encode(['success' => true, 'facilities' => $facilitiesWithRisk]); } else { http_response_code(405); echo json_encode(['success' => false, 'error' => 'Method Not Allowed']); } } catch (Exception $e) { http_response_code(500); $error = [ 'success' => false, 'error' => 'An unexpected error occurred in facilities API.', 'message' => $e->getMessage(), ]; error_log(print_r($error, true)); echo json_encode($error); } ?>