feat: implement location selection interface with EXIF, GPS, and map picker
- Created LocationSelector component with three location input methods: * Auto-populate from photo EXIF data * Device GPS location capture with accuracy indicator * Interactive Leaflet map picker with draggable marker - Added Ireland coordinate validation (lat: 51.4-55.4, lng: -10.5--5.5) - Created coordinate utility functions for validation and formatting - Added LocationSelectorDemo page for testing at /demo/location-selector - Displays selected location with coordinates, source, and accuracy - Provides clear error messages for validation failures - Satisfies requirements 1.1, 1.2, 1.3, 8.2
This commit is contained in:
70
src/utils/coordinates.ts
Normal file
70
src/utils/coordinates.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Utility functions for coordinate validation and manipulation
|
||||
*/
|
||||
|
||||
import type { Coordinates } from '@/types';
|
||||
|
||||
// Ireland bounding box for coordinate validation
|
||||
export const IRELAND_BOUNDS = {
|
||||
minLat: 51.4,
|
||||
maxLat: 55.4,
|
||||
minLng: -10.5,
|
||||
maxLng: -5.5,
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates if coordinates are within Ireland's boundaries
|
||||
*/
|
||||
export function isWithinIreland(lat: number, lng: number): boolean {
|
||||
return (
|
||||
lat >= IRELAND_BOUNDS.minLat &&
|
||||
lat <= IRELAND_BOUNDS.maxLat &&
|
||||
lng >= IRELAND_BOUNDS.minLng &&
|
||||
lng <= IRELAND_BOUNDS.maxLng
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if coordinates object is within Ireland's boundaries
|
||||
*/
|
||||
export function validateIrelandCoordinates(coords: Coordinates): boolean {
|
||||
return isWithinIreland(coords.latitude, coords.longitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance between two coordinates in meters using the Haversine formula
|
||||
*/
|
||||
export function calculateDistance(
|
||||
coord1: Coordinates,
|
||||
coord2: Coordinates
|
||||
): number {
|
||||
const R = 6371e3; // Earth's radius in meters
|
||||
const φ1 = (coord1.latitude * Math.PI) / 180;
|
||||
const φ2 = (coord2.latitude * Math.PI) / 180;
|
||||
const Δφ = ((coord2.latitude - coord1.latitude) * Math.PI) / 180;
|
||||
const Δλ = ((coord2.longitude - coord1.longitude) * Math.PI) / 180;
|
||||
|
||||
const a =
|
||||
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||||
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats coordinates for display
|
||||
*/
|
||||
export function formatCoordinates(coords: Coordinates, precision: number = 6): string {
|
||||
return `${coords.latitude.toFixed(precision)}, ${coords.longitude.toFixed(precision)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets accuracy label based on accuracy value in meters
|
||||
*/
|
||||
export function getAccuracyLabel(accuracy: number): string {
|
||||
if (accuracy < 10) return 'Excellent';
|
||||
if (accuracy < 50) return 'Good';
|
||||
if (accuracy < 100) return 'Fair';
|
||||
return 'Poor';
|
||||
}
|
||||
@@ -1,17 +1,7 @@
|
||||
// Utility functions for the Beach Litter Mapping Platform
|
||||
|
||||
/**
|
||||
* Validates if coordinates are within Ireland's geographical bounds
|
||||
*/
|
||||
export function isValidIrishCoordinates(lat: number, lng: number): boolean {
|
||||
// Ireland approximate bounds
|
||||
const MIN_LAT = 51.4;
|
||||
const MAX_LAT = 55.4;
|
||||
const MIN_LNG = -10.5;
|
||||
const MAX_LNG = -5.5;
|
||||
|
||||
return lat >= MIN_LAT && lat <= MAX_LAT && lng >= MIN_LNG && lng <= MAX_LNG;
|
||||
}
|
||||
// Re-export coordinate utilities
|
||||
export * from './coordinates';
|
||||
|
||||
/**
|
||||
* Formats a date to ISO 8601 format
|
||||
|
||||
Reference in New Issue
Block a user