/ src / google-maps-resolver.ts
google-maps-resolver.ts
  1  import { Client } from '@googlemaps/google-maps-services-js';
  2  import { PlaceInfo } from './types';
  3  
  4  export class GoogleMapsResolver {
  5    private client: Client;
  6    private apiKey: string;
  7  
  8    constructor(apiKey: string) {
  9      this.apiKey = apiKey;
 10      this.client = new Client({});
 11    }
 12  
 13    private extractPlaceIdFromUrl(url: string): string | null {
 14      // Extract place ID from Google Maps URL
 15      // The hex values in these URLs are not valid place IDs, so we'll return null
 16      // and fall back to text search
 17      return null;
 18    }
 19  
 20    private extractPlaceNameFromUrl(url: string): string | null {
 21      // Extract place name from URL path
 22      const match = url.match(/\/place\/([^\/]+)/);
 23      if (match) {
 24        return decodeURIComponent(match[1].replace(/\+/g, ' '));
 25      }
 26      return null;
 27    }
 28  
 29    async resolveCoordinates(place: PlaceInfo): Promise<PlaceInfo> {
 30      if (!place.url) {
 31        console.warn(`No URL provided for place: ${place.name}`);
 32        return place;
 33      }
 34  
 35      try {
 36        // First try to extract place ID from URL
 37        const placeId = this.extractPlaceIdFromUrl(place.url);
 38        
 39        if (placeId) {
 40          // Use Place Details API with place ID
 41          const response = await this.client.placeDetails({
 42            params: {
 43              place_id: placeId,
 44              key: this.apiKey,
 45              fields: ['geometry', 'name']
 46            }
 47          });
 48  
 49          if (response.data.result?.geometry?.location) {
 50            const location = response.data.result.geometry.location;
 51            return {
 52              ...place,
 53              latitude: location.lat,
 54              longitude: location.lng
 55            };
 56          }
 57        }
 58  
 59        // Fallback: use text search with place name from URL or provided name
 60        const searchQuery = place.name || this.extractPlaceNameFromUrl(place.url);
 61        
 62        if (searchQuery) {
 63          const response = await this.client.textSearch({
 64            params: {
 65              query: searchQuery,
 66              key: this.apiKey
 67            }
 68          });
 69  
 70          if (response.data.results && response.data.results.length > 0) {
 71            const location = response.data.results[0].geometry?.location;
 72            if (location) {
 73              return {
 74                ...place,
 75                latitude: location.lat,
 76                longitude: location.lng
 77              };
 78            }
 79          }
 80        }
 81  
 82        console.warn(`Could not resolve coordinates for: ${place.name || place.url}`);
 83        return place;
 84  
 85      } catch (error) {
 86        console.error(`Error resolving coordinates for ${place.name}:`, error);
 87        return place;
 88      }
 89    }
 90  
 91    async resolveAllCoordinates(places: PlaceInfo[]): Promise<PlaceInfo[]> {
 92      const results: PlaceInfo[] = [];
 93      
 94      for (const place of places) {
 95        const resolvedPlace = await this.resolveCoordinates(place);
 96        results.push(resolvedPlace);
 97        
 98        // Add small delay to avoid hitting rate limits
 99        await new Promise(resolve => setTimeout(resolve, 100));
100      }
101  
102      return results;
103    }
104  }