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() ]); } ?>