Smart City 2D Mapping #4: Pengembangan Fitur Peta Interaktif untuk Portal Informasi Kota - Perwira Learning Center

1. Latar Belakang

    Dalam pengembangan portal informasi kota, peta tidak cukup hanya menampilkan marker secara statis. Dibutuhkan pengelolaan data yang terstruktur, fitur pencarian lokasi, serta tampilan yang informatif agar pengguna lebih mudah memahami informasi yang ditampilkan. Oleh karena itu, pada tahap ini dilakukan pengembangan fitur peta interaktif yang mencakup pengelompokan data, navigasi berbasis parameter URL, serta penambahan elemen visual seperti header dan legend.

2. Alat dan Bahan

- Visual Studio Code (atau text editor lainnya)
- Laravel
- React (opsional)
- Library Leaflet

B. Perangkat Keras

- Laptop / PC

3. Pembahasan

3.1 Pengelompokan Data Lokasi

    Data lokasi dalam peta tidak ditulis secara manual satu per satu, tetapi diambil dari sumber data terstruktur. Disini, saya membuat data dummy lewat JavaScript yang saya taruh di sebuah folder tersendiri, yaitu:

SMARTCITY/
├── backend/
├── frontend/
│   ├── src/
│   │   ├── assets/ 
│   │   ├── components/ 
│   │   ├── data/ 
│   │   │   └── mockData.js (Tempat data dummy)
│   │   ├── assets/ 
│   │   └── pages/
│   ├── App.css
│   ├── App.jsx
│   ├── index.css
│   ├── main.jsx
│   └── package.json

    Didalamnya, terdapat data-data dummy dari suatu lokasi atau tempat seperti data nama wisata, data gambar, dan lain sebagainya seperti yang dapat dilihat di salah satu bentuk data dibawah:

export const wisataData = [
    {
        id: 1,
        nama: 'Owabong Water Park',
        slug: 'owabong-water-park',
        kategori: 'Wisata Air',
        gambar: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=600&q=80',
        lokasi: 'Kec. Bojongsari, Purbalingga',
        kecamatan: 'Bojongsari',
        deskripsi: 'Objek wisata air terpopuler di Purbalingga dengan berbagai wahana seru dan kolam renang modern yang menjadi favorit keluarga.',
        deskripsi_panjang: 'Owabong (Obyek Wisata Air Bojongsari) adalah taman air terbesar dan terpopuler di Kabupaten Purbalingga. Dengan luas area lebih dari 5 hektar, Owabong menawarkan berbagai wahana air yang seru dan aman untuk seluruh keluarga.',
        harga_dewasa: 50000,
        harga_anak: 35000,
        rating: 4.5,
        ulasan: 2847,
        jam_buka: '08:00 – 17:00 WIB',
        fasilitas: 'Parkir, Mushola, Food Court',
        kontak: '(0281) 892234',
        lat: -7.3642,
        lng: 109.3456,
    },

3.2 Pengembangan Fitur Pencarian Data Lokasi

    Fitur pencarian diimplementasikan menggunakan parameter URL. Ketika user memilih lokasi, maka halaman akan menerima koordinat dan nama lokasi tersebut.

Contoh URL : /peta?nama=Owabong&lat=-7.388&lng=109.363
if (instanceRef.current) {
    if (hasFly) instanceRef.current.flyTo([flyLat, flyLng], ZOOM_WISATA, { duration: 1.2 });
    return;
}

3.3 Penggunaan Layer pada Peta

    Untuk meningkatkan tampilan visual, digunakan tile layer dari CARTO (light mode) untuk tampilan map yang lebih bersih dan bagus:

L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
    attribution: '© CARTO',
    maxZoom: 19,
}).addTo(map);

Hasil:



3.4 Navigasi Header Peta

    Navigasi pada bagian atas peta berfungsi sebagai identitas halaman sekaligus memberikan akses cepat untuk kembali ke halaman sebelumnya (Portal Informasi). Tampilan ini dibuat menggunakan elemen HTML biasa yang diposisikan di atas peta atau biasa disebut sebagai overlay.

<div style={{
                position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)',
                zIndex: 1000, background: 'rgba(10,29,61,.85)', backdropFilter: 'blur(12px)',
                border: '1px solid rgba(255,255,255,.12)', borderRadius: 50,
                padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 14,
                color: 'white', boxShadow: '0 4px 20px rgba(0,0,0,.4)',
            }}>
                <Link to="/" style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'rgba(255,255,255,.7)', textDecoration: 'none', fontSize: 13, transition: 'color .2s' }}>
                    <i className="fas fa-arrow-left" /> Kembali
                </Link>
                <div style={{ width: 1, height: 20, background: 'rgba(255,255,255,.2)' }} />
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <i className="fas fa-map-marked-alt" style={{ color: '#5eead4' }} />
                    <span style={{ fontWeight: 700, fontSize: 14 }}>Peta Wisata Purbalingga</span>
                </div>
                {hasFly && (
                    <>
                        <div style={{ width: 1, height: 20, background: 'rgba(255,255,255,.2)' }} />
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: '#d4a853' }}>
                            <i className="fas fa-location-dot" /> {flyNama}
                        </div>
                    </>
                )}
            </div>
Hasil:

3.5 Tampilan Legend (Keterangan Peta)

    Legend digunakan untuk menjelaskan arti dari setiap simbol atau marker yang ada pada peta, sehingga pengguna tidak kebingungan dalam membaca informasi.

<div style={{
                position: 'absolute', bottom: 24, left: 16, zIndex: 1000,
                background: 'rgba(10,29,61,.85)', backdropFilter: 'blur(10px)',
                border: '1px solid rgba(255,255,255,.12)', borderRadius: 12,
                padding: '14px 18px', color: 'white', minWidth: 200,
                boxShadow: '0 4px 20px rgba(0,0,0,.4)',
            }}>
                <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: 2, textTransform: 'uppercase', color: '#5eead4', marginBottom: 10 }}>
                    Legend
                </div>
                {[
                    { color: '#0d9488', label: 'Destinasi Wisata' },
                    { color: '#d4a853', label: 'Destinasi Pilihan' },
                    { color: '#5d93c7', label: 'Pusat Kota' },
                ].map((l) => (
                    <div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 7 }}>
                        <div style={{ width: 12, height: 12, borderRadius: '50%', background: l.color, border: '2px solid white', flexShrink: 0 }} />
                        <span style={{ fontSize: 12, color: 'rgba(255,255,255,.8)' }}>{l.label}</span>
                    </div>
                ))}

                <div style={{ marginTop: 12, paddingTop: 10, borderTop: '1px solid rgba(255,255,255,.12)', fontSize: 11, color: 'rgba(255,255,255,.45)' }}>
                    {wisataData.length} destinasi wisata terdaftar
                </div>
            </div>
Hasil:

3.6 Fitur Pencarian Tempat

    Fitur ini dapat memudahkan pengguna untuk mencari letak dan informasi dari suatu tempat yang ingin dicari.

    <div style={{
                position: 'absolute', top: 80, right: 16, zIndex: 1000,
                background: 'rgba(10,29,61,.85)', backdropFilter: 'blur(10px)',
                border: '1px solid rgba(255,255,255,.12)', borderRadius: 12,
                color: 'white', width: 240,
                boxShadow: '0 4px 20px rgba(0,0,0,.4)', overflow: 'hidden',
                maxHeight: 'calc(100vh - 120px)', display: 'flex', flexDirection: 'column',
            }}>
                {/* Header */}
                <div style={{ padding: '12px 16px', borderBottom: '1px solid rgba(255,255,255,.1)', fontSize: 11, fontWeight: 700, letterSpacing: 2, textTransform: 'uppercase', color: '#5eead4', flexShrink: 0 }}>
                    Destinasi Wisata
                </div>

                {/* Search input */}
                <div style={{ padding: '10px 12px', borderBottom: '1px solid rgba(255,255,255,.1)', flexShrink: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: 'rgba(255,255,255,.08)', borderRadius: 20, padding: '7px 12px' }}>
                        <i className="fas fa-search" style={{ color: '#5eead4', fontSize: 11 }} />
                        <input
                            type="text"
                            placeholder="Cari destinasi..."
                            value={query}
                            onChange={e => setQuery(e.target.value)}
                            style={{
                                background: 'none', border: 'none', outline: 'none',
                                color: 'white', fontSize: 12, width: '100%',
                            }}
                        />
                        {query && (
                            <button onClick={() => setQuery('')} style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,.4)', cursor: 'pointer', padding: 0, fontSize: 12, lineHeight: 1 }}>
                                ✕
                            </button>
                        )}
                    </div>
                </div>

                {/* List hasil filter */}
                <div style={{ overflowY: 'auto', flex: 1 }}>
                    {filteredWisata.length === 0 ? (
                        <div style={{ padding: 20, textAlign: 'center', color: 'rgba(255,255,255,.4)', fontSize: 12 }}>
                            Tidak ditemukan
                        </div>
                    ) : filteredWisata.map((w) => {
                        const isActive = hasFly && w.nama === flyNama;
                        return (
                                <a key={w.id}
                                href={`/peta?nama=${encodeURIComponent(w.nama)}&lat=${w.lat}&lng=${w.lng}`}
                                style={{
                                    display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px',
                                    background: isActive ? 'rgba(13,148,136,.25)' : 'transparent',
                                    borderLeft: isActive ? '3px solid #0d9488' : '3px solid transparent',
                                    textDecoration: 'none', transition: 'background .15s',
                                    borderBottom: '1px solid rgba(255,255,255,.06)',
                                }}
                                onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'rgba(255,255,255,.07)'; }}
                                onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'transparent'; }}>
                                <div style={{ width: 8, height: 8, borderRadius: '50%', background: isActive ? '#d4a853' : '#0d9488', flexShrink: 0 }} />
                                <div style={{ flex: 1, minWidth: 0 }}>
                                    <div style={{ fontSize: 13, fontWeight: isActive ? 700 : 500, color: isActive ? '#d4a853' : 'white', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                                        {w.nama}
                                    </div>
                                    <div style={{ fontSize: 11, color: 'rgba(255,255,255,.45)' }}>{w.kecamatan}</div>
                                </div>
                            </a>
                        );
                    })}
                </div>
            </div>
Hasil:

Ketika pengguna mencari keyword

4. Daftar Pustaka

Leaflet. (n.d.). What are panes?. Diakses dari: https://leafletjs.com/examples/map-panes/

Post a Comment

0 Comments