From bb3eb8fcd5882e80f514bd97d202151c7b0f532b Mon Sep 17 00:00:00 2001 From: bdamokos <163609735+bdamokos@users.noreply.github.com> Date: Sun, 29 Dec 2024 21:39:51 +0100 Subject: [PATCH] location setup --- .env.example | 6 +- docs/setup/css/style.css | 217 +++++++++++ docs/setup/index.html | 2 + docs/setup/js/location_setup.js | 622 ++++++++++++++++++++++++++++++++ docs/setup/js/nominatim.js | 174 +++++++++ 5 files changed, 1018 insertions(+), 3 deletions(-) create mode 100644 docs/setup/js/location_setup.js create mode 100644 docs/setup/js/nominatim.js diff --git a/.env.example b/.env.example index 9252cfd..ba6279d 100644 --- a/.env.example +++ b/.env.example @@ -19,12 +19,12 @@ Lines = #Weather setup - use either coordinates or city name and country. Coordinates_LAT = Coordinates_LNG = -# City name +# City name // NOT RECOMMENDED - use coordinates instead City = -# Two letter country code +# Two letter country code // NOT RECOMMENDED - use coordinates instead Country = -# Display settings (IF you don't know what to use, try: epd2in13g_V2 for 4-color displays, epd2in13_V4 for black and white displays) +# Display settings (If you don't know what to use, try: epd2in13g_V2 for 4-color displays, epd2in13_V4 for black and white displays) display_model = epd2in13g_V2 # Screen rotation - by default we rotate the screen 90 degrees to reach a landscape orientation. If your screen is upside down, try 270. screen_rotation = 90 diff --git a/docs/setup/css/style.css b/docs/setup/css/style.css index 3d3b22c..083f087 100644 --- a/docs/setup/css/style.css +++ b/docs/setup/css/style.css @@ -1191,4 +1191,221 @@ input:checked + .slider:before { #advanced-settings-form button[type="submit"]:hover { background-color: #0056b3; +} + +/* Location Setup Styles */ +.location-setup-container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +.location-form { + background: #f5f5f5; + border-radius: 8px; + padding: 20px; + margin-top: 20px; +} + +.coordinates-section, +.city-section { + margin-bottom: 20px; + padding: 15px; + background: white; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.location-button { + display: flex; + align-items: center; + gap: 8px; + background-color: #4CAF50; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + margin-top: 10px; +} + +.location-button svg { + width: 20px; + height: 20px; + fill: currentColor; +} + +.location-button:hover { + background-color: #45a049; +} + +/* Form Group Styles */ +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; +} + +.form-group input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.form-group small { + display: block; + color: #666; + font-size: 12px; + margin-top: 4px; +} + +/* Button Group Styles */ +.button-group { + margin-top: 20px; + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.button-group button { + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: 500; +} + +.button-group button.primary { + background-color: #007bff; + color: white; +} + +.button-group button.primary:hover { + background-color: #0056b3; +} + +/* Status Messages */ +.status-message { + margin: 10px 0; + padding: 10px; + border-radius: 4px; +} + +.status-success { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.status-error { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.status-warning { + background-color: #fff3cd; + color: #856404; + border: 1px solid #ffeeba; +} + +.status-info { + background-color: #cce5ff; + color: #004085; + border: 1px solid #b8daff; +} + +.location-accuracy { + margin-top: 10px; + padding: 8px; + border-radius: 4px; + background-color: #e8f5e9; + color: #2e7d32; + font-size: 0.9em; + display: none; +} + +.location-accuracy.active { + display: block; + animation: fadeIn 0.3s ease-in; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Location Verification Styles */ +.verification-info, +.cross-verification { + margin-top: 10px; + padding: 10px; + border-radius: 4px; + font-size: 0.9em; + animation: fadeIn 0.3s ease-in; +} + +.verification-info .status-info, +.verification-info .status-success, +.verification-info .status-error, +.cross-verification .status-success, +.cross-verification .status-warning, +.cross-verification .status-error { + padding: 10px; + border-radius: 4px; + margin: 5px 0; +} + +.verification-info .status-info { + background-color: #e3f2fd; + color: #1565c0; + border: 1px solid #bbdefb; +} + +.verification-info .status-success, +.cross-verification .status-success { + background-color: #e8f5e9; + color: #2e7d32; + border: 1px solid #c8e6c9; +} + +.verification-info .status-error, +.cross-verification .status-error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.cross-verification .status-warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.cross-verification { + margin: 20px 0; + font-size: 0.95em; +} + +.cross-verification .status-warning { + line-height: 1.5; +} + +/* Add bullet point styling */ +.cross-verification .status-warning br { + margin-bottom: 5px; +} + +.cross-verification .status-warning { + padding-left: 15px; +} + +.cross-verification .status-warning br + • { + margin-left: -15px; } \ No newline at end of file diff --git a/docs/setup/index.html b/docs/setup/index.html index 1f933e4..ff630a3 100644 --- a/docs/setup/index.html +++ b/docs/setup/index.html @@ -212,6 +212,8 @@

⚙️ Advanced Settings

+ + \ No newline at end of file diff --git a/docs/setup/js/location_setup.js b/docs/setup/js/location_setup.js new file mode 100644 index 0000000..783e5e0 --- /dev/null +++ b/docs/setup/js/location_setup.js @@ -0,0 +1,622 @@ +// Location Setup Module + +document.addEventListener('DOMContentLoaded', () => { + const locationSetupButton = document.getElementById('location-setup-button'); + if (locationSetupButton) { + locationSetupButton.addEventListener('click', () => { + if (window.startLocationSetup) { + window.startLocationSetup(); + } else { + window.showError('Location setup not initialized yet'); + } + }); + } +}); + +// Location configuration functionality +window.startLocationSetup = async function() { + try { + const locationSetup = document.getElementById('location-setup'); + const button = document.getElementById('location-setup-button'); + + if (!locationSetup) { + throw new Error("Location setup element not found"); + } + + // Show location setup UI and hide the button + locationSetup.style.display = 'block'; + button.style.display = 'none'; + + // Get current location configuration + const currentConfig = {}; + const configKeys = ['Coordinates_LAT', 'Coordinates_LNG', 'City', 'Country']; + + for (const key of configKeys) { + try { + const response = await window.setupDevice.send(JSON.stringify({ + command: 'config_get', + config_type: 'display_env', + key: key + })); + + if (response.status === 'success') { + currentConfig[key] = response.value; + } + } catch (error) { + console.error(`Failed to get ${key}:`, error); + } + } + + // Render the location setup interface + locationSetup.innerHTML = ` +
+

Location Configuration

+

Set your location coordinates using one of these methods:

+ +
+
+
+
+ + + Valid range: -90 to 90 degrees +
+
+ + + Valid range: -180 to 180 degrees +
+ +
+ + + +
+ +
+
+
+ + + +
+ +
+
+
+
+ `; + + } catch (error) { + console.error('Failed to start location setup:', error); + window.showError('Location Setup Error: ' + error.message); + } +} + +// Helper function to format coordinate values consistently +window.formatCoordinate = function(value) { + if (!value) return ''; + // Ensure the value is a number and format it with a decimal point + return Number(value).toString().replace(',', '.'); +} + +/** + * Calculates the precision of latitude and longitude for a given number of decimal places and latitude. + * @param {number} latitude - The latitude at which to calculate the precision. + * @param {number} decimalPlaces - The number of decimal places for the coordinate. + * @returns {object} An object containing latitude and longitude precision in meters. + */ +window.calculateGPSPrecision = function(latitude, decimalPlaces) { + const EARTH_RADIUS_KM = 6371; // Mean radius of the Earth in kilometers + const METERS_PER_DEGREE_LATITUDE = (2 * Math.PI * EARTH_RADIUS_KM * 1000) / 360; // ~111,320 meters + + // Convert latitude to radians for cosine calculation + const latInRadians = (latitude * Math.PI) / 180; + + // Distance per degree of longitude + const metersPerDegreeLongitude = METERS_PER_DEGREE_LATITUDE * Math.cos(latInRadians); + + // Precision for latitude and longitude + const precisionLat = METERS_PER_DEGREE_LATITUDE / Math.pow(10, decimalPlaces); + const precisionLon = metersPerDegreeLongitude / Math.pow(10, decimalPlaces); + + return { + latitudePrecision: precisionLat, + longitudePrecision: precisionLon + }; +} + +// Helper function to format distance in a human-readable way +window.formatDistance = function(meters) { + if (meters >= 1000) { + return `≈ ${(meters/1000).toFixed(2)} km`; + } else if (meters >= 1) { + return `≈ ${Math.round(meters)} m`; + } else if (meters >= 0.01) { + return `≈ ${(meters * 100).toFixed(1)} cm`; + } else { + return `≈ ${(meters * 1000).toFixed(1)} mm`; + } +} + +// Helper function to calculate coordinate precision +window.calculatePrecision = function(value, type, otherValue) { + if (!value) return ''; + + // Convert to string and clean up + const str = value.toString().replace(',', '.'); + + // Find number of decimal places + const decimalPlaces = str.includes('.') ? str.split('.')[1].length : 0; + + // For longitude precision, we need the latitude + let precision; + if (type === 'longitude' && otherValue) { + // Calculate actual precision based on latitude + const gps = calculateGPSPrecision(Number(otherValue), decimalPlaces); + precision = formatDistance(gps.longitudePrecision); + } else { + // Calculate latitude precision or fallback for longitude if no latitude available + const gps = calculateGPSPrecision(0, decimalPlaces); // Use equator as fallback + precision = formatDistance(type === 'latitude' ? gps.latitudePrecision : gps.longitudePrecision); + } + + return `Precision with ${decimalPlaces} decimal places: ${precision}`; +} + +// Update the validateCoordinate function to only show range +window.validateCoordinate = function(input, type) { + // Clear any existing GPS accuracy info since we're manually editing + const accuracyDiv = document.getElementById('location-accuracy'); + if (accuracyDiv) { + accuracyDiv.innerHTML = ''; + accuracyDiv.classList.remove('active'); + } + + // Replace any commas with periods for consistency + let value = input.value.replace(',', '.'); + input.value = value; + + // Convert to number for validation + value = Number(value); + + if (isNaN(value)) { + input.setCustomValidity('Please enter a valid number'); + input.nextElementSibling.textContent = type === 'latitude' ? + 'Valid range: -90 to 90 degrees' : + 'Valid range: -180 to 180 degrees'; + return false; + } + + // Check range based on coordinate type + if (type === 'latitude' && (value < -90 || value > 90)) { + input.setCustomValidity('Latitude must be between -90 and 90 degrees'); + return false; + } + if (type === 'longitude' && (value < -180 || value > 180)) { + input.setCustomValidity('Longitude must be between -180 and 180 degrees'); + return false; + } + + input.setCustomValidity(''); + input.nextElementSibling.textContent = type === 'latitude' ? + 'Valid range: -90 to 90 degrees' : + 'Valid range: -180 to 180 degrees'; + + return true; +} + +// Show/hide city lookup section +window.showCityLookup = function() { + const cityLookup = document.getElementById('city-lookup'); + cityLookup.style.display = cityLookup.style.display === 'none' ? 'block' : 'none'; +} + +// Find coordinates for a city +window.findCityCoordinates = async function() { + const city = document.getElementById('city').value.trim(); + const country = document.getElementById('country').value.trim(); + const verificationDiv = document.getElementById('city-verification'); + + // Clear any existing GPS accuracy info since we're using city lookup + const accuracyDiv = document.getElementById('location-accuracy'); + if (accuracyDiv) { + accuracyDiv.innerHTML = ''; + accuracyDiv.classList.remove('active'); + } + + // Create verification div if it doesn't exist + if (!verificationDiv) { + const cityLookup = document.getElementById('city-lookup'); + if (cityLookup) { + const div = document.createElement('div'); + div.id = 'city-verification'; + div.className = 'verification-info'; + cityLookup.appendChild(div); + } else { + console.error('Could not find city lookup section'); + return; + } + } + + if (city && country) { + try { + document.getElementById('city-verification').innerHTML = '
Looking up coordinates...
'; + const location = await geocodeLocation(city, country); + + // Update coordinate fields + document.getElementById('latitude').value = location.lat; + document.getElementById('longitude').value = location.lon; + + // Validate coordinates + validateCoordinate(document.getElementById('latitude'), 'latitude'); + validateCoordinate(document.getElementById('longitude'), 'longitude'); + + document.getElementById('city-verification').innerHTML = ` +
+ Found coordinates for:
+ ${location.display_name} +
+ `; + } catch (error) { + document.getElementById('city-verification').innerHTML = ` +
+ Could not find coordinates: ${error.message} +
+ `; + } + } else { + document.getElementById('city-verification').innerHTML = ` +
+ Please enter both city name and country +
+ `; + } +} + +// Verify coordinates +window.verifyCoordinates = async function() { + const lat = Number(document.getElementById('latitude').value); + const lon = Number(document.getElementById('longitude').value); + const verificationDiv = document.getElementById('coords-verification'); + + // Create verification div if it doesn't exist + if (!verificationDiv) { + const coordsSection = document.querySelector('.coordinates-section'); + if (coordsSection) { + const div = document.createElement('div'); + div.id = 'coords-verification'; + div.className = 'verification-info'; + coordsSection.appendChild(div); + } else { + console.error('Could not find coordinates section'); + return; + } + } + + if (!isNaN(lat) && !isNaN(lon) && + lat >= -90 && lat <= 90 && + lon >= -180 && lon <= 180) { + try { + document.getElementById('coords-verification').innerHTML = '
Verifying location...
'; + const location = await reverseGeocodeLocation(lat, lon); + document.getElementById('coords-verification').innerHTML = ` +
+ These coordinates point to:
+ ${location.display_name} +
+ `; + } catch (error) { + document.getElementById('coords-verification').innerHTML = ` +
+ Could not verify these coordinates: ${error.message} +
+ `; + } + } else { + document.getElementById('coords-verification').innerHTML = ` +
+ Please enter valid coordinates +
+ `; + } +} + +// Update saveLocationConfig to only save coordinates +window.saveLocationConfig = async function(event) { + event.preventDefault(); + + const lat = document.getElementById('latitude').value; + const lng = document.getElementById('longitude').value; + let success = true; + + // Validate coordinates before saving + if (!validateCoordinate(document.getElementById('latitude'), 'latitude') || + !validateCoordinate(document.getElementById('longitude'), 'longitude')) { + return; + } + + try { + // Save latitude + const latResponse = await window.setupDevice.send(JSON.stringify({ + command: 'config_set', + config_type: 'display_env', + key: 'Coordinates_LAT', + value: lat.replace(',', '.') + })); + + // Save longitude + const lngResponse = await window.setupDevice.send(JSON.stringify({ + command: 'config_set', + config_type: 'display_env', + key: 'Coordinates_LNG', + value: lng.replace(',', '.') + })); + + if (latResponse.status !== 'success' || lngResponse.status !== 'success') { + success = false; + window.showError('Failed to update coordinates'); + } + + // Clear city/country fields if they exist + const cityInput = document.getElementById('city'); + const countryInput = document.getElementById('country'); + if (cityInput) cityInput.value = ''; + if (countryInput) countryInput.value = ''; + + if (success) { + window.showMessage('Location coordinates saved successfully'); + // Hide city lookup section + document.getElementById('city-lookup').style.display = 'none'; + } + } catch (error) { + console.error('Failed to save coordinates:', error); + window.showError(`Failed to save coordinates: ${error.message}`); + } +} + +// Add debounce function for API calls +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +// Handle coordinate changes +window.handleCoordinateChange = debounce(async function() { + const lat = Number(document.getElementById('latitude').value); + const lon = Number(document.getElementById('longitude').value); + const verificationDiv = document.getElementById('coords-verification'); + + if (!isNaN(lat) && !isNaN(lon) && + lat >= -90 && lat <= 90 && + lon >= -180 && lon <= 180) { + try { + verificationDiv.innerHTML = '
Verifying location...
'; + const location = await reverseGeocodeLocation(lat, lon); + verificationDiv.innerHTML = ` +
+ These coordinates correspond to:
+ ${location.display_name} +
+ `; + await crossVerifyInputs(); + } catch (error) { + verificationDiv.innerHTML = ` +
+ Could not verify these coordinates: ${error.message} +
+ `; + } + } else { + verificationDiv.innerHTML = ''; + } +}, 1000); + +// Handle city/country changes +window.handleCityCountryChange = debounce(async function() { + const city = document.getElementById('city').value.trim(); + const country = document.getElementById('country').value.trim(); + const verificationDiv = document.getElementById('city-verification'); + + if (city && country && country.length === 2) { + try { + verificationDiv.innerHTML = '
Verifying location...
'; + const location = await geocodeLocation(city, country); + verificationDiv.innerHTML = ` +
+ Found location at:
+ ${location.display_name} +
+ `; + await crossVerifyInputs(); + } catch (error) { + verificationDiv.innerHTML = ` +
+ Could not verify this location: ${error.message} +
+ `; + } + } else { + verificationDiv.innerHTML = ''; + } +}, 1000); + +// Cross-verify all inputs +async function crossVerifyInputs() { + const lat = Number(document.getElementById('latitude').value); + const lon = Number(document.getElementById('longitude').value); + const city = document.getElementById('city').value.trim(); + const country = document.getElementById('country').value.trim(); + const crossVerificationDiv = document.getElementById('cross-verification'); + + // Only cross-verify if we have both coordinate and city/country data + if (!isNaN(lat) && !isNaN(lon) && city && country) { + try { + const result = await crossVerifyLocation({ lat, lon, city, country }); + + if (result.match) { + crossVerificationDiv.innerHTML = ` +
+ ✓ The coordinates and city/country information match! +
+ `; + } else { + const distance = result.details.distance.toFixed(1); + const cityMatch = result.details.cityMatch ? '✓' : '✗'; + const countryMatch = result.details.countryMatch ? '✓' : '✗'; + + crossVerificationDiv.innerHTML = ` +
+ ⚠️ Location Mismatch Details:
+ • Coordinates point to: ${result.details.fromCoords.display_name}
+ • City/Country resolves to coordinates: (${result.details.fromCity.lat}, ${result.details.fromCity.lon})
+ • Distance between points: ${distance} km
+ • City match: ${cityMatch} ${result.details.fromCoords.city || 'Unknown'} vs ${city}
+ • Country match: ${countryMatch} ${result.details.fromCoords.country_code || 'Unknown'} vs ${country} +
+ `; + } + } catch (error) { + crossVerificationDiv.innerHTML = ` +
+ Could not cross-verify locations: ${error.message} +
+ `; + } + } else { + crossVerificationDiv.innerHTML = ''; + } +} + +// Update getCurrentLocation to show both accuracy and precision +window.getCurrentLocation = function() { + if (!navigator.geolocation) { + window.showError('Geolocation is not supported by your browser'); + return; + } + + navigator.geolocation.getCurrentPosition( + async (position) => { + const lat = position.coords.latitude; + const lng = position.coords.longitude; + + // Format coordinates consistently with decimal points + document.getElementById('latitude').value = lat.toString().replace(',', '.'); + document.getElementById('longitude').value = lng.toString().replace(',', '.'); + + // Validate coordinates + validateCoordinate(document.getElementById('latitude'), 'latitude'); + validateCoordinate(document.getElementById('longitude'), 'longitude'); + + // Show accuracy and precision if available + if (position.coords.accuracy) { + const accuracy = Math.round(position.coords.accuracy); + const latPrecision = calculateGPSPrecision(lat, lat.toString().split('.')[1]?.length || 0); + const accuracyDiv = document.getElementById('location-accuracy'); + accuracyDiv.innerHTML = ` +
+ Location Data Quality:
+ • Your actual location is within ±${accuracy} meters of these coordinates
+ • At this latitude, each decimal place in coordinates represents ${formatDistance(latPrecision.latitudePrecision)} North/South and ${formatDistance(latPrecision.longitudePrecision)} East/West +
+ `; + accuracyDiv.classList.add('active'); + } + + // Verify the coordinates + try { + const verificationDiv = document.getElementById('coords-verification'); + if (!verificationDiv) { + const coordsSection = document.querySelector('.coordinates-section'); + if (coordsSection) { + const div = document.createElement('div'); + div.id = 'coords-verification'; + div.className = 'verification-info'; + coordsSection.appendChild(div); + } + } + + if (document.getElementById('coords-verification')) { + document.getElementById('coords-verification').innerHTML = '
Verifying location...
'; + const location = await reverseGeocodeLocation(lat, lng); + document.getElementById('coords-verification').innerHTML = ` +
+ These coordinates point to:
+ ${location.display_name} +
+ `; + } + } catch (error) { + console.error('Verification error:', error); + if (document.getElementById('coords-verification')) { + document.getElementById('coords-verification').innerHTML = ` +
+ Could not verify these coordinates: ${error.message} +
+ `; + } + } + }, + (error) => { + window.showError('Error getting location: ' + error.message); + }, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 0 + } + ); +} \ No newline at end of file diff --git a/docs/setup/js/nominatim.js b/docs/setup/js/nominatim.js new file mode 100644 index 0000000..aa1fb2f --- /dev/null +++ b/docs/setup/js/nominatim.js @@ -0,0 +1,174 @@ +// Nominatim API Module + +// Base URL for Nominatim API +const NOMINATIM_BASE_URL = 'https://nominatim.openstreetmap.org'; + +/** + * Forward geocoding - get coordinates from city and country + * @param {string} city - City name + * @param {string} country - Two-letter country code + * @returns {Promise<{lat: number, lon: number, display_name: string}>} + */ +window.geocodeLocation = async function(city, country) { + const params = new URLSearchParams({ + q: `${city}, ${country}`, + format: 'json', + limit: 1, + addressdetails: 1 + }); + + try { + const response = await fetch(`${NOMINATIM_BASE_URL}/search?${params}`, { + headers: { + 'Accept': 'application/json', + 'User-Agent': 'RPi-Display-Setup' + } + }); + + if (!response.ok) { + throw new Error('Geocoding failed'); + } + + const data = await response.json(); + if (!data || data.length === 0) { + throw new Error('Location not found'); + } + + return { + lat: parseFloat(data[0].lat), + lon: parseFloat(data[0].lon), + display_name: data[0].display_name + }; + } catch (error) { + console.error('Geocoding error:', error); + throw error; + } +} + +/** + * Reverse geocoding - get location details from coordinates + * @param {number} lat - Latitude + * @param {number} lon - Longitude + * @returns {Promise<{city: string, country_code: string, display_name: string}>} + */ +window.reverseGeocodeLocation = async function(lat, lon) { + const params = new URLSearchParams({ + lat: lat, + lon: lon, + format: 'json', + addressdetails: 1 + }); + + try { + const response = await fetch(`${NOMINATIM_BASE_URL}/reverse?${params}`, { + headers: { + 'Accept': 'application/json', + 'User-Agent': 'RPi-Display-Setup' + } + }); + + if (!response.ok) { + throw new Error('Reverse geocoding failed'); + } + + const data = await response.json(); + if (!data) { + throw new Error('Location not found'); + } + + return { + city: data.address.city || data.address.town || data.address.village || data.address.municipality, + country_code: data.address.country_code?.toUpperCase(), + display_name: data.display_name + }; + } catch (error) { + console.error('Reverse geocoding error:', error); + throw error; + } +} + +/** + * Cross-verify location data + * @param {Object} params - Location parameters + * @param {number} [params.lat] - Latitude + * @param {number} [params.lon] - Longitude + * @param {string} [params.city] - City name + * @param {string} [params.country] - Country code + * @returns {Promise<{match: boolean, details: Object}>} + */ +window.crossVerifyLocation = async function({ lat, lon, city, country }) { + let coordsLocation, cityLocation; + let details = {}; + + try { + // Get location details from coordinates + if (typeof lat === 'number' && typeof lon === 'number') { + coordsLocation = await reverseGeocodeLocation(lat, lon); + details.fromCoords = coordsLocation; + } + + // Get coordinates from city and country + if (city && country) { + cityLocation = await geocodeLocation(city, country); + details.fromCity = cityLocation; + } + + // If we have both, check if they match + if (coordsLocation && cityLocation) { + // Calculate distance between the entered coordinates and the city's coordinates + const distance = calculateDistance( + lat, lon, + cityLocation.lat, cityLocation.lon + ); + + details.distance = distance; + + // Consider it a match if: + // 1. Distance is less than 10km OR + // 2. The city names match (accounting for variations) + const cityMatch = coordsLocation.city?.toLowerCase() === city.toLowerCase(); + const countryMatch = coordsLocation.country_code?.toLowerCase() === country.toLowerCase(); + + details.match = distance < 10 || (cityMatch && countryMatch); + details.cityMatch = cityMatch; + details.countryMatch = countryMatch; + } + + return { + match: details.match, + details: details + }; + } catch (error) { + console.error('Cross-verification error:', error); + throw error; + } +} + +/** + * Calculate distance between two points using Haversine formula + * @param {number} lat1 - First latitude + * @param {number} lon1 - First longitude + * @param {number} lat2 - Second latitude + * @param {number} lon2 - Second longitude + * @returns {number} Distance in kilometers + */ +function calculateDistance(lat1, lon1, lat2, lon2) { + const R = 6371; // Earth's radius in km + const dLat = toRad(lat2 - lat1); + const dLon = toRad(lon2 - lon1); + const a = + Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * + Math.sin(dLon/2) * Math.sin(dLon/2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + return R * c; +} + +function toRad(degrees) { + return degrees * (Math.PI / 180); +} + +// Add a small delay between API calls to respect rate limits +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} \ No newline at end of file