geolocation support
This commit is contained in:
157
app.js
157
app.js
@ -58,12 +58,18 @@ let hotelMarker = null;
|
||||
let routePolyline = null;
|
||||
let hotelLocation = null;
|
||||
let isPlacingHotel = false;
|
||||
let userLocationMarker = null;
|
||||
let userLocationCircle = null;
|
||||
let gpsWatchId = null;
|
||||
let hasInitialGpsFix = false;
|
||||
let lastKnownLocation = null;
|
||||
|
||||
// Constants
|
||||
const WALKING_SPEED_KMH = 5.0;
|
||||
const WALKING_SPEED_MPH = 3.1;
|
||||
const STREET_FACTOR = 1.3;
|
||||
const BUDAPEST_CENTER = [47.4979, 19.0402];
|
||||
const GPS_FOCUS_ZOOM = 15;
|
||||
|
||||
// Category colors for markers
|
||||
const categoryColors = {
|
||||
@ -109,6 +115,7 @@ const categoryIcons = {
|
||||
|
||||
// Initialize the application
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setGpsButtonState('idle');
|
||||
initializeMap();
|
||||
renderSightsList();
|
||||
setupEventListeners();
|
||||
@ -185,6 +192,142 @@ function handleMapClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// GPS button state manager
|
||||
function setGpsButtonState(state) {
|
||||
const gpsButton = document.getElementById('gpsToggle');
|
||||
if (!gpsButton) return;
|
||||
|
||||
gpsButton.classList.remove('gps-control--active', 'gps-control--pending');
|
||||
gpsButton.dataset.state = state;
|
||||
|
||||
if (state === 'active') {
|
||||
gpsButton.classList.add('gps-control--active');
|
||||
gpsButton.setAttribute('aria-pressed', 'true');
|
||||
gpsButton.setAttribute('title', 'Center map on my location');
|
||||
} else if (state === 'pending') {
|
||||
gpsButton.classList.add('gps-control--pending');
|
||||
gpsButton.setAttribute('aria-pressed', 'true');
|
||||
gpsButton.setAttribute('title', 'Locating...');
|
||||
} else {
|
||||
gpsButton.setAttribute('aria-pressed', 'false');
|
||||
gpsButton.setAttribute('title', 'Show my location');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle GPS toggle clicks
|
||||
function handleGpsButtonClick() {
|
||||
if (!gpsWatchId) {
|
||||
startGpsTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasInitialGpsFix && lastKnownLocation) {
|
||||
map.setView(lastKnownLocation, Math.max(map.getZoom(), GPS_FOCUS_ZOOM), {
|
||||
animate: true
|
||||
});
|
||||
} else {
|
||||
setGpsButtonState('pending');
|
||||
}
|
||||
}
|
||||
|
||||
function startGpsTracking() {
|
||||
if (!('geolocation' in navigator)) {
|
||||
alert('Geolocation is not supported by your browser.');
|
||||
return;
|
||||
}
|
||||
|
||||
setGpsButtonState('pending');
|
||||
|
||||
gpsWatchId = navigator.geolocation.watchPosition(
|
||||
handleGpsSuccess,
|
||||
handleGpsError,
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
maximumAge: 10000,
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleGpsSuccess(position) {
|
||||
const { latitude, longitude, accuracy } = position.coords;
|
||||
const latlng = [latitude, longitude];
|
||||
|
||||
lastKnownLocation = latlng;
|
||||
|
||||
if (!userLocationMarker) {
|
||||
userLocationMarker = L.circleMarker(latlng, {
|
||||
radius: 8,
|
||||
fillColor: '#2563eb',
|
||||
color: '#ffffff',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
pane: 'markerPane'
|
||||
}).addTo(map);
|
||||
} else {
|
||||
userLocationMarker.setLatLng(latlng);
|
||||
}
|
||||
|
||||
if (!userLocationCircle) {
|
||||
userLocationCircle = L.circle(latlng, {
|
||||
radius: accuracy,
|
||||
color: '#2563eb',
|
||||
weight: 1,
|
||||
opacity: 0.6,
|
||||
fillColor: '#3b82f6',
|
||||
fillOpacity: 0.15
|
||||
}).addTo(map);
|
||||
} else {
|
||||
userLocationCircle.setLatLng(latlng);
|
||||
userLocationCircle.setRadius(accuracy);
|
||||
}
|
||||
|
||||
if (!hasInitialGpsFix) {
|
||||
hasInitialGpsFix = true;
|
||||
map.setView(latlng, Math.max(map.getZoom(), GPS_FOCUS_ZOOM));
|
||||
}
|
||||
|
||||
setGpsButtonState('active');
|
||||
}
|
||||
|
||||
function handleGpsError(error) {
|
||||
console.warn('Geolocation error:', error);
|
||||
|
||||
if (error.code === error.PERMISSION_DENIED) {
|
||||
stopGpsTracking(true);
|
||||
alert('Location access was denied. Enable location permissions to display your position.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasInitialGpsFix) {
|
||||
setGpsButtonState('idle');
|
||||
alert('Unable to determine your location. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
function stopGpsTracking(clearLayers = false) {
|
||||
if (gpsWatchId !== null) {
|
||||
navigator.geolocation.clearWatch(gpsWatchId);
|
||||
gpsWatchId = null;
|
||||
}
|
||||
|
||||
if (clearLayers) {
|
||||
if (userLocationMarker) {
|
||||
map.removeLayer(userLocationMarker);
|
||||
userLocationMarker = null;
|
||||
}
|
||||
if (userLocationCircle) {
|
||||
map.removeLayer(userLocationCircle);
|
||||
userLocationCircle = null;
|
||||
}
|
||||
hasInitialGpsFix = false;
|
||||
lastKnownLocation = null;
|
||||
}
|
||||
|
||||
setGpsButtonState('idle');
|
||||
}
|
||||
|
||||
// Place hotel marker
|
||||
function placeHotel(latlng) {
|
||||
// Remove existing hotel marker
|
||||
@ -561,6 +704,12 @@ function setupEventListeners() {
|
||||
document.getElementById('clearRouteBtn').addEventListener('click', clearRoute);
|
||||
document.getElementById('downloadRouteBtn').addEventListener('click', downloadRoute);
|
||||
|
||||
// GPS control
|
||||
const gpsButton = document.getElementById('gpsToggle');
|
||||
if (gpsButton) {
|
||||
gpsButton.addEventListener('click', handleGpsButtonClick);
|
||||
}
|
||||
|
||||
// Modal controls
|
||||
document.getElementById('closeModal').addEventListener('click', () => {
|
||||
document.getElementById('sightModal').classList.add('hidden');
|
||||
@ -578,6 +727,12 @@ function setupEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (gpsWatchId !== null && 'geolocation' in navigator) {
|
||||
navigator.geolocation.clearWatch(gpsWatchId);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear route
|
||||
function clearRoute() {
|
||||
selectedSights.clear();
|
||||
@ -665,4 +820,4 @@ function showInstructions() {
|
||||
|
||||
// Make functions available globally for HTML onclick handlers
|
||||
window.toggleSightSelection = toggleSightSelection;
|
||||
window.openSightModal = openSightModal;
|
||||
window.openSightModal = openSightModal;
|
||||
|
||||
@ -105,6 +105,13 @@
|
||||
<!-- Map Container -->
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
<button id="gpsToggle" class="gps-control" type="button" aria-label="Show my location" aria-pressed="false" title="Show my location">
|
||||
<svg class="gps-control__icon" viewBox="0 0 24 24" role="img" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="3" fill="currentColor"></circle>
|
||||
<circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8"></circle>
|
||||
<path d="M12 4V2M12 22v-2M4 12H2M22 12h-2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div id="mapInstructions" class="map-instructions">
|
||||
<div class="instruction-content">
|
||||
<h4>🗺️ How to Use This Map</h4>
|
||||
@ -145,4 +152,4 @@
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
44
style.css
44
style.css
@ -1288,6 +1288,50 @@ p {
|
||||
border-radius: var(--radius-base);
|
||||
}
|
||||
|
||||
.gps-control {
|
||||
position: absolute;
|
||||
bottom: var(--space-16);
|
||||
right: var(--space-16);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(var(--color-slate-500-rgb), 0.25);
|
||||
color: var(--color-slate-500);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: background var(--duration-fast) var(--ease-standard),
|
||||
color var(--duration-fast) var(--ease-standard),
|
||||
transform var(--duration-fast) var(--ease-standard);
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.gps-control:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.gps-control:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--shadow-md), var(--focus-ring);
|
||||
}
|
||||
|
||||
.gps-control--pending {
|
||||
background: rgba(var(--color-slate-500-rgb), 0.4);
|
||||
}
|
||||
|
||||
.gps-control--active {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-btn-primary-text);
|
||||
}
|
||||
|
||||
.gps-control__icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.map-instructions {
|
||||
position: absolute;
|
||||
top: var(--space-20);
|
||||
|
||||
Reference in New Issue
Block a user