1- // Simple in-memory cache to avoid repeated geocoding calls
2- const locationCache = new Map < string , string > ( ) ;
1+ // Enhanced in-memory cache with TTL
2+ interface CacheEntry {
3+ location : string ;
4+ timestamp : number ;
5+ }
6+ const locationCache = new Map < string , CacheEntry > ( ) ;
7+ const CACHE_TTL = 24 * 60 * 60 * 1000 ; // 24 hours
8+
9+ // Clean up expired cache entries periodically
10+ setInterval ( ( ) => {
11+ const now = Date . now ( ) ;
12+ for ( const [ key , entry ] of locationCache . entries ( ) ) {
13+ if ( now - entry . timestamp > CACHE_TTL ) {
14+ locationCache . delete ( key ) ;
15+ }
16+ }
17+ } , 60 * 60 * 1000 ) ; // Clean up every hour
318
419/**
520 * Reverse geocode lat/lon coordinates to a human-readable location string
@@ -15,17 +30,28 @@ export async function reverseGeocode(
1530) : Promise < string > {
1631 if ( ! lat || ! lon ) return 'Location TBD' ;
1732
18- const cacheKey = `${ lat } ,${ lon } ` ;
19- if ( locationCache . has ( cacheKey ) ) {
20- return locationCache . get ( cacheKey ) ! ;
33+ const cacheKey = `${ lat . toFixed ( 4 ) } ,${ lon . toFixed ( 4 ) } ` ;
34+ const cached = locationCache . get ( cacheKey ) ;
35+
36+ // Return cached location if still valid
37+ if ( cached && Date . now ( ) - cached . timestamp < CACHE_TTL ) {
38+ return cached . location ;
2139 }
2240
2341 try {
42+ const controller = new AbortController ( ) ;
43+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 2000 ) ; // 2 second timeout
44+
2445 const response = await fetch (
2546 `https://nominatim.openstreetmap.org/reverse?format=json&lat=${ lat } &lon=${ lon } ` ,
26- { headers : { 'User-Agent' : 'CineCircle/1.0' } }
47+ {
48+ headers : { 'User-Agent' : 'CineCircle/1.0' } ,
49+ signal : controller . signal
50+ }
2751 ) ;
2852
53+ clearTimeout ( timeoutId ) ;
54+
2955 if ( ! response . ok ) throw new Error ( 'Geocoding failed' ) ;
3056
3157 const data = await response . json ( ) ;
@@ -36,11 +62,38 @@ export async function reverseGeocode(
3662 const region = address . state || address . country ;
3763 const location = city && region ? `${ city } , ${ region } ` : `${ lat . toFixed ( 2 ) } , ${ lon . toFixed ( 2 ) } ` ;
3864
39- locationCache . set ( cacheKey , location ) ;
65+ // Cache the result
66+ locationCache . set ( cacheKey , { location, timestamp : Date . now ( ) } ) ;
4067 return location ;
4168 } catch ( error ) {
4269 // Fallback to coordinates if geocoding fails
43- return `${ lat . toFixed ( 2 ) } , ${ lon . toFixed ( 2 ) } ` ;
70+ const fallback = `${ lat . toFixed ( 2 ) } , ${ lon . toFixed ( 2 ) } ` ;
71+ // Cache fallback for a shorter period
72+ locationCache . set ( cacheKey , { location : fallback , timestamp : Date . now ( ) } ) ;
73+ return fallback ;
74+ }
75+ }
76+
77+ /**
78+ * Batch reverse geocode multiple coordinates efficiently
79+ * @param coordinates - Array of {lat, lon} objects
80+ * @returns Promise resolving to array of location strings
81+ */
82+ export async function batchReverseGeocode (
83+ coordinates : Array < { lat : number , lon : number } >
84+ ) : Promise < string [ ] > {
85+ // Process in parallel with concurrency limit to avoid overwhelming the API
86+ const BATCH_SIZE = 5 ;
87+ const results : string [ ] = [ ] ;
88+
89+ for ( let i = 0 ; i < coordinates . length ; i += BATCH_SIZE ) {
90+ const batch = coordinates . slice ( i , i + BATCH_SIZE ) ;
91+ const batchResults = await Promise . all (
92+ batch . map ( ( { lat, lon} ) => reverseGeocode ( lat , lon ) )
93+ ) ;
94+ results . push ( ...batchResults ) ;
4495 }
96+
97+ return results ;
4598}
4699
0 commit comments