$y) != ($yj > $y)) && ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi); if ($intersect) { $inside = !$inside; } } return $inside; } /** * Haversine formula for distance between two points on a sphere. * @param float $lat1 * @param float $lon1 * @param float $lat2 * @param float $lon2 * @return float Distance in kilometers. */ 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) { $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) { return ['score' => 0, 'details' => 'Could not fetch wildfire data.']; } $wildfireData = json_decode($wildfireDataJson, true); if (!$wildfireData || empty($wildfireData['features'])) { return ['score' => 0, 'details' => 'No active wildfires found.']; } $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." ]; } function getHurricaneRisk($lat, $lon) { // Construct the URL to the local hurricanes API $localApiUrl = 'http://localhost/api/hurricanes.php'; $context = stream_context_create(['http' => ['timeout' => 15]]); $geoJsonData = @file_get_contents($localApiUrl, false, $context); if (!$geoJsonData) { return ['score' => 0, 'details' => '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) { // We are only interested in the forecast cone polygons if (isset($feature['properties']['layer']) && $feature['properties']['layer'] === 'cone' && $feature['geometry']['type'] === 'Polygon') { $polygon = $feature['geometry']['coordinates'][0]; if (pointInPolygon($point, $polygon)) { $risk = 100; // Max risk if inside the cone $stormName = $feature['properties']['STORMNAME'] ?? 'Unnamed Storm'; $details[] = "Inside the forecast cone for " . $stormName; break; // Exit after finding the first cone } } } return [ 'score' => $risk, 'details' => !empty($details) ? implode(', ', $details) : 'Not in any active hurricane forecast cones.' ]; } function getHistoricalHurricaneRisk($lat, $lon) { $pdo = db(); if (!$pdo) { return ['score' => 0, 'details' => 'Database connection failed.']; } // Search for storms within a 1-degree radius (~111 km) $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; // Simple scoring: 10 points per storm, max 100 $score = min($count * 10, 100); return [ 'score' => $score, 'details' => "{$count} historical storms passed within ~111km." ]; } // --- Main Execution --- $lat = $_GET['lat'] ?? null; $lon = $_GET['lon'] ?? null; if (!$lat || !$lon) { http_response_code(400); echo json_encode(['error' => 'Latitude and longitude are required.']); exit; } $wildfireRisk = getWildfireRisk((float)$lat, (float)$lon); $hurricaneRisk = getHurricaneRisk((float)$lat, (float)$lon); $historicalRisk = getHistoricalHurricaneRisk((float)$lat, (float)$lon); // Weighted average for the total score $totalScore = ($wildfireRisk['score'] * 0.4) + ($hurricaneRisk['score'] * 0.4) + ($historicalRisk['score'] * 0.2); $response = [ 'latitude' => (float)$lat, 'longitude' => (float)$lon, 'risk_score' => round($totalScore), 'factors' => [ 'wildfire' => $wildfireRisk, 'live_hurricane' => $hurricaneRisk, 'historical_hurricane' => $historicalRisk, ] ]; echo json_encode($response, JSON_PRETTY_PRINT);