Files
d1041231029-sig-b/api/seed_regional_poverty_data.php
T
2026-06-02 12:40:37 +07:00

257 lines
13 KiB
PHP

<?php
header('Content-Type: application/json');
require_once 'config.php';
function randomFloat($min, $max) {
return $min + (mt_rand() / mt_getrandmax()) * ($max - $min);
}
function buildGridLocation($bbox, $index, $total) {
$rows = max(1, (int)ceil(sqrt($total)));
$cols = max(1, (int)ceil($total / $rows));
$row = intdiv($index, $cols);
$col = $index % $cols;
$latStep = ($bbox['max_lat'] - $bbox['min_lat']) / $rows;
$lngStep = ($bbox['max_lng'] - $bbox['min_lng']) / $cols;
$baseLat = $bbox['min_lat'] + ($row + 0.5) * $latStep;
$baseLng = $bbox['min_lng'] + ($col + 0.5) * $lngStep;
$latMin = max($bbox['min_lat'], $baseLat - $latStep * 0.35);
$latMax = min($bbox['max_lat'], $baseLat + $latStep * 0.35);
$lngMin = max($bbox['min_lng'], $baseLng - $lngStep * 0.35);
$lngMax = min($bbox['max_lng'], $baseLng + $lngStep * 0.35);
return [
'latitude' => randomFloat($latMin, $latMax),
'longitude' => randomFloat($lngMin, $lngMax)
];
}
function distanceSquared($lat1, $lng1, $lat2, $lng2) {
$dLat = $lat1 - $lat2;
$dLng = $lng1 - $lng2;
return ($dLat * $dLat) + ($dLng * $dLng);
}
function findNearestMasjidId($latitude, $longitude, $masjids) {
$nearestId = null;
$nearestDistance = null;
foreach ($masjids as $masjid) {
$distance = distanceSquared($latitude, $longitude, $masjid['latitude'], $masjid['longitude']);
if ($nearestDistance === null || $distance < $nearestDistance) {
$nearestDistance = $distance;
$nearestId = $masjid['id'];
}
}
return $nearestId;
}
try {
$db = getDB();
$force = isset($_POST['force']) || isset($_GET['force']);
// Load GeoJSON land polygons to avoid placing points at sea
$geojsonPath = __DIR__ . '/../export.geojson';
$landPolygons = [];
if (file_exists($geojsonPath)) {
$gj = json_decode(file_get_contents($geojsonPath), true);
if ($gj && isset($gj['features']) && is_array($gj['features'])) {
foreach ($gj['features'] as $feature) {
if (!isset($feature['geometry'])) continue;
$geom = $feature['geometry'];
if ($geom['type'] === 'Polygon') {
$landPolygons[] = $geom['coordinates'];
} elseif ($geom['type'] === 'MultiPolygon') {
foreach ($geom['coordinates'] as $poly) $landPolygons[] = $poly;
}
}
}
}
function pointInRing($lat, $lng, $ring) {
$inside = false;
$j = count($ring) - 1;
for ($i = 0; $i < count($ring); $i++) {
$xi = $ring[$i][0]; $yi = $ring[$i][1];
$xj = $ring[$j][0]; $yj = $ring[$j][1];
$intersect = (($yi > $lat) != ($yj > $lat)) && ($lng < ($xj - $xi) * ($lat - $yi) / ($yj - $yi + 0.0) + $xi);
if ($intersect) $inside = !$inside;
$j = $i;
}
return $inside;
}
function isPointOnLand($lat, $lng, $landPolygons) {
if (empty($landPolygons)) return true; // if no polygons available, default to true
foreach ($landPolygons as $poly) {
// poly is array of rings; test outer ring first
$outer = $poly[0];
if (pointInRing($lat, $lng, $outer)) {
// ensure not in hole
$isInHole = false;
for ($r = 1; $r < count($poly); $r++) {
if (pointInRing($lat, $lng, $poly[$r])) {
$isInHole = true;
break;
}
}
if (!$isInHole) return true;
}
}
return false;
}
$regional_data = [
['region' => 'Jakarta', 'province' => 'DKI Jakarta', 'bbox' => ['min_lat' => -6.40, 'max_lat' => -5.90, 'min_lng' => 106.60, 'max_lng' => 106.95], 'masjids' => 15, 'need_points' => 2435],
['region' => 'Bandung', 'province' => 'Jawa Barat', 'bbox' => ['min_lat' => -7.50, 'max_lat' => -5.90, 'min_lng' => 106.00, 'max_lng' => 108.10], 'masjids' => 10, 'need_points' => 1500],
['region' => 'Bekasi', 'province' => 'Jawa Barat', 'bbox' => ['min_lat' => -7.20, 'max_lat' => -6.00, 'min_lng' => 106.50, 'max_lng' => 108.00], 'masjids' => 6, 'need_points' => 800],
['region' => 'Semarang', 'province' => 'Jawa Tengah', 'bbox' => ['min_lat' => -8.10, 'max_lat' => -5.90, 'min_lng' => 108.30, 'max_lng' => 111.20], 'masjids' => 9, 'need_points' => 1400],
['region' => 'Yogyakarta', 'province' => 'DI Yogyakarta', 'bbox' => ['min_lat' => -8.20, 'max_lat' => -7.30, 'min_lng' => 109.90, 'max_lng' => 110.90], 'masjids' => 7, 'need_points' => 900],
['region' => 'Surabaya', 'province' => 'Jawa Timur', 'bbox' => ['min_lat' => -8.80, 'max_lat' => -6.80, 'min_lng' => 111.00, 'max_lng' => 114.50], 'masjids' => 12, 'need_points' => 1800],
['region' => 'Malang', 'province' => 'Jawa Timur', 'bbox' => ['min_lat' => -8.60, 'max_lat' => -7.30, 'min_lng' => 110.80, 'max_lng' => 113.20], 'masjids' => 5, 'need_points' => 700],
['region' => 'Medan', 'province' => 'Sumatera Utara', 'bbox' => ['min_lat' => 1.00, 'max_lat' => 4.50, 'min_lng' => 98.00, 'max_lng' => 100.50], 'masjids' => 11, 'need_points' => 1600],
['region' => 'Palembang', 'province' => 'Sumatera Selatan', 'bbox' => ['min_lat' => -4.20, 'max_lat' => -2.00, 'min_lng' => 103.00, 'max_lng' => 106.10], 'masjids' => 8, 'need_points' => 1100],
['region' => 'Pekanbaru', 'province' => 'Riau', 'bbox' => ['min_lat' => -0.60, 'max_lat' => 2.20, 'min_lng' => 100.00, 'max_lng' => 102.90], 'masjids' => 6, 'need_points' => 850],
['region' => 'Jambi', 'province' => 'Jambi', 'bbox' => ['min_lat' => -2.30, 'max_lat' => 0.50, 'min_lng' => 101.00, 'max_lng' => 104.20], 'masjids' => 4, 'need_points' => 600],
['region' => 'Padang', 'province' => 'Sumatera Barat', 'bbox' => ['min_lat' => -2.90, 'max_lat' => 1.00, 'min_lng' => 99.40, 'max_lng' => 101.80], 'masjids' => 5, 'need_points' => 700],
['region' => 'Makassar', 'province' => 'Sulawesi Selatan', 'bbox' => ['min_lat' => -6.50, 'max_lat' => -2.50, 'min_lng' => 118.00, 'max_lng' => 121.80], 'masjids' => 9, 'need_points' => 1300],
['region' => 'Manado', 'province' => 'Sulawesi Utara', 'bbox' => ['min_lat' => 0.80, 'max_lat' => 4.00, 'min_lng' => 123.00, 'max_lng' => 125.80], 'masjids' => 4, 'need_points' => 550],
['region' => 'Kendari', 'province' => 'Sulawesi Tenggara', 'bbox' => ['min_lat' => -6.50, 'max_lat' => -2.00, 'min_lng' => 120.00, 'max_lng' => 124.50], 'masjids' => 3, 'need_points' => 400],
['region' => 'Palu', 'province' => 'Sulawesi Tengah', 'bbox' => ['min_lat' => -3.00, 'max_lat' => 2.00, 'min_lng' => 118.00, 'max_lng' => 123.50], 'masjids' => 4, 'need_points' => 500],
['region' => 'Banjarmasin', 'province' => 'Kalimantan Selatan', 'bbox' => ['min_lat' => -4.20, 'max_lat' => -1.00, 'min_lng' => 114.00, 'max_lng' => 116.80], 'masjids' => 7, 'need_points' => 1000],
['region' => 'Pontianak', 'province' => 'Kalimantan Barat', 'bbox' => ['min_lat' => -3.50, 'max_lat' => 3.00, 'min_lng' => 108.00, 'max_lng' => 112.80], 'masjids' => 5, 'need_points' => 700],
['region' => 'Samarinda', 'province' => 'Kalimantan Timur', 'bbox' => ['min_lat' => -2.80, 'max_lat' => 2.00, 'min_lng' => 115.00, 'max_lng' => 119.80], 'masjids' => 6, 'need_points' => 850],
['region' => 'Palangkaraya', 'province' => 'Kalimantan Tengah', 'bbox' => ['min_lat' => -3.80, 'max_lat' => 1.20, 'min_lng' => 112.00, 'max_lng' => 116.50], 'masjids' => 3, 'need_points' => 450],
['region' => 'Jayapura', 'province' => 'Papua', 'bbox' => ['min_lat' => -9.50, 'max_lat' => -1.00, 'min_lng' => 130.00, 'max_lng' => 141.50], 'masjids' => 4, 'need_points' => 700],
['region' => 'Mataram', 'province' => 'Nusa Tenggara Barat', 'bbox' => ['min_lat' => -9.20, 'max_lat' => -7.00, 'min_lng' => 115.20, 'max_lng' => 119.00], 'masjids' => 3, 'need_points' => 450],
['region' => 'Kupang', 'province' => 'Nusa Tenggara Timur', 'bbox' => ['min_lat' => -11.20, 'max_lat' => -8.00, 'min_lng' => 118.50, 'max_lng' => 125.50], 'masjids' => 2, 'need_points' => 300],
['region' => 'Ambon', 'province' => 'Maluku', 'bbox' => ['min_lat' => -8.50, 'max_lat' => -1.00, 'min_lng' => 125.00, 'max_lng' => 134.50], 'masjids' => 2, 'need_points' => 250],
['region' => 'Denpasar', 'province' => 'Bali', 'bbox' => ['min_lat' => -9.20, 'max_lat' => -8.00, 'min_lng' => 114.50, 'max_lng' => 115.80], 'masjids' => 1, 'need_points' => 100]
];
$stmt = $db->prepare('SELECT COUNT(*) as count FROM masjids');
$stmt->execute();
$masjidCount = (int)$stmt->fetch(PDO::FETCH_ASSOC)['count'];
if ($masjidCount > 20 && !$force) {
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Regional poverty data already exists. Use force=true to reseed.',
'current_masjid_count' => $masjidCount
]);
exit;
}
if ($force && $masjidCount > 0) {
$db->exec('DELETE FROM need_points');
$db->exec('DELETE FROM masjids');
}
$db->beginTransaction();
$masjidStmt = $db->prepare('INSERT INTO masjids (name, latitude, longitude, radius_meters) VALUES (?, ?, ?, ?)');
$needPointStmt = $db->prepare('INSERT INTO need_points (name, latitude, longitude, masjid_id, household_name, head_name, economic_status) VALUES (?, ?, ?, ?, ?, ?, ?)');
$insertedMasjids = 0;
$insertedNeedPoints = 0;
foreach ($regional_data as $region) {
$provinceMasjids = [];
for ($i = 0; $i < $region['masjids']; $i++) {
// ensure masjid sits on land (try a few attempts)
$attempt = 0;
do {
$location = buildGridLocation($region['bbox'], $i, $region['masjids']);
$attempt++;
} while ($attempt < 8 && !isPointOnLand($location['latitude'], $location['longitude'], $landPolygons));
$latSpan = $region['bbox']['max_lat'] - $region['bbox']['min_lat'];
$lngSpan = $region['bbox']['max_lng'] - $region['bbox']['min_lng'];
$coverageEstimate = (int)round((($latSpan + $lngSpan) * 111000) / max(1, $region['masjids']) * 0.55);
$radiusMeters = max(20000, min(180000, $coverageEstimate + rand(3000, 12000)));
$masjidStmt->execute([
'Masjid ' . $region['region'] . ' - ' . ($i + 1),
$location['latitude'],
$location['longitude'],
$radiusMeters
]);
$provinceMasjids[] = [
'id' => (int)$db->lastInsertId(),
'latitude' => $location['latitude'],
'longitude' => $location['longitude']
];
$insertedMasjids++;
}
for ($i = 0; $i < $region['need_points']; $i++) {
// ensure need point lands on land; allow several tries
$attempt = 0;
do {
$pointLocation = buildGridLocation($region['bbox'], $i, $region['need_points']);
$attempt++;
} while ($attempt < 12 && !isPointOnLand($pointLocation['latitude'], $pointLocation['longitude'], $landPolygons));
$nearestMasjidId = findNearestMasjidId($pointLocation['latitude'], $pointLocation['longitude'], $provinceMasjids);
// generate household and head names and economic status
$hhName = 'Keluarga ' . $region['region'] . ' - ' . ($i + 1);
$firstNames = ['Ahmad','Budi','Siti','Aulia','Rizal','Dewi','Agus','Fitri','Hendra','Yulia'];
$lastNames = ['Santoso','Wijaya','Pratama','Hidayat','Saputra','Nur','Ibrahim','Setiawan','Rahman','Putri'];
$headName = $firstNames[array_rand($firstNames)] . ' ' . $lastNames[array_rand($lastNames)];
$statuses = ['sangat_miskin','miskin','rentan','cukup','baik'];
$econ = $statuses[array_rand($statuses)];
$needPointStmt->execute([
'Keluarga Miskin ' . $region['region'] . ' - ' . ($i + 1),
$pointLocation['latitude'],
$pointLocation['longitude'],
$nearestMasjidId,
$hhName,
$headName,
$econ
]);
$insertedNeedPoints++;
}
}
$db->commit();
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Regional poverty assistance data seeded successfully',
'masjids_inserted' => $insertedMasjids,
'need_points_inserted' => $insertedNeedPoints,
'regions_covered' => count($regional_data),
'total_need_points_capacity' => 21935
]);
} catch (PDOException $e) {
if (isset($db) && $db->inTransaction()) {
$db->rollBack();
}
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
if (isset($db) && $db->inTransaction()) {
$db->rollBack();
}
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>