Selasa, 05 Maret 2019

Terasi (bagian 2): Panen dan Pengolahan Data

Setelah membahas bagaimana ide dasar proyek Terasi muncul, tulisan ini akan membahas pengolahan data yang dibutuhkan.


Jakara Smart City API

Dengan googling sekilas, saya menemukan bahwa Jakarta Smart City memang membuka API untuk Transjakarta. Terdapat data untuk halte-halte dalam suatu koridor, koordinat halte, dan koordinat setiap bus pada saat itu juga. Saya tidak menemukan data lain yang lebih berguna, jadi diputuskanlah untuk menggunakan koordinat setiap bus pada suatu waktu.

API untuk koordinat setiap bus dapat diakses di http://202.51.116.138:8088/jsc_tj_api.php. API itu sekarang sudah tewas, tetapi sebelumnya dia akan memberikan JSON berisi koordinat setiap bus dalam format:
Saya hanya menampilkan tiga bus saja, tetapi sebenarnya ada 461 bus pada 26 Januari 2016. Sekali mengkonsumsi API itu, didapatkan data sekitar 60 kB. Untuk keperluan Terasi, saya akan mengkonsumsi API itu setiap 1 menit sekali, sehingga setiap hari didapatkan 24 * 60 berkas. Jika 1 berkas berukuran 60 kB, berarti setiap harinya saya memanen data sebesar 86,4 MB. Dalam 1 bulan, datanya akan sebesar 2,6 GB. Angka ini terlihat besar, tapi sebenarnya data JSON yang diberikan ini dapat dikompres menjadi data tabular (misal: csv) yang lebih ringkas. Karena itu saya tidak mengkhawatirkan ukuran datanya.

Dengan "snapshot" koordinat bus setiap menitnya, kita dapat mensimulasikan pergerakan bus secara cukup akurat.


Pemanen Data

Selanjutnya saya menuliskan program sederhana dengan Node.js untuk memanggil API tersebut setiap menitnya, lalu menyimpan JSON yang diberikan. Setiap akhir hari, data yang terkumpul dikompres menjadi .tar.gz. Kalau ditanya kenapa pakai Node.js, karena kebetulan saya sedang belajar itu.

Karena saya tidak mau menyalakan komputer sepanjang waktu, saya menyewa server melalui DigitalOcean. Dengan biaya 5 dollar AS setiap bulannya, saya mendapat server paling murah dengan spesifikasi lebih lemah dari laptop tua saya. Servernya memiliki RAM 512 MB dan hard disk 25 GB. Meskipun lemah, cukup untuk keperluan pemanenan data ini.

Untuk memastikan bahwa data terus menerus dipanen, saya membuat sistem logging sederhana. Kalau untuk alasan apapun API memberikan error atau data yang rusak, maka error akan dicatat dan dilaporkan ke channel gratisan Slack (https://slack.com/). Saya cukup memasang aplikasi Slack di hp saya, lalu setiap notifikasi error dapat segera diketahui. Untuk menjaga kesehatannya, setiap hari akan ada laporan berapa ukuran data yang dipanen hari itu juga. Jadi kalau ukuran datanya tidak normal, error bisa disampaikan juga.

Sampai saat ini, saya rasa pemanen datanya sudah bekerja. Selanjutnya perlu dipastikan bahwa data yang diberikan API ini masuk akal. Kalau misalnya posisi busnya sering tidak diperbaharui, atau koordinatnya bisa kacau, dijamin hasil akhirnya amburadul. Oleh karena itu saya terpikir untuk menulis program sederhana untuk memvisualisasikan data perjalanan bus tersebut seperti sebuah video.


Visualisasi

Setelah pulang kerja keesokan harinya, saya memeriksa data yang telah dipanen. Terlihat baik, karena setiap menitnya ada data dan tidak ada kerusakan data.

Lalu saya menghabiskan 2 jam berurusan dengan Java awt sambil mengingat kembali ilmu kuliah semester 1. Akhirnya berhasillah penulisan program untuk menggambarkan pergerakan bus berdasarkan API.

Hasilnya memuaskan. Berikut gambaran bus dari 00:00 sampai 23:59. Terlihat bahwa dari 00:00 sampai 03:00 pergerakan bus hanya di koridor tertentu. Masuk akal, berhubung tidak semua bus beroperasi tengah malam. Sesudah itu bus mulai ramai (jam masuk kerja), terutama sejak 05:00. Selanjutnya saya menyaring hasilnya supaya hanya menunjukkan bus dengan label koridor "1".

Video beresolusi tinggi bisa dilihat di https://youtu.be/lzyAfKbAiwc



Ketika diteliti, terdapat kejanggalan. Beberapa bus mengaku sedang berada di suatu koridor, tapi ternyata sedang berkeliaran di koridor lainnya. Berikut cuplikan bus-bus yang mengaku di koridor 1 (merah), tetapi berjalan di koridor 3 (biru) atau 5 (hijau). Hal ini juga terjadi di koridor-koridor lainnya.

Saya rasa ada semacam GPS yang dipasang di setiap bus, yang secara periodik mengirimkan data lokasi ke server, berikut informasi ID bus dan koridor yang sedang dilalui. Tapi karena bus-bus mungkin dialihkan ke koridor lain pada jam tertentu, dan barangkali informasi koridornya perlu diganti secara manual dan terlupakan, didapatkanlah data yang tidak bersih ini.

Berkat program visualisasi ini, dapat disimpulkan:
  1. TJ beroperasi 24 jam, meskipun tidak semua koridor beroperasi pada jam malam.
  2. Beberapa bus akan "bertengger" di suatu lokasi (misal: selatan Jakarta) saat sedang tidak beroperasi. Mungkin itu semacam bengkel?
  3. Bus bisa saja berpindah koridor. Pada pagi hari di koridor A, lalu sore hari di koridor B.
  4. Bus mungkin saja bergerak di jalan yang bukan trayek transjakarta. Mungkin sedang berpindah koridor.
  5. Informasi koridor tidak bisa dipercaya, artinya saya harus menebak di mana suatu bus berada berdasarkan posisinya.


Ekstraksi Data

Setelah data dipanen dan dimengerti, kini saatnya mengolah data tersebut. Untuk memenuhi kebutuhan perhitungan, saya perlu mengubah data koordinat bus setiap menit menjadi 2 macam informasi:
  1. Pada pukul T, bus dengan suatu ID tiba di halte A, dan diketahui halte terakhir yang akan dicapai adalah B. Bus ini sedang melayani koridor-koridor K1, K2, K3, .... (suatu halte bisa digunakan oleh beberapa koridor).
  2. Pada pukul T1, bus dengan suatu ID berpindah dari halte A, dan tiba di halte B pada pukul T2. Dijamin A dan B adalah halte yang bertetangga. 
Untuk menghitung nilai-nilai tersebut, cara yang terpikirkan adalah melakukan segmentasi terhadap perjalanan bus. Misalnya kita sedang berurusan pada bus di koridor 1 (Kota - Blok M). Cari tahu kapan waktu bus berangkat dari Kota dan tiba di blok M, atau sebaliknya. Perhitungan ketibaan bus dapat dihitung dengan fokus pada segmen ini.

Misalkan diketahui berangkatnya pukul 06:00 dan tiba pukul 07:50 (ini hanya angka asal-asalan). Dengan informasi posisi setiap halte dalam koridor tersebut dan rekam jejak posisi bus setiap menitnya, kita bisa menggunakan interpolasi linier untuk memperkirakan kapan bus tiba di setiap halte-halte dalam segmen tersebut.

Idenya adalah "meluruskan" polyline halte-halte dan rekam jejak bus ke dalam garis lurus, lalu dari sana bisa diperkirakan jam, menit, dan detiknya suatu bus sampai di setiap halte. Tulisan dicetak biru pada gambar di bawah adalah nilai yang hendak kita dapatkan. "Pelurusan" polyline dilakukan dengan memproyeksikan polyline rekam jejak bus ke polyline trayek bus.



Namun cara ini hanya bekerja apabila kita dapat menyelesaikan 2 permasalahan:
  1. Tahu kapan suatu bus memulai perjalanan (awal segmen) dan mengakhiri perjalanan (akhir segmen).
  2. Mencari tahu di koridor mana bus ini sedang berjalan, penting untuk mengetahui dasar polyline mana yang perlu digunakan untuk sasaran proyeksinya.
Mari kita bahas satu per satu.

Proses Segmentasi

Pencarian awal dan akhir segmen cukup sederhana. Setelah saya menonton pergerakan bus-bus dan mencoba menulis program awal, ditemukan tiga ciri-ciri suatu bus mencapai awal/akhir segmen:
  1. Bus menghabiskan waktu yang lama (>3 menit) di halte bus yang merupakan ujung suatu koridor.
  2. Bus melakukan U-turn, artinya dia mendekati halte B, lalu ke halte A, lalu ke halte B lagi. Ada pengecualian untuk koridor 12 yang mana trayeknya memiliki suatu U-turn.
  3. Bus mencapai suatu halte ujung koridor, lalu keluar dari trayek bus dan pergi jauh entah ke mana (mungkin bengkel, atau bergerak melayani koridor lainnya).

Kalau kita tahu ujung-ujung segmen (tanpa peduli itu awal atau akhir), cukup dipotong-potong saja. Akhir suatu segmen pastilah awal segmen berikutnya.

Proses Pencocokan Halte (polyline matching?)

Sejauh ini kita menyelesaikan masalah pertama. Masalah kedua ini lebih sulit, karena kita harus mencocokkan polyline dengan polyline koridor. Parahnya saya tidak punya data polyline suatu koridor. Yang ada hanya koordinat setiap halte saja dan itu tidak cukup akurat untuk memodelkan rute TJ.

Oleh karena itu saya perlu kembali menonton visualisasi yang dibuat, lalu secara manual mengambil sampel bus yang berada di koridor tertentu. Kemudian catat kapan busnya berangkat dari halte pertama, dan kapan busnya sampai halte terakhir. Karena jalan yang dilalui tidak selalu simetris antara "pergi" dan "pulang" (misal: Kota - Blok M dan Blok M - Kota), cara ini perlu diulang untuk kepergian dan kepulangan. Setelah dicatat, tinggal jalankan program untuk mengambil semua titik koordinat yang dilalui bus, lalu lakukan smoothing. Algoritma smoothingnya sesederhana membuang titik yang sangat dekat dengan titik sebelumnya, dan membuang tiga titik yang co-linear. Cukup melelahkan untuk melakukannya bagi seluruh koridor dan kedua arahnya. Untungnya ini hanya perlu dilakukan satu kali saja.
Smoothing polyline untuk koridor 1

Manfaat dari smoothing polyline ini:
  • Mengurangi "segmen garis" yang memiliki panjang 0. Hal ini terjadi ketika bus tidak bergerak selama beberapa menit. Garisnya kini cacat, dan terlihat seperti titik. Kasus cacat bisa menimbulkan kasus khusus (special case) untuk algoritma interpolasi, berhubung akan banyak hitung-hitungan geometrinya. Saya sudah bisa mencium bau-bau "division by zero" saat menghitung proyeksi titik ke segmen garis dengan panjang 0.
  • Banyaknya titik dalam polyline menjadi berkurang, dari ratusan kini hanya belasan saja. Mengerjakan soal dengan batasan N kecil pastinya lebih mudah daripada N besar.

Selanjutnya adalah bagaimana mencocokkan polyline suatu segmen dengan koridor. Cara yang saya gunakan adalah brute force, mencoba semua koridor dan mencari yang paling cocok. Kecocokan didefinisikan dengan jarak terjauh dari jarak terdekat antara titik segmen dengan titik polyline koridor. Semakin kecil, artinya semakin cocok. Perlu diketahui bahwa suatu koridor bisa saja merupakan subhimpunan dari koridor lainnya, sehingga suatu segmen bisa saja cocok dengan lebih dari satu koridor.

Apabila ketibaan ditemukan, kita cukup mengurutkan ketibaan di dalam suatu segmen, dan rentang waktu antar 2 ketibaan akan menjadi data waktu tempuh. Jadi berhasillah ekstraksi data ketibaan dan waktu tempuh dari data log posisi bus.


Implementasi

Setelah idenya jelas, saya mulai menuliskan program untuk mengekstraksi ketibaan. Banyak kasus-kasus yang perlu diperhatikan berhubung datanya tidak selalu bersih. Misalnya bus bisa saja tidak memberikan data selama beberapa menit, atau bus tiba-tiba kehilangan koneksi. Saya menghabiskan beberapa hari untuk menuliskan kodenya dalam Node.js, berhubung pengolahan JSON mudah dilakukan dengan Javascript. Sambil coding, saya menyadari bahwa lagu Trance membantu fokus selama coding sampai lupa waktu. Rasanya saya memasuki sebuah kondisi "coding trance". Ini contohnya: https://www.youtube.com/watch?v=fY3D2VpPRkE

Akhirnya selesailah program ekstraksi datanya dan dapat dihasilkan:

Saya hanya menampilkan 3 datum saja. Sebenarnya terdapat 48372 ketibaan pada hari itu. Secara mengejutkan banyak juga ketibaannya. Kalau kalian tertarik, datanya bisa diunduh:


Langkah Selanjutnya

Kini data telah didapatkan. Saya tinggal melakukan ekstraksi data untuk beberapa hari untuk dijadikan bahan percobaan dalam perhitungan nilai statistiknya. Apabila Hitung-hitungannya sudah masuk akal, berarti sudah waktunya diimplementasikan dalam bentuk back end API.

Dari perjalanan kali ini, dipelajari bahwa pembuatan visualisasi sangat penting. Program itu membantu saya men-debug, terutama untuk mencari tahu apa yang terjadi saat ada anomali program ekstraksi data.

Berhubung tulisan ini sudah panjang, langkah ini akan saya bahas pada kesempatan berikutnya.

Tidak ada komentar :

Posting Komentar