164 lines
6.8 KiB
PHP
164 lines
6.8 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../db/config.php';
|
|
|
|
header('Content-Type: application/json');
|
|
ini_set('display_errors', 0);
|
|
|
|
// --- Utility Functions ---
|
|
|
|
function pointInPolygon($point, $polygon) {
|
|
$inside = false;
|
|
$x = $point[0];
|
|
$y = $point[1];
|
|
for ($i = 0, $j = count($polygon) - 1; $i < count($polygon); $j = $i++) {
|
|
$xi = $polygon[$i][0]; $yi = $polygon[$i][1];
|
|
$xj = $polygon[$j][0]; $yj = $polygon[$j][1];
|
|
$intersect = (($yi > $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);
|
|
}
|
|
?>
|