257 lines
13 KiB
PHP
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()
|
|
]);
|
|
}
|
|
?>
|