Spaces:
Running
Running
| <script type="text/javascript"> | |
| var gk_isXlsx = false; | |
| var gk_xlsxFileLookup = {}; | |
| var gk_fileData = {}; | |
| function filledCell(cell) { | |
| return cell !== '' && cell != null; | |
| } | |
| function loadFileData(filename) { | |
| if (gk_isXlsx && gk_xlsxFileLookup[filename]) { | |
| try { | |
| var workbook = XLSX.read(gk_fileData[filename], { type: 'base64' }); | |
| var firstSheetName = workbook.SheetNames[0]; | |
| var worksheet = workbook.Sheets[firstSheetName]; | |
| // Convert sheet to JSON to filter blank rows | |
| var jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false, defval: '' }); | |
| // Filter out blank rows (rows where all cells are empty, null, or undefined) | |
| var filteredData = jsonData.filter(row => row.some(filledCell)); | |
| // Heuristic to find the header row by ignoring rows with fewer filled cells than the next row | |
| var headerRowIndex = filteredData.findIndex((row, index) => | |
| row.filter(filledCell).length >= filteredData[index + 1]?.filter(filledCell).length | |
| ); | |
| // Fallback | |
| if (headerRowIndex === -1 || headerRowIndex > 25) { | |
| headerRowIndex = 0; | |
| } | |
| // Convert filtered JSON back to CSV | |
| var csv = XLSX.utils.aoa_to_sheet(filteredData.slice(headerRowIndex)); // Create a new sheet from filtered array of arrays | |
| csv = XLSX.utils.sheet_to_csv(csv, { header: 1 }); | |
| return csv; | |
| } catch (e) { | |
| console.error(e); | |
| return ""; | |
| } | |
| } | |
| return gk_fileData[filename] || ""; | |
| } | |
| </script><!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Dhaka Metro Rail Fare Checker</title> | |
| <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Roboto', sans-serif; | |
| background-color: #f4f4f4; | |
| color: #333; | |
| overflow-x: hidden; | |
| } | |
| header { | |
| background: linear-gradient(135deg, #003366, #005588); | |
| color: white; | |
| padding: 2rem; | |
| text-align: center; | |
| box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); | |
| animation: slideInDown 1s ease-out; | |
| } | |
| header h1 { | |
| font-size: 2.5rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| aside#sidebar { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 280px; | |
| height: 100%; | |
| background: #D8C4B6; | |
| padding: 20px; | |
| overflow-y: auto; | |
| box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); | |
| transform: translateX(-100%); | |
| animation: slideInLeft 0.8s ease-out forwards; | |
| } | |
| aside h2 { | |
| color: #003366; | |
| margin-bottom: 15px; | |
| } | |
| aside p { | |
| font-size: 0.95rem; | |
| line-height: 1.6; | |
| } | |
| aside a { | |
| color: #009688; | |
| text-decoration: none; | |
| } | |
| main { | |
| margin-left: 300px; | |
| padding: 30px; | |
| animation: fadeIn 1s ease-in; | |
| } | |
| #fare-checker, #map-section { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); | |
| margin-bottom: 30px; | |
| transition: transform 0.3s ease; | |
| } | |
| #fare-checker:hover, #map-section:hover { | |
| transform: translateY(-5px); | |
| } | |
| h2 { | |
| color: #003366; | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| display: block; | |
| } | |
| select { | |
| width: 100%; | |
| padding: 12px; | |
| margin-bottom: 20px; | |
| border: 2px solid #009688; | |
| border-radius: 6px; | |
| font-size: 1rem; | |
| transition: border-color 0.3s ease; | |
| } | |
| select:focus { | |
| border-color: #e91e63; | |
| outline: none; | |
| } | |
| #destination-buttons { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 12px; | |
| margin-bottom: 20px; | |
| } | |
| #destination-buttons button { | |
| background: #009688; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| #destination-buttons button:hover { | |
| background: #00796b; | |
| transform: scale(1.05); | |
| } | |
| #destination-buttons button.selected { | |
| background: #e91e63; | |
| transform: scale(1.05); | |
| } | |
| #clear-destinations { | |
| background: #e91e63; | |
| color: white; | |
| border: none; | |
| padding: 12px 25px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| #clear-destinations:hover { | |
| background: #c2185b; | |
| transform: scale(1.05); | |
| } | |
| #fare-display .fare-item { | |
| background: #f0f8ff; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); | |
| animation: fadeInUp 0.5s ease-out; | |
| } | |
| #map { | |
| height: 500px; | |
| border-radius: 8px; | |
| margin-top: 20px; | |
| } | |
| #map-controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 10px; | |
| margin-top: 15px; | |
| flex-wrap: wrap; | |
| } | |
| #map-controls button { | |
| background: #003366; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| #map-controls button:hover { | |
| background: #005588; | |
| transform: scale(1.05); | |
| } | |
| @keyframes slideInDown { | |
| from { transform: translateY(-100%); } | |
| to { transform: translateY(0); } | |
| } | |
| @keyframes slideInLeft { | |
| from { transform: translateX(-100%); } | |
| to { transform: translateX(0); } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes fadeInUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @media (max-width: 768px) { | |
| aside#sidebar { | |
| position: relative; | |
| width: 100%; | |
| height: auto; | |
| transform: none; | |
| animation: none; | |
| } | |
| main { | |
| margin-left: 0; | |
| padding: 15px; | |
| } | |
| #map { | |
| height: 300px; | |
| } | |
| header h1 { | |
| font-size: 1.8rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Dhaka Metro Rail Fare Checker π</h1> | |
| </header> | |
| <aside id="sidebar"> | |
| <h2>Instructions</h2> | |
| <p> | |
| <strong>Welcome to the Dhaka Metro Rail Fare Checker! π</strong><br> | |
| <em>How to use:</em><br> | |
| 1. <strong>Select your Location station</strong>: Choose your starting station from the "Select your Location" dropdown.<br> | |
| 2. <strong>Select your destination(s)</strong>: Click the buttons for your desired destinations. Select multiple for different routes!<br> | |
| 3. <strong>Fare Calculation</strong>: See fares below for your selected routes.<br> | |
| 4. <strong>Clear Destinations</strong>: Hit "Clear All Destinations" to reset.<br> | |
| <hr> | |
| <strong>Interactive Map</strong>: Visualize routes and animate stations below.<br> | |
| - <strong>Dropdowns</strong>: Pick source and destination.<br> | |
| - <strong>Animate Route</strong>: Watch your route come to life.<br> | |
| - <strong>Animate All</strong>: See all stations animated.<br> | |
| - <strong>Stop</strong>: Pause animations anytime.<br> | |
| Enjoy your journey! π<br> | |
| Need help? <a href="https://wa.me/+8801719296601">Contact Support</a>. | |
| </p> | |
| </aside> | |
| <main> | |
| <section id="fare-checker"> | |
| <h2>Check Your Fare</h2> | |
| <label for="origin">Select your Location:</label> | |
| <select id="origin"> | |
| <option value="">Select Journey from</option> | |
| </select> | |
| <div id="destination-buttons"></div> | |
| <button id="clear-destinations">Clear All Destinations</button> | |
| <div id="fare-display"></div> | |
| </section> | |
| <section id="map-section"> | |
| <h2>Interactive Map</h2> | |
| <div id="map"></div> | |
| <div id="map-controls"> | |
| <select id="map-source"> | |
| <option value="">Select Source</option> | |
| </select> | |
| <select id="map-destination"> | |
| <option value="">Select Destination</option> | |
| </select> | |
| <button onclick="startRouteAnimation()">Animate Route</button> | |
| <button onclick="animateAllLocations()">Animate All Locations</button> | |
| <button onclick="stopAnimation()">Stop Animation</button> | |
| </div> | |
| </section> | |
| </main> | |
| <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> | |
| <script src="data.js"></script> | |
| <script> | |
| // Populate origin dropdown | |
| const originSelect = document.getElementById('origin'); | |
| const origins = [...new Set(fareData.map(item => item.Origin))]; | |
| origins.forEach(origin => { | |
| const option = document.createElement('option'); | |
| option.value = option.textContent = origin; | |
| originSelect.appendChild(option); | |
| }); | |
| // Handle origin selection | |
| let selectedDestinations = []; | |
| originSelect.addEventListener('change', () => { | |
| const origin = originSelect.value; | |
| const destButtons = document.getElementById('destination-buttons'); | |
| destButtons.innerHTML = ''; | |
| selectedDestinations = []; | |
| document.getElementById('fare-display').innerHTML = ''; | |
| if (origin) { | |
| const destinations = fareData.filter(item => item.Origin === origin).map(item => item.Destination); | |
| destinations.forEach(dest => { | |
| const btn = document.createElement('button'); | |
| btn.textContent = `Select ${dest}`; | |
| btn.dataset.dest = dest; | |
| btn.addEventListener('click', () => { | |
| btn.classList.toggle('selected'); | |
| if (btn.classList.contains('selected')) { | |
| selectedDestinations.push(dest); | |
| } else { | |
| selectedDestinations = selectedDestinations.filter(d => d !== dest); | |
| } | |
| updateFareDisplay(origin); | |
| }); | |
| destButtons.appendChild(btn); | |
| }); | |
| } | |
| }); | |
| // Update fare display | |
| function updateFareDisplay(origin) { | |
| const fareDisplay = document.getElementById('fare-display'); | |
| fareDisplay.innerHTML = ''; | |
| if (origin && selectedDestinations.length) { | |
| selectedDestinations.forEach(dest => { | |
| const fare = fareData.find(item => item.Origin === origin && item.Destination === dest); | |
| if (fare) { | |
| const div = document.createElement('div'); | |
| div.className = 'fare-item'; | |
| div.innerHTML = ` | |
| <h4>π <strong>${origin}</strong> to <strong>${dest}</strong> Fare</h4> | |
| <p>π΅ Fare: <strong>${fare['Fare (ΰ§³)']}ΰ§³</strong></p> | |
| <p>β¨ Enjoy your journey!</p> | |
| `; | |
| fareDisplay.appendChild(div); | |
| } | |
| }); | |
| } | |
| } | |
| // Clear destinations | |
| document.getElementById('clear-destinations').addEventListener('click', () => { | |
| document.querySelectorAll('#destination-buttons button.selected').forEach(btn => { | |
| btn.classList.remove('selected'); | |
| }); | |
| selectedDestinations = []; | |
| updateFareDisplay(originSelect.value); | |
| }); | |
| // Map setup | |
| const coordinates = { | |
| "Uttara North": [23.869066, 90.367445], | |
| "Uttara Center": [23.860118, 90.365106], | |
| "Uttara South": [23.845934, 90.363175], | |
| "Pallabi": [23.82619516961383, 90.36481554252525], | |
| "Mirpur 11": [23.819438208310213, 90.36528532902963], | |
| "Mirpur 10": [23.808582994847285, 90.36821595330717], | |
| "Kazipara": [23.800017952100532, 90.37178261495391], | |
| "Shewrapara": [23.79070140857881, 90.37564622631841], | |
| "Agargaon": [23.778385546736345, 90.3800557456356], | |
| "Bijoy Sarani": [23.766638127271825, 90.38307537134754], | |
| "Farmgate": [23.75923604938459, 90.38694218434738], | |
| "Kawran Bazar": [23.751392319539104, 90.39275707447003], | |
| "Shahbagh": [23.740324209546923, 90.39600784811131], | |
| "Dhaka University": [23.732091083122114, 90.39659408796354], | |
| "Bangladesh Secretariat": [23.73004754106779, 90.40764881366906], | |
| "Motijheel": [23.72816566933198, 90.41923497972823], | |
| "Kamalapur": [23.732367758919807, 90.42547378971085] | |
| }; | |
| const map = L.map('map').setView([23.8103, 90.4125], 12); | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| maxZoom: 19, | |
| attribution: 'Β© OpenStreetMap contributors' | |
| }).addTo(map); | |
| const markers = {}; | |
| for (const [name, coord] of Object.entries(coordinates)) { | |
| markers[name] = L.marker(coord).addTo(map).bindPopup(`<b>${name}</b>`); | |
| } | |
| const mapSource = document.getElementById('map-source'); | |
| const mapDest = document.getElementById('map-destination'); | |
| Object.keys(coordinates).forEach(loc => { | |
| const opt = document.createElement('option'); | |
| opt.value = opt.textContent = loc; | |
| mapSource.appendChild(opt.cloneNode(true)); | |
| mapDest.appendChild(opt); | |
| }); | |
| let currentIndex = 0; | |
| const markerArray = Object.values(markers); | |
| let animationTimeout; | |
| function getIntermediateNodes(source, dest) { | |
| const locations = Object.keys(coordinates); | |
| const sIdx = locations.indexOf(source); | |
| const dIdx = locations.indexOf(dest); | |
| return sIdx < dIdx ? locations.slice(sIdx, dIdx + 1) : locations.slice(dIdx, sIdx + 1).reverse(); | |
| } | |
| function startRouteAnimation() { | |
| const source = mapSource.value; | |
| const dest = mapDest.value; | |
| if (!source || !dest) return alert('Select source and destination.'); | |
| const route = getIntermediateNodes(source, dest); | |
| let idx = 0; | |
| function animate() { | |
| if (idx >= route.length) return; | |
| const marker = markers[route[idx]]; | |
| map.flyTo(marker.getLatLng(), 14, { duration: 2 }); | |
| marker.openPopup(); | |
| idx++; | |
| animationTimeout = setTimeout(animate, 3000); | |
| } | |
| animate(); | |
| } | |
| function animateAllLocations() { | |
| if (currentIndex > 0) markerArray[currentIndex - 1].closePopup(); | |
| if (currentIndex < markerArray.length) { | |
| const marker = markerArray[currentIndex]; | |
| map.flyTo(marker.getLatLng(), 14, { duration: 2 }); | |
| marker.openPopup(); | |
| currentIndex++; | |
| animationTimeout = setTimeout(animateAllLocations, 3000); | |
| } else { | |
| currentIndex = 0; | |
| } | |
| } | |
| function stopAnimation() { | |
| clearTimeout(animationTimeout); | |
| currentIndex = 0; | |
| markerArray.forEach(m => m.closePopup()); | |
| map.setView([23.8103, 90.4125], 12); | |
| mapSource.value = mapDest.value = ''; | |
| } | |
| </script> | |
| </body> | |
| </html> |