Skip to content

Geographic Queries ​

Laravel Nusa provides geographic data including coordinates, boundaries, and postal codes. This guide shows how to work with geographic information for mapping, location services, and spatial analysis.

Working with Coordinates ​

Basic Coordinate Access ​

php
use Creasi\Nusa\Models\Province;

$province = Province::find('33'); // Central Java

// Get center coordinates
$latitude = $province->latitude;   // -6.9934809206806
$longitude = $province->longitude; // 110.42024335421

echo "Central Java center: {$latitude}, {$longitude}";

Boundary Coordinates ​

php
use Creasi\Nusa\Models\{Province, Regency};

$province = Province::find('33');

// Get boundary coordinates (if available)
$boundaries = $province->coordinates;

if ($boundaries) {
    echo "Province has " . count($boundaries) . " boundary points";
    
    // Convert to GeoJSON format
    $geoJson = [
        'type' => 'Feature',
        'properties' => [
            'name' => $province->name,
            'code' => $province->code
        ],
        'geometry' => [
            'type' => 'Polygon',
            'coordinates' => [$boundaries]
        ]
    ];
    
    // Use with mapping libraries
    return response()->json($geoJson);
}

Postal Code Queries ​

Find by Postal Code ​

php
use Creasi\Nusa\Models\Village;

// Find villages by postal code
$villages = Village::where('postal_code', '51111')->get();

foreach ($villages as $village) {
    echo "{$village->name}, {$village->district->name}";
}

// Find all postal codes in a province
$province = Province::find('33');
$postalCodes = $province->postal_codes;

echo "Postal codes in {$province->name}: " . implode(', ', $postalCodes);

Postal Code Validation ​

php
function validatePostalCode(string $postalCode, string $villageCode): bool
{
    $village = Village::find($villageCode);
    
    return $village && $village->postal_code === $postalCode;
}

// Usage
$isValid = validatePostalCode('51111', '3375011002');

Distance Calculations ​

Haversine Distance Formula ​

php
class GeographicHelper
{
    /**
     * Calculate distance between two points using Haversine formula
     * 
     * @param float $lat1 Latitude of first point
     * @param float $lon1 Longitude of first point
     * @param float $lat2 Latitude of second point
     * @param float $lon2 Longitude of second point
     * @return float Distance in kilometers
     */
    public static function calculateDistance(float $lat1, float $lon1, float $lat2, float $lon2): float
    {
        $earthRadius = 6371; // Earth's radius in kilometers
        
        $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;
    }
    
    /**
     * Find nearest administrative region to given coordinates
     */
    public static function findNearestProvince(float $lat, float $lon): ?Province
    {
        $provinces = Province::whereNotNull('latitude')
            ->whereNotNull('longitude')
            ->get();
            
        $nearest = null;
        $minDistance = PHP_FLOAT_MAX;
        
        foreach ($provinces as $province) {
            $distance = self::calculateDistance(
                $lat, $lon,
                $province->latitude, $province->longitude
            );
            
            if ($distance < $minDistance) {
                $minDistance = $distance;
                $nearest = $province;
            }
        }
        
        return $nearest;
    }
}

// Usage
$userLat = -6.200000;
$userLon = 106.816666;

$nearestProvince = GeographicHelper::findNearestProvince($userLat, $userLon);
echo "Nearest province: {$nearestProvince->name}";

// Calculate distance between two cities
$jakarta = Regency::search('jakarta pusat')->first();
$semarang = Regency::search('kota semarang')->first();

$distance = GeographicHelper::calculateDistance(
    $jakarta->latitude, $jakarta->longitude,
    $semarang->latitude, $semarang->longitude
);

echo "Distance from Jakarta to Semarang: " . round($distance, 2) . " km";

Mapping Integration ​

Leaflet.js Integration ​

js
// Initialize map
const map = L.map('map').setView([-2.5, 118], 5); // Center on Indonesia

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

// Load and display provinces
async function loadProvinces() {
    try {
        const response = await fetch('/nusa/provinces');
        const data = await response.json();
        
        data.data.forEach(province => {
            if (province.latitude && province.longitude) {
                // Add province marker
                const marker = L.marker([province.latitude, province.longitude])
                    .addTo(map)
                    .bindPopup(`
                        <strong>${province.name}</strong><br>
                        Code: ${province.code}<br>
                        Coordinates: ${province.latitude}, ${province.longitude}
                    `);
                
                // Add province boundary if available
                if (province.coordinates && province.coordinates.length > 0) {
                    const polygon = L.polygon(province.coordinates, {
                        color: 'blue',
                        fillColor: 'lightblue',
                        fillOpacity: 0.3
                    }).addTo(map);
                    
                    polygon.bindPopup(`<strong>${province.name}</strong>`);
                }
            }
        });
    } catch (error) {
        console.error('Error loading provinces:', error);
    }
}

// Load regencies when province is clicked
async function loadRegencies(provinceCode) {
    try {
        const response = await fetch(`/nusa/provinces/${provinceCode}/regencies`);
        const data = await response.json();
        
        data.data.forEach(regency => {
            if (regency.latitude && regency.longitude) {
                L.circleMarker([regency.latitude, regency.longitude], {
                    radius: 5,
                    color: 'red',
                    fillColor: 'orange',
                    fillOpacity: 0.8
                }).addTo(map)
                .bindPopup(`
                    <strong>${regency.name}</strong><br>
                    Province: ${regency.province_code}<br>
                    Code: ${regency.code}
                `);
            }
        });
    } catch (error) {
        console.error('Error loading regencies:', error);
    }
}

// Initialize
loadProvinces();

Google Maps Integration ​

js
class IndonesiaMap {
    constructor(mapElementId) {
        this.map = new google.maps.Map(document.getElementById(mapElementId), {
            zoom: 5,
            center: { lat: -2.5, lng: 118 }, // Center on Indonesia
            mapTypeId: 'terrain'
        });
        
        this.infoWindow = new google.maps.InfoWindow();
        this.markers = [];
        this.polygons = [];
    }
    
    async loadProvinces() {
        try {
            const response = await fetch('/nusa/provinces');
            const data = await response.json();
            
            data.data.forEach(province => {
                this.addProvinceMarker(province);
                this.addProvinceBoundary(province);
            });
        } catch (error) {
            console.error('Error loading provinces:', error);
        }
    }
    
    addProvinceMarker(province) {
        if (!province.latitude || !province.longitude) return;
        
        const marker = new google.maps.Marker({
            position: { lat: province.latitude, lng: province.longitude },
            map: this.map,
            title: province.name,
            icon: {
                url: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png'
            }
        });
        
        marker.addListener('click', () => {
            this.infoWindow.setContent(`
                <div>
                    <h3>${province.name}</h3>
                    <p>Code: ${province.code}</p>
                    <p>Coordinates: ${province.latitude}, ${province.longitude}</p>
                    <button onclick="loadRegencies('${province.code}')">
                        Load Regencies
                    </button>
                </div>
            `);
            this.infoWindow.open(this.map, marker);
        });
        
        this.markers.push(marker);
    }
    
    addProvinceBoundary(province) {
        if (!province.coordinates || province.coordinates.length === 0) return;
        
        const coordinates = province.coordinates.map(coord => ({
            lat: coord[0],
            lng: coord[1]
        }));
        
        const polygon = new google.maps.Polygon({
            paths: coordinates,
            strokeColor: '#0000FF',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#0000FF',
            fillOpacity: 0.1
        });
        
        polygon.setMap(this.map);
        this.polygons.push(polygon);
    }
    
    clearMarkers() {
        this.markers.forEach(marker => marker.setMap(null));
        this.markers = [];
    }
    
    clearPolygons() {
        this.polygons.forEach(polygon => polygon.setMap(null));
        this.polygons = [];
    }
}

// Initialize map
const indonesiaMap = new IndonesiaMap('map');
indonesiaMap.loadProvinces();

Spatial Queries ​

Point-in-Polygon Checks ​

php
class SpatialHelper
{
    /**
     * Check if a point is inside a polygon using ray casting algorithm
     */
    public static function pointInPolygon(float $lat, float $lon, array $polygon): bool
    {
        $x = $lon;
        $y = $lat;
        $inside = false;
        
        $j = count($polygon) - 1;
        for ($i = 0; $i < count($polygon); $i++) {
            $xi = $polygon[$i][1]; // longitude
            $yi = $polygon[$i][0]; // latitude
            $xj = $polygon[$j][1];
            $yj = $polygon[$j][0];
            
            if ((($yi > $y) !== ($yj > $y)) && 
                ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi)) {
                $inside = !$inside;
            }
            $j = $i;
        }
        
        return $inside;
    }
    
    /**
     * Find which province contains the given coordinates
     */
    public static function findProvinceByCoordinates(float $lat, float $lon): ?Province
    {
        $provinces = Province::whereNotNull('coordinates')->get();
        
        foreach ($provinces as $province) {
            if ($province->coordinates && 
                self::pointInPolygon($lat, $lon, $province->coordinates)) {
                return $province;
            }
        }
        
        return null;
    }
}

// Usage
$userLat = -6.200000;
$userLon = 106.816666;

$province = SpatialHelper::findProvinceByCoordinates($userLat, $userLon);
if ($province) {
    echo "You are in: {$province->name}";
} else {
    echo "Location not found in any province";
}

Bounding Box Queries ​

php
class BoundingBoxHelper
{
    /**
     * Calculate bounding box for a set of coordinates
     */
    public static function calculateBoundingBox(array $coordinates): array
    {
        $minLat = $maxLat = $coordinates[0][0];
        $minLon = $maxLon = $coordinates[0][1];
        
        foreach ($coordinates as $coord) {
            $lat = $coord[0];
            $lon = $coord[1];
            
            $minLat = min($minLat, $lat);
            $maxLat = max($maxLat, $lat);
            $minLon = min($minLon, $lon);
            $maxLon = max($maxLon, $lon);
        }
        
        return [
            'min_lat' => $minLat,
            'max_lat' => $maxLat,
            'min_lon' => $minLon,
            'max_lon' => $maxLon
        ];
    }
    
    /**
     * Find administrative regions within bounding box
     */
    public static function findRegionsInBoundingBox(
        float $minLat, float $maxLat, 
        float $minLon, float $maxLon
    ): array {
        $provinces = Province::whereBetween('latitude', [$minLat, $maxLat])
            ->whereBetween('longitude', [$minLon, $maxLon])
            ->get();
            
        $regencies = Regency::whereBetween('latitude', [$minLat, $maxLat])
            ->whereBetween('longitude', [$minLon, $maxLon])
            ->get();
            
        return [
            'provinces' => $provinces,
            'regencies' => $regencies
        ];
    }
}

// Usage - Find regions in Jakarta area
$jakartaBounds = BoundingBoxHelper::findRegionsInBoundingBox(
    -6.4, -6.0,  // Latitude range
    106.6, 107.0 // Longitude range
);

echo "Found {$jakartaBounds['provinces']->count()} provinces";
echo "Found {$jakartaBounds['regencies']->count()} regencies";

Location Services ​

Address Geocoding ​

php
class LocationService
{
    /**
     * Get coordinates for an address
     */
    public function geocodeAddress(array $addressComponents): ?array
    {
        $village = Village::find($addressComponents['village_code']);
        
        if ($village && $village->latitude && $village->longitude) {
            return [
                'latitude' => $village->latitude,
                'longitude' => $village->longitude,
                'accuracy' => 'village'
            ];
        }
        
        $district = District::find($addressComponents['district_code']);
        if ($district && $district->latitude && $district->longitude) {
            return [
                'latitude' => $district->latitude,
                'longitude' => $district->longitude,
                'accuracy' => 'district'
            ];
        }
        
        $regency = Regency::find($addressComponents['regency_code']);
        if ($regency && $regency->latitude && $regency->longitude) {
            return [
                'latitude' => $regency->latitude,
                'longitude' => $regency->longitude,
                'accuracy' => 'regency'
            ];
        }
        
        $province = Province::find($addressComponents['province_code']);
        if ($province && $province->latitude && $province->longitude) {
            return [
                'latitude' => $province->latitude,
                'longitude' => $province->longitude,
                'accuracy' => 'province'
            ];
        }
        
        return null;
    }
    
    /**
     * Reverse geocoding - find address from coordinates
     */
    public function reverseGeocode(float $lat, float $lon): ?array
    {
        // Find nearest village
        $nearestVillage = $this->findNearestVillage($lat, $lon);
        
        if ($nearestVillage) {
            return [
                'village' => $nearestVillage,
                'district' => $nearestVillage->district,
                'regency' => $nearestVillage->regency,
                'province' => $nearestVillage->province,
                'full_address' => $this->buildFullAddress($nearestVillage)
            ];
        }
        
        return null;
    }
    
    private function findNearestVillage(float $lat, float $lon): ?Village
    {
        // This is a simplified version - in production, you'd use spatial indexes
        $villages = Village::whereNotNull('latitude')
            ->whereNotNull('longitude')
            ->get();
            
        $nearest = null;
        $minDistance = PHP_FLOAT_MAX;
        
        foreach ($villages as $village) {
            $distance = GeographicHelper::calculateDistance(
                $lat, $lon,
                $village->latitude, $village->longitude
            );
            
            if ($distance < $minDistance) {
                $minDistance = $distance;
                $nearest = $village;
            }
        }
        
        return $nearest;
    }
    
    private function buildFullAddress(Village $village): string
    {
        return implode(', ', [
            $village->name,
            $village->district->name,
            $village->regency->name,
            $village->province->name,
            $village->postal_code
        ]);
    }
}

// Usage
$locationService = new LocationService();

// Geocode an address
$coordinates = $locationService->geocodeAddress([
    'village_code' => '3375011002',
    'district_code' => '337501',
    'regency_code' => '3375',
    'province_code' => '33'
]);

if ($coordinates) {
    echo "Address coordinates: {$coordinates['latitude']}, {$coordinates['longitude']}";
    echo "Accuracy: {$coordinates['accuracy']}";
}

// Reverse geocode coordinates
$address = $locationService->reverseGeocode(-6.8969497174987, 109.66208089654);
if ($address) {
    echo "Address: {$address['full_address']}";
}

These examples demonstrate how to work with geographic data in Laravel Nusa for mapping, location services, and spatial analysis. The geographic features enable building sophisticated location-based applications with accurate Indonesian administrative data.

Released under the MIT License.