geolocation support

This commit is contained in:
2025-10-23 20:04:46 +02:00
parent 91a583941b
commit e2e38b617b
3 changed files with 208 additions and 2 deletions

157
app.js
View File

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

View File

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

View File

@ -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);