Bina aplikasi navigasi ringkas dengan CFML dan sumber terbuka
Bina aplikasi navigasi sendiri dengan CFML, OpenStreetMap, Nominatim dan OSRM: carian tempat, laluan, arahan belok dalam Bahasa Melayu dan peta Leaflet.

Kandungan
Bina aplikasi navigasi ringkas dengan CFML dan sumber terbuka
Aplikasi navigasi komersial memudahkan kerja, tetapi ia juga bermakna setiap destinasi yang anda cari dihantar kepada syarikat lain, bersama masa, lokasi dan tabiat perjalanan anda. Untuk sesetengah organisasi, itu bukan pertukaran yang boleh diterima.
Alternatif sumber terbuka sudah cukup matang untuk kerja harian: peta, carian tempat dan laluan semuanya ada, dan tiada satu pun memerlukan akaun. Artikel ini membina aplikasi navigasi yang berfungsi dalam dua fail, menggunakan CFML sebagai perantara dan tiga perkhidmatan terbuka.

Hasil sebenar pada rig ujian: laluan Kuala Lumpur ke Melaka, 131.4 km dan 24 langkah.
Apa yang kita bina
Satu halaman dengan borang di kiri dan peta di kanan. Anda taip tempat asal dan destinasi, tekan butang, dan halaman memaparkan jarak, anggaran masa, arahan belok dalam Bahasa Melayu, dan garisan laluan pada peta.
| Komponen | Perkhidmatan | Lesen |
|---|---|---|
| Peta dan jubin | OpenStreetMap | ODbL |
| Carian tempat | Nominatim | ODbL |
| Laluan dan arahan | OSRM | BSD |
| Peta interaktif | Leaflet | BSD-2 |
Tiada kunci API, tiada pendaftaran, dan tiada bayaran untuk penggunaan kecil.
Kenapa CFML menjadi perantara
Pelayar boleh memanggil Nominatim dan OSRM terus, tetapi tiga perkara menjadi lebih sukar: dasar penggunaan memerlukan User-Agent yang jelas, cache perlu dikongsi antara pelawat, dan kunci atau had kadar tidak boleh diletakkan dalam JavaScript.
Dengan CFML di tengah, satu permintaan pelawat menjadi satu permintaan keluar, hasilnya disimpan dalam cache, dan pelayar hanya menerima JSON yang sudah bersih.
Pelayar -> CFML -> Nominatim (cari tempat)
-> OSRM (kira laluan)
<- cache 24 jam
Langkah 1: cari tempat
Nominatim menerima nama tempat dan memulangkan koordinat. Dua perkara wajib: User-Agent yang menyebut siapa anda, dan satu permintaan sesaat.
function cariTempat(string tempat) {
var kunci = "cari:" & lcase(trim(tempat));
if (structKeyExists(application.navCache, kunci) && application.navCache[kunci].expires > now()) {
return application.navCache[kunci].nilai;
}
cfhttp(url = "https://nominatim.openstreetmap.org/search",
method = "GET", result = "r", timeout = 20) {
cfhttpparam(type = "url", name = "q", value = tempat);
cfhttpparam(type = "url", name = "format", value = "json");
cfhttpparam(type = "url", name = "limit", value = "1");
cfhttpparam(type = "url", name = "countrycodes", value = "my");
cfhttpparam(type = "header", name = "User-Agent", value = "LuceeMalaysia-Nav/1.0 (contact@lucee.my)");
}
var hasil = [];
if (r.statusCode contains "200" && isJSON(r.fileContent)) {
var data = deserializeJSON(r.fileContent);
if (arrayLen(data)) {
hasil = { nama = data[1].display_name, lat = val(data[1].lat), lng = val(data[1].lon) };
}
}
application.navCache[kunci] = { nilai: hasil, expires: dateAdd("h", 24, now()) };
return hasil;
}
countrycodes=my mengehadkan carian kepada Malaysia, dan itu mengurangkan kesilapan seperti "Melaka" yang sepadan dengan tempat di negara lain.
Cache disimpan dalam skop aplikasi. Ia hilang apabila aplikasi dimulakan semula, dan itu memadai untuk alat kecil. Untuk trafik yang lebih besar, simpan cache dalam jadual MySQL supaya ia kekal.
Langkah 2: kira laluan
OSRM menerima dua koordinat dan memulangkan jarak, masa, bentuk laluan dan arahan setiap belokan.
function laluan(numeric lat1, numeric lng1, numeric lat2, numeric lng2) {
var kunci = "laluan:" & lat1 & "," & lng1 & ";" & lat2 & "," & lng2;
if (structKeyExists(application.navCache, kunci) && application.navCache[kunci].expires > now()) {
return application.navCache[kunci].nilai;
}
var alamat = "https://router.project-osrm.org/route/v1/driving/"
& lng1 & "," & lat1 & ";" & lng2 & "," & lat2
& "?overview=full&geometries=geojson&steps=true";
cfhttp(url = alamat, method = "GET", result = "r", timeout = 25) {
cfhttpparam(type = "header", name = "User-Agent", value = "LuceeMalaysia-Nav/1.0 (contact@lucee.my)");
}
var hasil = {};
if (r.statusCode contains "200" && isJSON(r.fileContent)) {
var data = deserializeJSON(r.fileContent);
if (data.code == "Ok" && arrayLen(data.routes)) {
var jalan = data.routes[1];
hasil = {
jarakKm = jalan.distance / 1000,
minit = jalan.duration / 60,
garis = jalan.geometry.coordinates,
langkah = []
};
for (var s in jalan.legs[1].steps) {
arrayAppend(hasil.langkah, {
arahan = arahanMelayu(s.maneuver.type, s.maneuver.get("modifier", ""), s.name),
jarakM = s.distance,
jalan = s.name
});
}
}
}
application.navCache[kunci] = { nilai: hasil, expires: dateAdd("h", 24, now()) };
return hasil;
}
Perhatikan urutan koordinat: OSRM menerima longitud dahulu, kemudian latitud. Menukar urutan itu tidak menghasilkan ralat, ia hanya menghasilkan laluan di tempat yang salah, dan itulah kesilapan yang paling biasa berlaku.
geometries=geojson memulangkan senarai koordinat yang boleh terus dihantar ke Leaflet tanpa penukaran.
Langkah 3: tukar arahan kepada Bahasa Melayu
OSRM memberi jenis dan pengubah dalam bahasa Inggeris: turn, slight right, roundabout, arrive. Fungsi kecil ini menukarnya kepada ayat yang boleh dibaca pemandu.
function arahanMelayu(string jenis, string pengubah, string namaJalan) {
var jalan = len(trim(namaJalan)) ? " ke " & namaJalan : "";
switch (jenis) {
case "depart": return "Mula" & jalan;
case "arrive": return "Anda telah tiba";
case "roundabout":
case "rotary": return "Masuk bulatan" & jalan;
case "merge": return "Masuk ke lebuh raya" & jalan;
case "fork":
if (pengubah contains "left") return "Ambil cabang kiri" & jalan;
if (pengubah contains "right") return "Ambil cabang kanan" & jalan;
return "Terus di cabang" & jalan;
default:
var arah = "Terus";
if (pengubah contains "sharp left") arah = "Belok tajam ke kiri";
else if (pengubah contains "sharp right") arah = "Belok tajam ke kanan";
else if (pengubah contains "slight left") arah = "Belok sedikit ke kiri";
else if (pengubah contains "slight right") arah = "Belok sedikit ke kanan";
else if (pengubah contains "left") arah = "Belok kiri";
else if (pengubah contains "right") arah = "Belok kanan";
return arah & jalan;
}
}
Susunan pemeriksaan penting. slight left mengandungi left, jadi ia mesti diperiksa dahulu. Kalau left diperiksa dahulu, setiap belokan sedikit akan menjadi belokan penuh, dan arahan itu menjadi salah tanpa sebarang ralat.
Langkah 4: paparkan peta
Leaflet memerlukan dua fail dan satu bekas. Koordinat daripada OSRM ialah [longitud, latitud], manakala Leaflet menerima [latitud, longitud], jadi tukar urutan semasa membina titik.
<div id="peta"></div>
<script>
var peta = L.map('peta').setView([3.139, 101.6869], 7);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors'
}).addTo(peta);
</script>
Kemudian lukis laluan.
<script>
var koordinat = <cfoutput>#serializeJSON(laluanJalan.garis)#</cfoutput>;
var titik = koordinat.map(function (c) { return [c[1], c[0]]; });
var garisan = L.polyline(titik, { color: '#0b6b5f', weight: 5 }).addTo(peta);
peta.fitBounds(garisan.getBounds(), { padding: [20, 20] });
</script>
Satu perangkap yang selalu berlaku: warna heksadesimal dalam blok <style> atau atribut style di dalam <cfoutput> mematahkan kompilasi Lucee dengan missing terminating [#]. Letakkan blok gaya di luar <cfoutput>, atau tulis ## bagi setiap #.
Hasil ujian sebenar
Saya jalankan aplikasi ini pada enjin Lucee 7.1.1.17 dengan laluan Kuala Lumpur ke Melaka:
- Jarak 131.4 km, anggaran 107 minit, 24 langkah
- Arahan yang terhasil termasuk "Belok kiri ke Jalan Raja Laut" dan "Terus ke Lebuhraya Kuala Lumpur"
- Panggilan pertama mengambil masa 2.1 saat, panggilan kedua dari cache 0.008 saat
- Peta memaparkan garisan laluan dari Kuala Lumpur ke Melaka dengan penanda mula dan tamat
Angka itu juga menunjukkan mengapa cache penting: satu carian dan satu laluan mengambil dua permintaan keluar, dan tanpa cache setiap muat semula halaman akan mengulanginya.
Had yang mesti dihormati
Perkhidmatan demo percuma bukan untuk produksi, dan artikel ini tidak menyembunyikan perkara itu.
- Nominatim awam: satu permintaan sesaat, User-Agent wajib, tiada muat turun pukal. Untuk trafik lebih, hos sendiri atau gunakan perkhidmatan berbayar.
- OSRM demo: untuk ujian dan demo sahaja. Ia boleh menyekat permintaan berulang, dan tiada jaminan ketersediaan.
- Jubin OpenStreetMap: dasar penggunaan melarang muat turun pukal dan penggunaan berat. Atribusi wajib dikekalkan.
- Data: OpenStreetMap dikemas kini oleh komuniti, jadi jalan baharu mungkin lambat muncul.
Untuk penggunaan sebenar oleh sebuah organisasi, langkah seterusnya ialah hos sendiri OSRM dengan ekstrak Malaysia dan hos sendiri jubin peta. Itu kerja berasingan, tetapi ia menghapuskan ketiga-tiga had di atas.
Soalan Lazim
Boleh saya gunakan ini untuk aplikasi mudah alih? Boleh. Halaman ini responsif, dan bahagian pelayan CFML boleh menjadi API JSON yang dipanggil aplikasi mudah alih.
Kenapa tidak panggil OSRM terus dari JavaScript? Kerana anda perlukan User-Agent, cache, dan kawalan had kadar. Selain itu, sesetengah rangkaian korporat menyekat panggilan terus ke domain luar.
Adakah ia memberi arahan suara?
Tidak. Artikel ini hanya memaparkan arahan teks. Suara memerlukan speechSynthesis dalam pelayar, dan itu boleh ditambah dalam beberapa baris.
Bagaimana jika tempat tidak dijumpai? Nominatim memulangkan senarai kosong. Dalam kod di atas, halaman memaparkan mesej "Tempat tidak dijumpai" dan tidak memanggil OSRM.
Boleh saya simpan sejarah carian pengguna? Boleh, tetapi jangan tanpa sebab. Aplikasi navigasi yang tidak menyimpan sejarah ialah kelebihan privasi yang paling mudah dikekalkan.
Sumber
- Panggilan API luar dari CFML: cfhttp-api-luar-cfml
- Mengendalikan JSON: json-serializejson-cfml
- Cache pertanyaan: cache-query-cachedwithin-lucee
- Ralat SSL ketika memanggil API luar: cfhttp-ralat-ssl-truststore