/**
* 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();
});