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> | |
<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> |