/** * Nominatim-only implementation using MapBase */ class NominatimMapApp extends MapBase { constructor() { super(); this.initializeMap(); } /** * Initialize the Leaflet map */ initializeMap() { this.map = L.map('map').setView([42.9634, -85.6681], 10); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, }).addTo(this.map); } /** * Create custom marker for location */ createMarker(location) { const isPersistent = !!location.persistent; const timeAgo = window.getTimeAgo ? window.getTimeAgo(location.created_at) : ''; const persistentText = isPersistent ? '
📌 Persistent Report' : ''; const customIcon = this.createCustomIcon(isPersistent); const marker = L.marker([location.latitude, location.longitude], { icon: customIcon }) .addTo(this.map) .bindPopup(`${location.address}
${location.description || 'No additional details'}
Reported ${timeAgo}${persistentText}`); return marker; } /** * Create custom icon for markers */ createCustomIcon(isPersistent = false) { const iconColor = isPersistent ? '#28a745' : '#dc3545'; // Green for persistent, red for temporary const iconSymbol = isPersistent ? '🔒' : '⚠️'; // Lock for persistent, warning for temporary return L.divIcon({ className: 'custom-marker', html: `
${iconSymbol}
`, iconSize: [30, 30], iconAnchor: [15, 15], popupAnchor: [0, -15] }); } /** * Search for addresses using Nominatim */ async searchAddress(query) { try { // Generate multiple search variations for better results const searches = [ query, // Original query query.replace(' & ', ' and '), // Replace & with 'and' query.replace(' and ', ' & '), // Replace 'and' with & query.replace(' at ', ' & '), // Replace 'at' with & `${query}, Michigan`, // Add Michigan if not present `${query}, MI`, // Add MI if not present ]; // Add street type variations const streetVariations = []; searches.forEach(search => { streetVariations.push(search); streetVariations.push(search.replace(' St ', ' Street ')); streetVariations.push(search.replace(' Street ', ' St ')); streetVariations.push(search.replace(' Ave ', ' Avenue ')); streetVariations.push(search.replace(' Avenue ', ' Ave ')); streetVariations.push(search.replace(' Rd ', ' Road ')); streetVariations.push(search.replace(' Road ', ' Rd ')); streetVariations.push(search.replace(' Blvd ', ' Boulevard ')); streetVariations.push(search.replace(' Boulevard ', ' Blvd ')); }); // Remove duplicates const uniqueSearches = [...new Set(streetVariations)]; // Try each search variation until we get results for (const searchQuery of uniqueSearches) { try { const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&extratags=1&limit=5&q=${encodeURIComponent(searchQuery)}`); const data = await response.json(); if (data && data.length > 0) { // Prefer results that are in Michigan/Grand Rapids const michiganResults = data.filter(item => item.display_name.toLowerCase().includes('michigan') || item.display_name.toLowerCase().includes('grand rapids') || item.display_name.toLowerCase().includes(', mi') ); const results = michiganResults.length > 0 ? michiganResults : data; this.showAutocomplete(results.slice(0, 5)); return; } } catch (error) { console.warn(`Geocoding failed for: "${searchQuery}"`, error); } } // If no results found, show empty autocomplete this.showAutocomplete([]); } catch (error) { console.error('Error searching addresses:', error); this.showAutocomplete([]); } } /** * Handle form submission with enhanced geocoding */ async handleSubmit() { const address = this.addressInput.value.trim(); const description = this.descriptionInput.value.trim(); if (!address) { this.showMessage('Please enter an address', 'error'); return; } // If coordinates are not set, try to geocode the address let lat = parseFloat(this.addressInput.dataset.lat); let lon = parseFloat(this.addressInput.dataset.lon); if (!lat || !lon || isNaN(lat) || isNaN(lon)) { // Try to geocode the current address value const geocodeResult = await this.geocodeAddress(address); if (geocodeResult) { lat = parseFloat(geocodeResult.lat); lon = parseFloat(geocodeResult.lon); } else { this.showMessage('Please select a valid address from the suggestions', 'error'); return; } } // Disable submit button this.submitButton.disabled = true; this.submitButton.innerHTML = ' Submitting...'; try { const response = await fetch('/api/locations', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ address, latitude: lat, longitude: lon, description }) }); const data = await response.json(); if (response.ok) { this.showMessage('Location reported successfully!', 'success'); this.resetForm(); this.refreshLocations(); } else { this.handleSubmitError(data); } } catch (error) { console.error('Error submitting location:', error); this.showMessage('Error submitting location. Please try again.', 'error'); } finally { this.submitButton.disabled = false; this.submitButton.innerHTML = ' Report Ice'; } } /** * Geocode an address using Nominatim */ async geocodeAddress(address) { try { const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(address + ', Michigan')}`); const data = await response.json(); if (data && data.length > 0) { return data[0]; } return null; } catch (error) { console.error('Error geocoding address:', error); return null; } } } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new NominatimMapApp(); });