| Server IP : 103.233.193.20 / Your IP : 216.73.216.169 Web Server : Apache/2 System : Linux host1.itclever.com 4.18.0-553.16.1.el8_10.x86_64 #1 SMP Thu Aug 8 17:47:08 UTC 2024 x86_64 User : oriscomadm ( 1120) PHP Version : 5.6.40 Disable Function : exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/oriscomadm/domains/oriscom.com/private_html/taxi_estimate/ |
Upload File : |
// ========== CONFIGURATION (การตั้งค่าระบบ) ==========
const CONFIG = {
// API Key สำหรับเข้าถึง Longdo Map Services
API_KEY: '4fc6a833488e90b3df56acc1388c198b',
// Session ID สำหรับแยกข้อมูลแต่ละการใช้งาน
SESSION_ID: 'session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9),
// สีเส้นทางบนแผนที่
ROUTE_COLORS: {
initial: {
primary: '#A0AEC0', // เทาอมฟ้า (เส้นแสดงครั้งแรก)
fallback: '#CBD5E0' // เทาอ่อน (สำรอง)
},
main: {
primary: '#1E90FF', // น้ำเงินสด (เส้นหลัก)
fallback: '#63B3ED' // น้ำเงินอ่อน (สำรอง)
},
fastest: {
primary: '#6A5ACD', // ม่วงเข้ม (เส้นเร็วที่สุด)
fallback: '#9370DB' // ม่วงอ่อน (สำรอง)
}
}
};
// ========== STATE (สถานะของระบบ) ==========
// เก็บค่าต่างๆ ที่เปลี่ยนแปลงตามการใช้งาน
const STATE = {
currentLang: localStorage.getItem('language') || 'th', // ภาษาที่ใช้งานปัจจุบัน
selectedOrigin: null, // ข้อมูลจุดเริ่มต้น
selectedDestination: null, // ข้อมูลจุดหมายปลายทาง
selectedRouteType: null, // ประเภทเส้นทางที่เลือก (main/fastest)
routeData: {}, // ข้อมูลเส้นทางที่คำนวณแล้ว
routeDataDebug: {}, // 🔥 ข้อมูล debug (เปรียบเทียบรถติด/ไม่มีรถติด)
currentMapMode: 'origin', // โหมดการคลิกบนแผนที่ (origin/destination)
map: null, // ออบเจ็กต์แผนที่ Longdo
markers: {
origin: null, // Marker จุดเริ่มต้น
destination: null // Marker จุดหมายปลายทาง
},
searchTimeout: null, // Timeout สำหรับการค้นหา (เพื่อหน่วงเวลา)
isAutoLocating: false // ป้องกันการเรียกหาตำแหน่ง GPS ซ้ำ
};
// ========== MARKER ICONS (ไอคอน Marker บนแผนที่) ==========
// สร้างไอคอน SVG สำหรับแสดงตำแหน่งบนแผนที่
const createMarkerIcon = (color, label) =>
`data:image/svg+xml,${encodeURIComponent(`
<svg width="40" height="50" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2C11.7 2 5 8.7 5 17c0 8.3 15 31 15 31s15-22.7 15-31c0-8.3-6.7-15-15-15z"
fill="${color}" stroke="#fff" stroke-width="2"/>
<circle cx="20" cy="17" r="11" fill="white"/>
<text x="50%" y="35%" text-anchor="middle" dominant-baseline="central"
font-size="14" font-weight="bold" fill="${color}">${label}</text>
</svg>
`)}`;
const MARKERS = {
green: createMarkerIcon('#22c55e', 'S'), // S = Start (จุดเริ่มต้น)
red: createMarkerIcon('#ef4444', 'E') // E = End (จุดหมายปลายทาง)
};
// ========================================================================================================
// ========== GPS PERMISSION & AUTO-DETECT FUNCTIONS (ฟังก์ชันตรวจสอบสิทธิ์ GPS และค้นหาตำแหน่ง) ==========
// ========================================================================================================
/**
* ตรวจสอบสิทธิ์การใช้ GPS และขอตำแหน่งปัจจุบันอัตโนมัติ
* - ใช้ Permission API เพื่อตรวจสอบสถานะการอนุญาต
* - แสดงข้อความแจ้งเตือนหากถูกปฏิเสธ
* @returns {Promise<boolean>} true ถ้าสามารถใช้ GPS ได้
*/
async function checkAndRequestGPSPermission() {
const t = TRANSLATIONS[STATE.currentLang];
// ตรวจสอบว่า browser รองรับ Geolocation API หรือไม่
if (!navigator.geolocation) {
console.error('❌ Geolocation is not supported by this browser');
return false;
}
// ตรวจสอบ permission status (ถ้า browser รองรับ)
if (navigator.permissions && navigator.permissions.query) {
try {
const permissionStatus = await navigator.permissions.query({
name: 'geolocation'
});
console.log('📍 GPS Permission Status:', permissionStatus.state);
if (permissionStatus.state === 'denied') {
// ถูกปฏิเสธแล้ว → แสดงข้อความแจ้งเตือน
showGPSAlert(t.gps_denied_message);
return false;
} else if (permissionStatus.state === 'prompt') {
// ยังไม่ได้ตอบ → แสดงข้อความขออนุญาต
showGPSAlert(t.gps_permission_message, true);
}
// ดักฟังเมื่อ permission เปลี่ยนแปลง
permissionStatus.addEventListener('change', function () {
console.log('🔄 GPS permission changed to:', this.state);
// ถ้าอนุญาตและยังไม่มีจุดเริ่มต้น → ค้นหาตำแหน่งอัตโนมัติ
if (this.state === 'granted' && !STATE.selectedOrigin) {
autoDetectCurrentLocation();
}
});
} catch (error) {
console.log('⚠️ Permission API not fully supported:', error);
}
} else {
// Browser ไม่รองรับ permission API → ขอตำแหน่งโดยตรง
console.log('⚠️ Permissions API not supported, requesting location directly');
showGPSAlert(t.gps_permission_message, true);
}
return true;
}
/**
* แสดงกล่องแจ้งเตือน GPS แบบกำหนดสี/ไอคอนได้
* - แสดงเป็นแถบลอยด้านบน
* - หายเองอัตโนมัติใน 5 วินาที
* @param {string} message - ข้อความที่จะแสดง
* @param {boolean} isInfo - true = สีน้ำเงิน (ข้อมูล), false = สีแดง (ข้อผิดพลาด)
*/
function showGPSAlert(message, isInfo = false) {
// สร้าง custom alert box
const alertDiv = document.createElement('div');
alertDiv.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: ${isInfo ? '#0d6efd' : '#dc3545'};
color: white;
padding: 15px 25px;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10000;
max-width: 90%;
text-align: center;
font-size: 14px;
line-height: 1.6;
animation: slideDown 0.3s ease-out;
`;
alertDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<i class="bi bi-${isInfo ? 'info-circle' : 'exclamation-triangle'}" style="font-size: 24px;"></i>
<div style="white-space: pre-line; text-align: left;">${message}</div>
</div>
`;
document.body.appendChild(alertDiv);
// Auto remove after 5 seconds
setTimeout(() => {
alertDiv.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => alertDiv.remove(), 300);
}, 5000);
// Add CSS animation (once)
if (!document.getElementById('gpsAlertStyle')) {
const style = document.createElement('style');
style.id = 'gpsAlertStyle';
style.textContent = `
@keyframes slideDown {
from { transform: translate(-50%, -100%); opacity: 0; }
to { transform: translate(-50%, 0); opacity: 1; }
}
@keyframes slideUp {
from { transform: translate(-50%, 0); opacity: 1; }
to { transform: translate(-50%, -100%); opacity: 0; }
}
`;
document.head.appendChild(style);
}
}
/**
* ตรวจหาตำแหน่งปัจจุบันอัตโนมัติ และเติมลงช่องต้นทาง
* - แสดงข้อความ "กำลังค้นหาตำแหน่ง" ในช่อง input
* - เมื่อพบตำแหน่งแล้ว จะทำ reverse geocode เพื่อหาชื่อสถานที่
* - ป้องกันการเรียกซ้ำด้วย isAutoLocating flag
*/
function autoDetectCurrentLocation() {
const t = TRANSLATIONS[STATE.currentLang];
// ป้องกันการเรียกซ้ำ
if (STATE.isAutoLocating) {
console.log('⚠️ Already detecting location...');
return;
}
STATE.isAutoLocating = true;
const originInput = document.getElementById('origin');
// แสดงสถานะกำลังค้นหา
originInput.value = t.auto_detecting_location;
originInput.style.color = '#0d6efd';
originInput.style.fontWeight = '300';
console.log('🔍 Auto-detecting current location...');
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('✓ Location detected:', position.coords);
// ตั้งค่าแผนที่ให้เด้งไปที่ตำแหน่งปัจจุบัน
if (STATE.map) {
STATE.map.location({
lon: position.coords.longitude,
lat: position.coords.latitude
}, true);
STATE.map.zoom(15);
}
// Reverse geocode เพื่อหาชื่อสถานที่
reverseGeocode(
position.coords.latitude,
position.coords.longitude,
'origin',
true // isCurrentLocation = true
);
STATE.isAutoLocating = false;
// แสดงข้อความสำเร็จ
showSuccessMessage(t.location_found);
},
(error) => {
console.error('❌ GPS Error:', error);
STATE.isAutoLocating = false;
// Reset input
originInput.value = '';
originInput.style.color = '';
originInput.style.fontWeight = '';
// แสดงข้อความ error ตามประเภท
let errorMessage = t.alert_gps_error;
switch (error.code) {
case error.PERMISSION_DENIED:
errorMessage = t.gps_denied_message;
break;
case error.POSITION_UNAVAILABLE:
errorMessage = "ไม่สามารถหาตำแหน่งได้ กรุณาตรวจสอบว่าเปิด GPS แล้ว";
break;
case error.TIMEOUT:
errorMessage = "หาตำแหน่งล่าช้าเกินไป กรุณาลองใหม่อีกครั้ง";
break;
}
showGPSAlert(errorMessage);
}, {
enableHighAccuracy: true, // ใช้ GPS ความแม่นยำสูง
timeout: 10000, // Timeout 10 วินาที
maximumAge: 0 // ไม่ใช้ cache
}
);
}
/**
* แสดงข้อความแจ้งเตือนสำเร็จแบบลอย
* - แสดงเป็นแถบสีเขียวด้านบน
* - หายเองอัตโนมัติใน 3 วินาที
* @param {string} message - ข้อความที่จะแสดง
*/
function showSuccessMessage(message) {
const successDiv = document.createElement('div');
successDiv.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #198754;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 10000;
font-size: 16px;
animation: slideDown 0.3s ease-out;
`;
successDiv.textContent = message;
document.body.appendChild(successDiv);
// Auto remove after 3 seconds
setTimeout(() => {
successDiv.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => successDiv.remove(), 300);
}, 3000);
}
// ========================================================================================================
// ========== LANGUAGE FUNCTIONS (ฟังก์ชันจัดการภาษา) ==========
// ========================================================================================================
/**
* ตั้งค่าภาษาและรีเฟรช UI/แผนที่
* @param {string} lang - รหัสภาษา ('th' หรือ 'en')
*/
function setLanguage(lang) {
STATE.currentLang = lang;
localStorage.setItem('language', lang);
updateLanguage();
updateLanguageSwitcher();
reloadMap();
}
/**
* อัปเดตตัวเลือกภาษาใน UI ให้แสดงว่าเลือกอันไหนอยู่
*/
function updateLanguageSwitcher() {
document.querySelectorAll('.lang-option').forEach(el => el.classList.remove('active'));
document.getElementById('lang' + STATE.currentLang.toUpperCase()).classList.add('active');
}
/**
* อัปเดตข้อความทั้งหมดในหน้าให้เป็นภาษาที่เลือก
* - อัปเดต title, placeholder
* - อัปเดตข้อความในปุ่มและ label
* - อัปเดตข้อมูลเส้นทาง (ถ้ามี)
*/
function updateLanguage() {
const t = TRANSLATIONS[STATE.currentLang];
// อัปเดต page title
document.getElementById('pageTitle').textContent = t.page_title;
document.documentElement.lang = STATE.currentLang;
// อัปเดตข้อความทั้งหมดที่มี data-i18n
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (t[key]) el.textContent = t[key];
});
// อัปเดต placeholder ทั้งหมด
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
if (t[key]) el.placeholder = t[key];
});
// อัปเดตข้อความตำแหน่งปัจจุบันใน input
updateLocationInputs();
// อัปเดตข้อมูลเส้นทาง (ถ้ามี)
if (STATE.routeData.main && STATE.routeData.fastest) {
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
displayDebugInfo(); // 🔥 อัปเดต debug info ด้วย
}
// อัปเดตภาษาของแผนที่
if (STATE.map) STATE.map.language(STATE.currentLang);
console.log(`🌐 Language switched to: ${STATE.currentLang}`);
}
/**
* อัปเดตชื่อจุดเริ่มต้น/ปลายทางตามภาษาใหม่
* - ถ้าเป็นตำแหน่งปัจจุบัน → แสดง "(ตำแหน่งปัจจุบัน)"
* - ถ้าไม่ใช่ → ทำ reverse geocode ใหม่
*/
function updateLocationInputs() {
const t = TRANSLATIONS[STATE.currentLang];
if (STATE.selectedOrigin) {
if (STATE.selectedOrigin.isCurrentLocation) {
document.getElementById('origin').value = t.current_location;
} else {
reverseGeocode(STATE.selectedOrigin.lat, STATE.selectedOrigin.lon, 'origin', false);
}
}
if (STATE.selectedDestination) {
if (STATE.selectedDestination.isCurrentLocation) {
document.getElementById('destination').value = t.current_location;
} else {
reverseGeocode(STATE.selectedDestination.lat, STATE.selectedDestination.lon, 'destination', false);
}
}
}
// ========================================================================================================
// ========== MAP FUNCTIONS (ฟังก์ชันจัดการแผนที่) ==========
// ========================================================================================================
/**
* สร้างแผนที่และตั้งค่าเริ่มต้น
* - สร้าง Longdo Map instance
* - ผูก event listener สำหรับการคลิกบนแผนที่
* - ตั้งต้นแผนที่ที่กรุงเทพฯ
*/
function initMap() {
STATE.map = new longdo.Map({
placeholder: document.getElementById('map'),
language: STATE.currentLang,
zoom: 12
});
// คลิกบนแผนที่เพื่อเลือกตำแหน่ง
STATE.map.Event.bind('click', function () {
const loc = STATE.map.location(longdo.LocationMode.Pointer);
handleMapClick(loc.lat, loc.lon);
});
// ตั้งต้นแผนที่ที่กรุงเทพฯ (จะถูก override ด้วย GPS ถ้ามี)
STATE.map.location({
lon: 100.5,
lat: 13.75
}, true);
// 🔥 ผูก Event Binding สำหรับคำนวณเส้นทาง (ใช้ pattern จาก test PHP)
initRouteEventBinding();
console.log('🗺️ Map initialized');
}
/**
* โหลดแผนที่ใหม่เมื่อเปลี่ยนภาษา
* - ล้างแผนที่เดิม
* - สร้างแผนที่ใหม่
* - คืนค่าตำแหน่งและเส้นทางเดิม (ถ้ามี)
*/
function reloadMap() {
if (!STATE.map) return;
// บันทึกตำแหน่งเดิม
const oldOrigin = STATE.selectedOrigin;
const oldDestination = STATE.selectedDestination;
// ล้างแผนที่
document.getElementById('map').innerHTML = '';
initMap();
// คืนค่าตำแหน่งเดิมหลังจากรีเฟรช
setTimeout(() => {
if (oldOrigin) {
updateLocation('origin', oldOrigin.name, oldOrigin.lat, oldOrigin.lon, oldOrigin.isCurrentLocation);
}
if (oldDestination) {
updateLocation('destination', oldDestination.name, oldDestination.lat, oldDestination.lon, oldDestination.isCurrentLocation);
}
if (STATE.routeData.main && STATE.routeData.fastest) {
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
}
}, 300);
console.log('🔄 Map reloaded for language change');
}
/**
* สลับโหมดเลือกตำแหน่ง (origin/destination)
* @param {string} mode - โหมด ('origin' หรือ 'destination')
*/
function setMapMode(mode) {
STATE.currentMapMode = mode;
console.log(`🎯 Map mode set to: ${mode}`);
}
/**
* จัดการเมื่อคลิกบนแผนที่
* - เรียก reverse geocode เพื่อหาชื่อสถานที่
* @param {number} lat - Latitude
* @param {number} lon - Longitude
*/
function handleMapClick(lat, lon) {
console.log(`📍 Map clicked: ${lat}, ${lon}`);
reverseGeocode(lat, lon, STATE.currentMapMode, false);
}
// ========================================================================================================
// ========== GEOCODING FUNCTIONS (ฟังก์ชัน Geocoding - แปลงพิกัดเป็นชื่อสถานที่) ==========
// ========================================================================================================
/**
* Reverse Geocode - แปลงพิกัด (lat, lon) เป็นชื่อสถานที่
* - เรียก API ของ Longdo Map
* - จัดรูปแบบชื่อสถานที่ตามภาษา
* - อัปเดต UI และ marker
* @param {number} lat - Latitude
* @param {number} lon - Longitude
* @param {string} type - ประเภท ('origin' หรือ 'destination')
* @param {boolean} isCurrentLocation - true ถ้าเป็นตำแหน่งปัจจุบัน
*/
async function reverseGeocode(lat, lon, type, isCurrentLocation = false) {
const t = TRANSLATIONS[STATE.currentLang];
try {
const response = await fetch(
`https://api.longdo.com/map/services/address?lon=${lon}&lat=${lat}&locale=${STATE.currentLang}&key=${CONFIG.API_KEY}`
);
const data = await response.json();
// ชื่อสถานที่ที่ผ่านการจัดรูปแบบตามภาษา
const name = formatLocationName(data) || `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
// อัปเดตตำแหน่ง โดยถ้าเป็นตำแหน่งปัจจุบันให้ใช้ t.current_location
updateLocation(
type,
isCurrentLocation ? t.current_location : name,
lat,
lon,
isCurrentLocation
);
console.log(`✓ Reverse geocode success: ${name}`);
} catch (error) {
console.error('❌ Reverse Geocode Error:', error);
// ถ้าดึงชื่อไม่ได้ → ใช้ lat/lon แทน
updateLocation(
type,
`${lat.toFixed(6)}, ${lon.toFixed(6)}`,
lat,
lon,
isCurrentLocation
);
}
}
/**
* จัดรูปแบบชื่อสถานที่จากข้อมูล API
* - รองรับทั้งภาษาไทยและอังกฤษ
* - เรียงข้อมูลจากละเอียด → กว้าง (เลขที่ → ถนน → ตำบล → อำเภอ → จังหวัด)
* @param {object} data - ข้อมูลจาก API
* @returns {string|null} ชื่อสถานที่ที่จัดรูปแบบแล้ว
*/
function formatLocationName(data) {
const parts = [];
const isTH = STATE.currentLang === "th";
// เลขที่ - ต้องเปลี่ยนคำว่า "เลขที่" → "No." เมื่อเป็นภาษาอังกฤษ
if (data.house_no) {
parts.push(isTH ? `เลขที่ ${data.house_no}` : `No. ${data.house_no}`);
}
// ฟิลด์อื่นๆ - เอาค่าตาม API ได้เลย (API จะส่งมาตามภาษาที่ระบุแล้ว)
[
'moo', 'place', 'building', 'condominium', 'village', 'village_official',
'sublane', 'alley', 'road', 'subdistrict', 'district', 'province'
].forEach(key => {
if (data[key]) parts.push(data[key]);
});
return parts.length > 0 ? parts.join(' ') : null;
}
// ========================================================================================================
// ========== UPDATE LOCATION (อัปเดตตำแหน่งและ Marker) ==========
// ========================================================================================================
/**
* อัปเดตตำแหน่ง (input + marker + state)
* - บันทึกข้อมูลลง STATE
* - อัปเดต input field
* - วาด marker บนแผนที่
* - แสดงปุ่มลบ (X)
* @param {string} type - ประเภท ('origin' หรือ 'destination')
* @param {string} name - ชื่อสถานที่
* @param {number} lat - Latitude
* @param {number} lon - Longitude
* @param {boolean} isCurrentLocation - true ถ้าเป็นตำแหน่งปัจจุบัน
*/
function updateLocation(type, name, lat, lon, isCurrentLocation = false) {
const locationData = {
name,
lat,
lon,
isCurrentLocation
};
const inputId = type;
const markerColor = type === 'origin' ? MARKERS.green : MARKERS.red;
// === เก็บข้อมูลลง STATE ===
if (type === 'origin') {
STATE.selectedOrigin = locationData;
} else {
STATE.selectedDestination = locationData;
}
// === อัปเดต Input UI ===
const input = document.getElementById(inputId);
input.value = name;
// ถ้าเป็นตำแหน่งปัจจุบัน → ใช้สีเขียวและตัวหนา
input.style.color = isCurrentLocation ? '#198754' : '';
input.style.fontWeight = isCurrentLocation ? '600' : '';
toggleClearButton(inputId, true);
// === อัปเดต Marker บนแผนที่ ===
// ลบ marker เก่า (ถ้ามี)
if (STATE.markers[type]) {
STATE.map.Overlays.remove(STATE.markers[type]);
}
// สร้าง marker ใหม่
STATE.markers[type] = new longdo.Marker({
lat,
lon
}, {
icon: {
url: markerColor,
offset: {
x: 20,
y: 50
}
},
title: name
});
STATE.map.Overlays.add(STATE.markers[type]);
// เลื่อนแผนที่ไปที่ตำแหน่งนี้
STATE.map.location({
lat,
lon
}, true);
console.log(`📍 Location updated: ${type} = ${name}`);
}
// ========================================================================================================
// ========== CLEAR & TOGGLE FUNCTIONS (ฟังก์ชันลบและสลับการแสดงผล) ==========
// ========================================================================================================
/**
* ลบข้อมูลตำแหน่ง (origin หรือ destination)
* - ล้าง input field
* - ลบ marker จากแผนที่
* - ลบข้อมูลจาก STATE
* - ซ่อนปุ่มลบ
* - ล้างเส้นทาง (ถ้าไม่มีจุดเริ่มต้นหรือปลายทาง)
* @param {string} type - ประเภท ('origin' หรือ 'destination')
*/
function clearInput(type) {
const input = document.getElementById(type);
// เคลียร์กล่องข้อความ
input.value = '';
input.style.color = '';
input.style.fontWeight = '';
// ล้างข้อมูลตำแหน่งใน STATE
if (type === 'origin') {
STATE.selectedOrigin = null;
} else {
STATE.selectedDestination = null;
}
// ซ่อนปุ่มลบ (X)
toggleClearButton(type, false);
// ลบ marker ออกจากแผนที่
if (STATE.markers[type]) {
STATE.map.Overlays.remove(STATE.markers[type]);
STATE.markers[type] = null;
}
// ไม่มีต้นทางหรือปลายทาง → ต้องล้างเส้นทางด้วย
if (!STATE.selectedOrigin || !STATE.selectedDestination) {
// ล้างเส้นจากแผนที่
if (STATE.map?.Route) STATE.map.Route.clear();
// ซ่อนส่วนแสดงเส้นทาง
const section = document.getElementById('routesSection');
if (section) section.style.display = 'none';
// ซ่อนปุ่มยืนยัน
const confirm = document.getElementById('confirmBtn');
if (confirm) confirm.style.display = 'none';
// 🔥 ซ่อน debug sections
const debug1 = document.getElementById('debug1');
const debug2 = document.getElementById('debug2');
if (debug1) debug1.classList.remove('show');
if (debug2) debug2.classList.remove('show');
// รีเซ็ตข้อมูลเส้นทางใน STATE
STATE.routeData = {};
STATE.routeDataDebug = {};
}
console.log(`🗑️ Cleared: ${type}`);
}
/**
* สลับการแสดงปุ่มลบ (X)
* @param {string} inputId - ID ของ input field
* @param {boolean} show - true = แสดง, false = ซ่อน
*/
function toggleClearButton(inputId, show) {
const btnId = 'clear' + inputId.charAt(0).toUpperCase() + inputId.slice(1);
const btn = document.getElementById(btnId);
if (btn) {
btn.classList.toggle('show', show);
}
}
// ========================================================================================================
// ========== SEARCH FUNCTIONS (ฟังก์ชันค้นหาสถานที่) ==========
// ========================================================================================================
/**
* ค้นหาสถานที่จาก keyword
* - เรียก Longdo Search API
* - แสดงผลลัพธ์ในรายการ suggestion
* @param {string} keyword - คำค้นหา
* @param {HTMLElement} element - element สำหรับแสดงผล
* @param {boolean} isOrigin - true = origin, false = destination
*/
async function searchPlace(keyword, element, isOrigin) {
if (!keyword || keyword.trim().length < 1) {
element.style.display = 'none';
return;
}
try {
const url = `https://search.longdo.com/mapsearch/json/search?keyword=${encodeURIComponent(keyword)}&limit=5&locale=${STATE.currentLang}&key=${CONFIG.API_KEY}`;
const response = await fetch(url);
const data = await response.json();
displaySuggestions(data?.data || [], element, isOrigin);
} catch (error) {
console.error('❌ Search Error:', error);
displaySuggestions([], element, isOrigin);
}
}
/**
* แสดงรายการ suggestion จากผลการค้นหา
* - แสดงตัวเลือก "ใช้ตำแหน่งปัจจุบัน" เสมอ
* - แสดงผลการค้นหา (ถ้ามี)
* @param {Array} places - รายการสถานที่
* @param {HTMLElement} element - element สำหรับแสดงผล
* @param {boolean} isOrigin - true = origin, false = destination
*/
function displaySuggestions(places, element, isOrigin) {
const t = TRANSLATIONS[STATE.currentLang];
element.innerHTML = '';
// === ตัวเลือก "ใช้ตำแหน่งปัจจุบัน" ===
const currentItem = document.createElement('div');
currentItem.className = 'suggestion-item current-location-item';
currentItem.innerHTML = `
<i class="bi bi-crosshair text-success me-2"></i>
<strong>${t.use_current_location}</strong>
`;
currentItem.onclick = () => {
useCurrentLocation(isOrigin);
element.style.display = 'none';
};
element.appendChild(currentItem);
// === ผลการค้นหา ===
if (places.length > 0) {
// เส้นแบ่ง + label
const separator = document.createElement('div');
separator.className = 'suggestion-separator';
separator.textContent = t.search_results;
element.appendChild(separator);
// เรียงตามชื่อ (A-Z หรือ ก-ฮ)
places.sort((a, b) =>
(a.name || "").localeCompare(b.name || "", "th")
);
places.forEach(place => {
const item = document.createElement('div');
item.className = 'suggestion-item';
item.innerHTML = `
<i class="bi bi-geo-alt text-secondary me-2"></i>
<div>
<div><strong>${place.name}</strong></div>
${place.address ? `<div class="text-muted small">${place.address}</div>` : ''}
</div>
`;
item.onclick = async () => {
await reverseGeocode(
place.lat,
place.lon,
isOrigin ? 'origin' : 'destination',
false
);
element.style.display = 'none';
};
element.appendChild(item);
});
}
element.style.display = 'block';
}
/**
* ใช้ตำแหน่งปัจจุบัน (GPS)
* - ขอตำแหน่งจาก browser
* - เลื่อนแผนที่ไปที่ตำแหน่งนั้น
* - ทำ reverse geocode
* @param {boolean} isOrigin - true = origin, false = destination
*/
function useCurrentLocation(isOrigin) {
const t = TRANSLATIONS[STATE.currentLang];
if (!navigator.geolocation) {
alert(t.alert_gps_error);
return;
}
const inputId = isOrigin ? 'origin' : 'destination';
const inputEl = document.getElementById(inputId);
inputEl.value = t.getting_location;
navigator.geolocation.getCurrentPosition(
pos => {
const lat = pos.coords.latitude;
const lon = pos.coords.longitude;
// เลื่อนแผนที่
STATE.map.location({
lon,
lat
}, true);
STATE.map.zoom(15);
// Reverse geocode
reverseGeocode(lat, lon, inputId, true);
},
error => {
console.error('❌ GPS Error:', error);
alert(t.alert_gps_error);
inputEl.value = '';
}, {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
}
// ========================================================================================================
// ========== ROUTE FUNCTIONS (ฟังก์ชันค้นหาและจัดการเส้นทาง) ==========
// ========================================================================================================
// ========================================================================================================
// ========== ROUTE FUNCTIONS (ฟังก์ชันค้นหาและจัดการเส้นทาง) ==========
// ========================================================================================================
/**
* 🔥 Route Calculation State Machine (ใช้ pattern จาก test PHP)
* - คำนวณเส้นทางทีละโหมด โดยใช้ global event binding
* - ใช้ state machine pattern เพื่อควบคุมลำดับการคำนวณ
*/
const ROUTE_CALC_STATE = {
calculating: false, // กำลังคำนวณอยู่หรือไม่
currentStep: 0, // ขั้นตอนปัจจุบัน
results: {}, // ผลลัพธ์ที่เก็บไว้
// ลำดับการคำนวณ (4 ขั้นตอน)
steps: [
{ name: 'AllDrive_Distance', routeType: 'AllDrive', mode: longdo.RouteMode.Cost, label: 'ทางด่วน (มีรถติด)' },
{ name: 'AllDrive_Traffic', routeType: 'AllDrive', mode: longdo.RouteMode.Traffic, label: 'ทางด่วน (ไม่มีรถติด)' },
{ name: 'Road_Distance', routeType: 'Road', mode: longdo.RouteMode.Cost, label: 'ทางถนน (มีรถติด)' },
{ name: 'Road_Traffic', routeType: 'Road', mode: longdo.RouteMode.Traffic, label: 'ทางถนน (ไม่มีรถติด)' }
]
};
/**
* Initialize Route Event Binding (เรียกครั้งเดียวตอน initMap)
* - ผูก event 'guideComplete' แบบ global
* - จะทำงานทุกครั้งที่ค้นหาเส้นทางเสร็จ
*/
function initRouteEventBinding() {
STATE.map.Event.bind('guideComplete', function () {
// ถ้าไม่ได้อยู่ในโหมดคำนวณ → ไม่ทำอะไร
if (!ROUTE_CALC_STATE.calculating) return;
const step = ROUTE_CALC_STATE.steps[ROUTE_CALC_STATE.currentStep];
try {
// ดึงค่าจาก SDK (ตรงกับเส้นบนแผนที่ 100%)
const distance = STATE.map.Route.distance(); // เมตร
const distanceText = STATE.map.Route.distance(true); // "X.X km"
const interval = STATE.map.Route.interval(); // วินาที
const intervalText = STATE.map.Route.interval(true); // "X ชม. Y นาที"
console.log(`✓ ${step.label}: ${distanceText}, ${intervalText}`);
// เก็บผลลัพธ์
ROUTE_CALC_STATE.results[step.name] = {
distance: distance,
interval: interval,
distanceText: distanceText,
intervalText: intervalText
};
// ไปขั้นตอนถัดไป
ROUTE_CALC_STATE.currentStep++;
if (ROUTE_CALC_STATE.currentStep < ROUTE_CALC_STATE.steps.length) {
// ยังมีขั้นตอนต่อไป → คำนวณต่อ
calculateNextStep();
} else {
// เสร็จทุกขั้นตอนแล้ว
finishRouteCalculation();
}
} catch (error) {
console.error('❌ Route calculation error:', error);
ROUTE_CALC_STATE.calculating = false;
document.getElementById('loadingSpinner').classList.remove('active');
alert('เกิดข้อผิดพลาดในการคำนวณเส้นทาง');
}
});
console.log('✓ Route event binding initialized');
}
/**
* เริ่มต้นการค้นหาเส้นทาง
* - ตรวจสอบข้อมูล
* - เริ่ม state machine
*/
function searchRoute() {
const t = TRANSLATIONS[STATE.currentLang];
if (!STATE.selectedOrigin || !STATE.selectedDestination) {
alert(t.alert_select_locations);
return;
}
// Reset state
ROUTE_CALC_STATE.calculating = true;
ROUTE_CALC_STATE.currentStep = 0;
ROUTE_CALC_STATE.results = {};
// UI loading
document.getElementById('loadingSpinner').classList.add('active');
document.getElementById('routesSection').style.display = 'none';
document.getElementById('confirmBtn').style.display = 'none';
console.log('🔍 Starting route calculation...');
// ตั้งค่า markers
STATE.map.Route.clear();
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}));
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}));
// เริ่มขั้นตอนแรก
calculateNextStep();
}
/**
* คำนวณขั้นตอนถัดไป
* - ตั้งค่า RouteType และ Mode
* - เรียก map.Route.search()
*/
function calculateNextStep() {
const step = ROUTE_CALC_STATE.steps[ROUTE_CALC_STATE.currentStep];
console.log(`🔍 Step ${ROUTE_CALC_STATE.currentStep + 1}/4: ${step.label}`);
// ตั้งค่า RouteType
if (step.routeType === 'AllDrive') {
STATE.map.Route.enableRoute(longdo.RouteType.Tollway, true);
//STATE.map.Route.enableRoute(longdo.RouteType.Road, false);
} else {
STATE.map.Route.enableRoute(longdo.RouteType.AllDrive, false);
STATE.map.Route.enableRoute(longdo.RouteType.Road, true);
}
// ตั้งค่า Mode และค้นหา
STATE.map.Route.mode(step.mode);
STATE.map.Route.search();
}
/**
* เมื่อคำนวณเสร็จทุกขั้นตอนแล้ว
* - จัดเตรียมข้อมูลให้ displayRoutes() และ displayDebugInfo()
* - แสดงผล UI
*/
function finishRouteCalculation() {
console.log('✓ All routes calculated!');
ROUTE_CALC_STATE.calculating = false;
// จัดเตรียมข้อมูลในรูปแบบเดิม (เพื่อให้ฟังก์ชันอื่นใช้งานได้)
STATE.routeData = {
main: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Distance.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Distance.interval,
distanceText: ROUTE_CALC_STATE.results.AllDrive_Distance.distanceText,
intervalText: ROUTE_CALC_STATE.results.AllDrive_Distance.intervalText
}]
},
fastest: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Distance.distance,
interval: ROUTE_CALC_STATE.results.Road_Distance.interval,
distanceText: ROUTE_CALC_STATE.results.Road_Distance.distanceText,
intervalText: ROUTE_CALC_STATE.results.Road_Distance.intervalText
}]
}
};
STATE.routeDataDebug = {
main: {
Distance: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Distance.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Distance.interval
}]
},
Traffic: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Traffic.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Traffic.interval
}]
}
},
fastest: {
Distance: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Distance.distance,
interval: ROUTE_CALC_STATE.results.Road_Distance.interval
}]
},
Traffic: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Traffic.distance,
interval: ROUTE_CALC_STATE.results.Road_Traffic.interval
}]
}
}
};
// แสดงผล
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
displayDebugInfo();
displayRouteOnMap();
sendDataToTestPage();
// UI
document.getElementById('loadingSpinner').classList.remove('active');
document.getElementById('routesSection').style.display = 'block';
document.getElementById('debug1').classList.add('show');
document.getElementById('debug2').classList.add('show');
document.getElementById('routesSection').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
console.log('✓ Routes displayed successfully');
}
/**
* ตั้งค่าสีเส้นทางบนแผนที่
* @param {string} routeType - ประเภทเส้นทาง ('initial', 'main', 'fastest')
*/
function setRouteColor(routeType = 'initial') {
const colors = CONFIG.ROUTE_COLORS[routeType] || CONFIG.ROUTE_COLORS.initial;
try {
// พยายามใช้ API ใหม่
STATE.map.call('Route.line', 'road', {
lineColor: colors.primary,
lineWidth: 3,
borderColor: '#000000',
borderWidth: 1
});
} catch (e) {
try {
// Fallback API รุ่นกลาง
STATE.map.Route.line('road', {
lineColor: colors.fallback,
lineWidth: 3
});
} catch (err2) {
// Fallback API รุ่นเก่า
STATE.map.Route.option({
lineColor: colors.fallback,
lineWidth: 3
});
}
}
}
/**
* แสดงเส้นทางบนแผนที่ (เส้นแรก - สีเทา)
* - ใช้เป็นเส้นตัวอย่างก่อนเลือก
*/
function displayRouteOnMap() {
STATE.map.Route.clear();
// เพิ่ม marker ต้นทาง
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}, {
icon: {
url: MARKERS.green,
offset: {
x: 20,
y: 50
}
}
}));
// เพิ่ม marker ปลายทาง
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}, {
icon: {
url: MARKERS.red,
offset: {
x: 20,
y: 50
}
}
}));
// ตั้งค่าสีเส้นทางเริ่มต้น (สีเทา)
setRouteColor();
// ตั้งค่าโหมดเส้นทาง
STATE.map.Route.mode(longdo.RouteMode.Cost);
// ค้นหาและวาดเส้นทาง
STATE.map.Route.search();
console.log('🗺️ Initial route displayed on map');
}
// ========================================================================================================
// ========== SELECT ROUTE (กดเลือก route-card) ==========
// ========================================================================================================
/**
* เลือกเส้นทาง (main หรือ fastest)
* - อัปเดต UI
* - วาดเส้นทางที่เลือกบนแผนที่
*/
function selectRoute(type) {
STATE.selectedRouteType = type;
// UI: ไฮไลต์การ์ดที่เลือก
document.querySelectorAll('.route-card').forEach(c => c.classList.remove('selected'));
document.getElementById(type === 'main' ? 'route1' : 'route2').classList.add('selected');
// แสดงปุ่มยืนยัน
document.getElementById('confirmBtn').style.display = 'block';
// วาดเส้นบนแผนที่
STATE.map.Route.clear();
if (type === 'main') {
// ทางด่วน (AllDrive)
STATE.map.Route.enableRoute(longdo.RouteType.Tollway, true);
// STATE.map.Route.enableRoute(longdo.RouteType.Road, false);
} else {
// ทางถนน (Road)
STATE.map.Route.enableRoute(longdo.RouteType.AllDrive, false);
STATE.map.Route.enableRoute(longdo.RouteType.Road, true);
}
STATE.map.Route.mode(longdo.RouteMode.Cost);
// เพิ่ม markers
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}, {
icon: { url: MARKERS.green, offset: { x: 20, y: 50 } }
}));
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}, {
icon: { url: MARKERS.red, offset: { x: 20, y: 50 } }
}));
// เปลี่ยนสีเส้น
setRouteColor(type);
// ไม่แสดงเส้นทาง KM
STATE.map.Route.label(false);
// ค้นหาเส้นทางใหม่
STATE.map.Route.search();
setTimeout(() => fitMapToRoute(), 800);
console.log(`✓ Route selected: ${type}`);
}
/**
* ปรับมุมมองแผนที่ให้พอดีกับเส้นทาง
* - คำนวณ bounding box จากจุดเริ่มต้นและปลายทาง
* - เพิ่ม padding เพื่อให้ดูสบายตา
*/
function fitMapToRoute() {
if (!STATE.selectedOrigin || !STATE.selectedDestination) return;
try {
const bounds = {
minLon: Math.min(STATE.selectedOrigin.lon, STATE.selectedDestination.lon),
minLat: Math.min(STATE.selectedOrigin.lat, STATE.selectedDestination.lat),
maxLon: Math.max(STATE.selectedOrigin.lon, STATE.selectedDestination.lon),
maxLat: Math.max(STATE.selectedOrigin.lat, STATE.selectedDestination.lat)
};
// เพิ่ม padding 15%
const padding = 0.15;
const lonPad = (bounds.maxLon - bounds.minLon) * padding || 0.01;
const latPad = (bounds.maxLat - bounds.minLat) * padding || 0.01;
bounds.minLon -= lonPad;
bounds.maxLon += lonPad;
bounds.minLat -= latPad;
bounds.maxLat += latPad;
STATE.map.bound(bounds, true);
console.log('🗺️ Map fitted to route bounds');
} catch (err) {
console.error('❌ Fit map error:', err);
// Fallback: ใช้จุดกึ่งกลาง
STATE.map.location({
lon: (STATE.selectedOrigin.lon + STATE.selectedDestination.lon) / 2,
lat: (STATE.selectedOrigin.lat + STATE.selectedDestination.lat) / 2
}, true);
STATE.map.zoom(10, true);
}
}
// ========================================================================================================
// ========== FARE CALCULATION (ฟังก์ชันคำนวณค่าโดยสาร) ==========
// ========================================================================================================
/**
* คำนวณค่าโดยสารแท็กซี่กรุงเทพฯ
* ตามกฎหมายปัจจุบัน:
* - ค่าเริ่มต้น: 35 บาท (1 กม. แรก)
* - 1-2 กม.: +6.5 บาท
* - 2-10 กม.: 6.5 บาท/กม.
* - 10-20 กม.: 7 บาท/กม.
* - 20-40 กม.: 8 บาท/กม.
* - 40-60 กม.: 8.5 บาท/กม.
* - 60-80 กม.: 9 บาท/กม.
* - 80+ กม.: 10.5 บาท/กม.
* - ค่ารถติด: 3 บาท/นาที
* @param {number} distanceKm - ระยะทาง (กิโลเมตร)
* @param {number} timeSeconds - เวลา (วินาที)
* @returns {number} ค่าโดยสาร (บาท)
*/
function calculateTaxiFare(distanceKm, timeSeconds) {
// ค่าเริ่มต้น (ขึ้นรถ) 35 บาท
let fare = 35;
// ---------------------------------------------------------
// 1) คิดค่าเดินทางตามระยะทาง
// ---------------------------------------------------------
if (distanceKm > 1) {
let remaining = distanceKm - 1;
// ตารางช่วงระยะทางและราคาต่อกิโลเมตร
const rateSteps = [{
limit: 1,
rate: 6.5
}, // 1-2 กม.
{
limit: 8,
rate: 6.5
}, // 2-10 กม.
{
limit: 10,
rate: 7
}, // 10-20 กม.
{
limit: 20,
rate: 8
}, // 20-40 กม.
{
limit: 20,
rate: 8.5
}, // 40-60 กม.
{
limit: 20,
rate: 9
}, // 60-80 กม.
{
limit: Infinity,
rate: 10.5
} // 80 กม. ขึ้นไป
];
// คำนวณทีละช่วง
rateSteps.forEach(({
limit,
rate
}) => {
if (remaining > 0) {
const dist = Math.min(remaining, limit);
fare += dist * rate;
remaining -= dist;
}
});
}
// ---------------------------------------------------------
// 2) ค่ารถติด (คิดเป็นนาที) = 3 บาท/นาที
// ---------------------------------------------------------
fare += (timeSeconds / 60) * 0;
// ปัดค่าขึ้นให้เป็นจำนวนเต็ม
return Math.round(fare);
}
// ========================================================================================================
// ========== DISPLAY ROUTES (แสดงข้อมูลเส้นทาง) ==========
// ========================================================================================================
/**
* แสดงข้อมูลเส้นทางทั้ง 2 แบบบนการ์ด
* - เส้นทางหลัก (main) - ทางด่วน
* - เส้นทางเร็วที่สุด (fastest) - ทางถนน
*/
function displayRoutes(routeMain, routeFastest) {
const t = TRANSLATIONS[STATE.currentLang];
const main = routeMain.data[0];
const fastest = routeFastest.data[0];
// --- แปลงค่าระยะทางให้เป็น Number ป้องกันค่าที่ไม่ใช่ตัวเลข ---
const mainKM = Number(main.distance) / 1000 || 0;
const fastestKM = Number(fastest.distance) / 1000 || 0;
// --- คำนวณ extra_time จากข้อมูล debug ---
const mainTraffic = STATE.routeDataDebug.main.Distance.data[0];
const mainNoTraffic = STATE.routeDataDebug.main.Traffic.data[0];
const mainExtraTime = Math.max(mainNoTraffic.interval - mainTraffic.interval, 0); // วินาที
const fastestTraffic = STATE.routeDataDebug.fastest.Distance.data[0];
const fastestNoTraffic = STATE.routeDataDebug.fastest.Traffic.data[0];
const fastestExtraTime = Math.max(fastestNoTraffic.interval - fastestTraffic.interval, 0); // วินาที
// --- คำนวณค่าโดยสารเพิ่มเติมจากรถติด (3 บาท/นาที) ---
const mainTrafficFare = Math.round((mainExtraTime / 60) * 3);
const fastestTrafficFare = Math.round((fastestExtraTime / 60) * 3);
// --- คำนวณราคารวม (ค่าโดยสารพื้นฐาน + ค่ารถติด) ---
const mainFare = calculateTaxiFare(mainKM, main.interval) + mainTrafficFare;
const fastestFare = calculateTaxiFare(fastestKM, fastest.interval) + fastestTrafficFare;
// --- คำนวณเวลารวม (เวลาเดิม + extra_time) ---
const mainTotalTime = main.interval + mainExtraTime;
const fastestTotalTime = fastest.interval + fastestExtraTime;
// ------------------------------
// การ์ดที่ 1 = Main (ทางด่วน)
// ------------------------------
document.getElementById('dist1').textContent = `${mainKM.toFixed(1)} ${t.km}`;
document.getElementById('time1').textContent = formatTime(mainTotalTime);
document.getElementById('cost1').textContent = `${mainFare} ${t.baht}`;
// ------------------------------
// การ์ดที่ 2 = Fastest (ทางถนน)
// ------------------------------
document.getElementById('dist2').textContent = `${fastestKM.toFixed(1)} ${t.km}`;
document.getElementById('time2').textContent = formatTime(fastestTotalTime);
document.getElementById('cost2').textContent = `${fastestFare} ${t.baht}`;
console.log(`📌 Main(AllDrive): เวลา=${formatTime(mainTotalTime)} ค่าโดยสาร=${mainFare}฿ (รถติด +${mainTrafficFare}฿)`);
console.log(`📌 Fastest(Road): เวลา=${formatTime(fastestTotalTime)} ค่าโดยสาร=${fastestFare}฿ (รถติด +${fastestTrafficFare}฿)`);
}
/**
* 🔥 แสดงข้อมูล Debug สำหรับเปรียบเทียบ "มีรถติด" vs "ไม่มีรถติด"
* ทำสำหรับทั้ง Route หลัก (main) และ Route ที่เร็วที่สุด (fastest)
*/
function displayDebugInfo() {
const t = TRANSLATIONS[STATE.currentLang]; // ดึงข้อความตามภาษาปัจจุบัน
// ---------------------------------------------------------
// 🟧 1) ดึงข้อมูลของเส้นทาง Main
// ---------------------------------------------------------
const mainTraffic = STATE.routeDataDebug.main.Distance.data[0]; // เส้นทาง main แบบมีรถติด
const mainNoTraffic = STATE.routeDataDebug.main.Traffic.data[0]; // เส้นทาง main แบบไม่มีรถติด
// คำนวณค่าโดยสาร (ใช้ระยะทางเป็นกม. + เวลา interval)
const mainFareTraffic = calculateTaxiFare(mainTraffic.distance / 1000, mainTraffic.interval);
//const mainFareNoTraffic = calculateTaxiFare(mainNoTraffic.distance / 1000, mainNoTraffic.interval);
// ผลกระทบจากรถติด = (ค่าที่มีรถติด) − (ค่าที่ไม่มีรถติด)
const mainExtraTime = Math.max(mainNoTraffic.interval - mainTraffic.interval, 0);
// const mainExtraFare = Math.max(mainFareNoTraffic - mainFareTraffic, 0);
// const mainExtraDist = Math.max((mainNoTraffic.distance - mainTraffic.distance) / 1000, 0);
// ---------------------------------------------------------
// 🟩 2) แสดงผล Debug สำหรับ Route Main
// ---------------------------------------------------------
// ไม่มีรถติด (Base Line)
document.getElementById('debug1_dist_traffic').textContent = `${(mainTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug1_time_traffic').textContent = formatTime(mainTraffic.interval);
document.getElementById('debug1_fare_traffic').textContent = `${mainFareTraffic} ${t.baht}`;
// มีรถติด
//document.getElementById('debug1_dist_notraffic').textContent = `${(mainNoTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug1_time_notraffic').textContent = formatTime(mainNoTraffic.interval);
//document.getElementById('debug1_fare_notraffic').textContent = `${mainFareNoTraffic} ${t.baht}`;
// ผลกระทบ
//document.getElementById('debug1_extra_distance').textContent = `${mainExtraDist.toFixed(1)} ${t.km}`;
document.getElementById('debug1_extra_time').textContent = formatTime(mainExtraTime);
//document.getElementById('debug1_extra_fare').textContent = `${mainExtraFare} ${t.baht}`;
// ---------------------------------------------------------
// 🟦 3) ดึงข้อมูลของเส้นทาง Fastest
// ---------------------------------------------------------
const fastestTraffic = STATE.routeDataDebug.fastest.Distance.data[0];
const fastestNoTraffic = STATE.routeDataDebug.fastest.Traffic.data[0];
const fastestFareTraffic = calculateTaxiFare(fastestTraffic.distance / 1000, fastestTraffic.interval);
//const fastestFareNoTraffic = calculateTaxiFare(fastestNoTraffic.distance / 1000, fastestNoTraffic.interval);
const fastestExtraTime = Math.max(fastestNoTraffic.interval - fastestTraffic.interval, 0);
//const fastestExtraFare = Math.max(fastestFareNoTraffic - fastestFareTraffic, 0);
//const fastestExtraDist = Math.max((fastestNoTraffic.distance - fastestTraffic.distance) / 1000, 0);
// ---------------------------------------------------------
// 🟦 4) แสดงผล Debug สำหรับ Route Fastest
// ---------------------------------------------------------
// ไม่มีรถติด
document.getElementById('debug2_dist_traffic').textContent = `${(fastestTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug2_time_traffic').textContent = formatTime(fastestTraffic.interval);
document.getElementById('debug2_fare_traffic').textContent = `${fastestFareTraffic} ${t.baht}`;
// มีรถติด
//document.getElementById('debug2_dist_notraffic').textContent = `${(fastestNoTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug2_time_notraffic').textContent = formatTime(fastestNoTraffic.interval);
//document.getElementById('debug2_fare_notraffic').textContent = `${fastestFareNoTraffic} ${t.baht}`;
// ผลกระทบ
//document.getElementById('debug2_extra_distance').textContent = `${fastestExtraDist.toFixed(1)} ${t.km}`;
document.getElementById('debug2_extra_time').textContent = formatTime(fastestExtraTime);
// document.getElementById('debug2_extra_fare').textContent = `${fastestExtraFare} ${t.baht}`;
console.log('🔍 Debug info displayed');
}
/**
* แปลงวินาที → ชั่วโมง/นาที
* @param {number} sec - จำนวนวินาที
* @returns {string} เวลาในรูปแบบ "X ชม. Y นาที" หรือ "Y นาที"
*/
function formatTime(sec) {
const t = TRANSLATIONS[STATE.currentLang];
const min = Math.round(sec / 60);
const h = Math.floor(min / 60);
const m = min % 60;
return h > 0 ? `${h} ${t.hr} ${m} ${t.min}` : `${m} ${t.min}`;
}
// ========================================================================================================
// ========== CONFIRM ROUTE (บันทึกเส้นทางที่เลือก) ==========
// ========================================================================================================
/**
* บันทึกข้อมูลเส้นทางเมื่อกดปุ่มยืนยัน
* - บันทึกลง LocalStorage
* - แสดงข้อความสำเร็จ
*/
async function confirmRoute() {
const t = TRANSLATIONS[STATE.currentLang];
if (!STATE.selectedRouteType) {
alert(t.alert_select_route);
return;
}
// เลือกเส้นทางที่ถูกคลิก
const routeInfo = STATE.routeData[
STATE.selectedRouteType === 'main' ? 'main' : 'fastest'
].data[0];
// คำนวณราคา
const fare = calculateTaxiFare(routeInfo.distance / 1000, routeInfo.interval);
// จัดเตรียมข้อมูลสำหรับบันทึก
const dataToSave = {
sessionId: CONFIG.SESSION_ID,
origin: {
name: STATE.selectedOrigin.name,
lat: STATE.selectedOrigin.lat,
lon: STATE.selectedOrigin.lon
},
destination: {
name: STATE.selectedDestination.name,
lat: STATE.selectedDestination.lat,
lon: STATE.selectedDestination.lon
},
routeType: STATE.selectedRouteType,
distance: (routeInfo.distance / 1000).toFixed(1),
time: formatTime(routeInfo.interval),
fare: fare,
timestamp: new Date().toISOString()
};
// บันทึกลง LocalStorage
localStorage.setItem('routeData_' + CONFIG.SESSION_ID, JSON.stringify(dataToSave));
console.log('💾 Data saved:', dataToSave);
// แสดงข้อความสำเร็จ
document.getElementById('confirmBtn').style.display = 'none';
document.getElementById('successMessage').classList.add('active');
document.getElementById('successMessage').scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
// ========================================================================================================
// ========== QR CODE FUNCTIONS (ฟังก์ชัน QR Code) ==========
// ========================================================================================================
/**
* สร้าง QR Code สำหรับผู้โดยสาร
* - ใช้ QRCode.js library
* - ลิงก์ไปหน้า passenger พร้อม session ID
*/
function generateQRCode() {
const qrDiv = document.getElementById('qrcode');
qrDiv.innerHTML = '';
// ดึง URL เฉพาะโฟลเดอร์หลัก เช่น https://example.com/driver
const baseUrl = window.location.href.split('/').slice(0, -1).join('/');
// สร้าง QR Code (ลิงก์ไปหน้า passenger)
new QRCode(qrDiv, {
text: baseUrl + '/passengerqp.php?session=' + CONFIG.SESSION_ID,
width: 180,
height: 180,
colorDark: '#0d1b3a',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
console.log('📱 QR Code generated');
}
/**
* เปิด/ปิด QR Code Box
* - สลับการแสดงผล
* - เลื่อนหน้าจอมาที่ QR เมื่อเปิด
*/
function toggleQR() {
const box = document.getElementById('qrcode-box');
const isHidden = box.style.display === 'none' || box.style.display === '';
box.style.display = isHidden ? 'flex' : 'none';
// ถ้าเปิดขึ้นมา ให้เลื่อนหน้าจอมาที่ QR อัตโนมัติ
if (isHidden) {
scrollToQR();
}
console.log(`📱 QR Box ${isHidden ? 'shown' : 'hidden'}`);
}
/**
* เลื่อนหน้าจอลงมาหา QR Code อัตโนมัติ
*/
function scrollToQR() {
const box = document.getElementById('qrcode-box');
box.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
// ========================================================================================================
// ========== TEST PAGE DATA SENDER (ส่งข้อมูลไปยัง test.html) ==========
// ========================================================================================================
/**
* 🔥 ส่งข้อมูล lat/long และเส้นทางไปยัง test.html
* - ส่งข้อมูลจุดเริ่มต้นและปลายทาง
* - ส่งข้อมูลเส้นทางทั้งสองแบบ (มีทางด่วน/ไม่มีทางด่วน)
* - ใช้ localStorage เพื่อถ่ายโอนข้อมูล
*/
function sendDataToTestPage() {
// ตรวจสอบว่ามีข้อมูลครบถ้วน
if (!STATE.selectedOrigin || !STATE.selectedDestination ||
!STATE.routeData.main || !STATE.routeData.fastest) {
console.log('⚠️ Cannot send to test page: Missing data');
return;
}
try {
// ดึงข้อมูลเส้นทาง
const mainRoute = STATE.routeData.main.data[0];
const fastestRoute = STATE.routeData.fastest.data[0];
// คำนวณค่าโดยสาร
const mainFare = calculateTaxiFare(mainRoute.distance / 1000, mainRoute.interval);
const fastestFare = calculateTaxiFare(fastestRoute.distance / 1000, fastestRoute.interval);
// จัดเตรียมข้อมูลส่ง
const testData = {
timestamp: new Date().toISOString(),
origin: {
name: STATE.selectedOrigin.name,
lat: STATE.selectedOrigin.lat.toFixed(6),
lon: STATE.selectedOrigin.lon.toFixed(6)
},
destination: {
name: STATE.selectedDestination.name,
lat: STATE.selectedDestination.lat.toFixed(6),
lon: STATE.selectedDestination.lon.toFixed(6)
},
mainRoute: {
type: 'มีทางด่วน (With Tollway)',
distance: (mainRoute.distance / 1000).toFixed(1),
time: formatTime(mainRoute.interval),
fare: mainFare
},
fastestRoute: {
type: 'ไม่มีทางด่วน (No Tollway)',
distance: (fastestRoute.distance / 1000).toFixed(1),
time: formatTime(fastestRoute.interval),
fare: fastestFare
}
};
// บันทึกลง localStorage พร้อม timestamp
const storageKey = 'testRouteData_' + Date.now();
localStorage.setItem(storageKey, JSON.stringify(testData));
console.log('📤 Data sent to test page:', testData);
console.log('🔑 Storage key:', storageKey);
// ลบข้อมูลเก่าที่เกิน 10 รายการ (เพื่อไม่ให้ localStorage เต็ม)
const allKeys = Object.keys(localStorage).filter(key => key.startsWith('testRouteData_'));
if (allKeys.length > 10) {
allKeys.sort();
allKeys.slice(0, allKeys.length - 10).forEach(key => localStorage.removeItem(key));
}
} catch (error) {
console.error('❌ Error sending data to test page:', error);
}
}
// ========================================================================================================
// ========== EVENT LISTENERS (ฟังก์ชันดักจับเหตุการณ์) ==========
// ========================================================================================================
/**
* ดักจับการพิมพ์ในช่องต้นทาง
* - แสดง/ซ่อนปุ่มลบ
* - ค้นหาสถานที่หลังจากหยุดพิมพ์ 300ms
*/
document.getElementById('origin').addEventListener('input', e => {
toggleClearButton('origin', e.target.value.length > 0);
clearTimeout(STATE.searchTimeout);
STATE.searchTimeout = setTimeout(() =>
searchPlace(e.target.value, document.getElementById('originSuggestions'), true), 300);
});
/**
* ดักจับการพิมพ์ในช่องปลายทาง
* - แสดง/ซ่อนปุ่มลบ
* - ค้นหาสถานที่หลังจากหยุดพิมพ์ 300ms
*/
document.getElementById('destination').addEventListener('input', e => {
toggleClearButton('destination', e.target.value.length > 0);
clearTimeout(STATE.searchTimeout);
STATE.searchTimeout = setTimeout(() =>
searchPlace(e.target.value, document.getElementById('destinationSuggestions'), false), 300);
});
/**
* คลิกข้างนอก suggestion list → ปิด list
*/
document.addEventListener('click', e => {
if (!e.target.closest('.position-relative')) {
document.getElementById('originSuggestions').style.display = 'none';
document.getElementById('destinationSuggestions').style.display = 'none';
}
});
/**
* เมื่อ focus ช่องต้นทาง → แสดง suggestion list
*/
document.getElementById('origin').addEventListener('focus', () => {
const box = document.getElementById('originSuggestions');
if (!box.innerHTML) displaySuggestions([], box, true);
box.style.display = 'block';
});
/**
* เมื่อ focus ช่องปลายทาง → แสดง suggestion list
*/
document.getElementById('destination').addEventListener('focus', () => {
const box = document.getElementById('destinationSuggestions');
if (!box.innerHTML) displaySuggestions([], box, false);
box.style.display = 'block';
});
// ========================================================================================================
// ========== INITIALIZE (เริ่มต้นระบบเมื่อโหลดหน้า) ==========
// ========================================================================================================
/**
* เริ่มต้นเมื่อ DOM พร้อม
* - อัปเดตภาษา
* - อัปเดตตัวเลือกภาษา
*/
document.addEventListener('DOMContentLoaded', () => {
updateLanguage();
updateLanguageSwitcher();
console.log('✓ DOM ready, language initialized');
});
/**
* เริ่มต้นเมื่อหน้าโหลดเสร็จสมบูรณ์
* - สร้างแผนที่
* - สร้าง QR Code
* - ตรวจสอบและขอสิทธิ์ GPS
* - ค้นหาตำแหน่งปัจจุบันอัตโนมัติ
*/
window.addEventListener('load', async () => {
console.log('🚀 System initializing...');
// 1. สร้างแผนที่
initMap();
// 2. สร้าง QR Code
generateQRCode();
// 3. รอให้แผนที่พร้อม
await new Promise(resolve => setTimeout(resolve, 500));
// 4. ขอสิทธิ์ GPS และค้นหาตำแหน่งอัตโนมัติ
const hasPermission = await checkAndRequestGPSPermission();
if (hasPermission) {
setTimeout(() => {
autoDetectCurrentLocation();
}, 300);
}
console.log('✓ System initialized');
});