Skip to main content
Glama
places.ts12.4 kB
import { Client, PlaceInputType, TravelMode, LatLngLiteral, Language, } from "@googlemaps/google-maps-services-js"; import { ServiceResponse, Location, LocationInput, PlaceDetails, DirectionsResult, DistanceMatrixResult, ElevationResult, } from "../types"; import { handleError, validateCoordinates, validateRequiredString, } from "../utils/error-handling"; import config from "../config/environment"; export class PlacesSearcher { private client: Client; constructor() { this.client = new Client({}); } async searchNearby(params: { center: LocationInput; keyword?: string; radius?: number; openNow?: boolean; minRating?: number; }): Promise<ServiceResponse<PlaceDetails[]>> { try { let location: Location; if (params.center.isCoordinates) { const [lat, lng] = params.center.value.split(",").map(Number); validateCoordinates(lat, lng); location = { lat, lng }; } else { const geocodeResult = await this.geocode(params.center.value); if (!geocodeResult.success || !geocodeResult.data) { throw new Error("Failed to geocode center location"); } location = geocodeResult.data; } const response = await this.client.placesNearby({ params: { key: config.googleMapsApiKey, location: location, radius: params.radius || 1000, keyword: params.keyword, opennow: params.openNow, language: config.defaultLanguage, }, }); let places = response.data.results.map((place) => { if (!place.geometry || !place.place_id || !place.name) { throw new Error("Required place data is missing"); } return { placeId: place.place_id, name: place.name, location: place.geometry.location, rating: place.rating, userRatingsTotal: place.user_ratings_total, types: place.types, vicinity: place.vicinity, }; }); if (params.minRating) { places = places.filter( (place) => (place.rating || 0) >= (params.minRating || 0) ); } return { success: true, data: places, }; } catch (error) { return handleError(error); } } async getPlaceDetails( placeId: string ): Promise<ServiceResponse<PlaceDetails>> { try { validateRequiredString(placeId, "Place ID"); const response = await this.client.placeDetails({ params: { key: config.googleMapsApiKey, place_id: placeId, language: config.defaultLanguage as Language, fields: [ "name", "formatted_address", "geometry", "googleMapsLinks", "rating", "reviews", "reviewSummary", "user_ratings_total", "opening_hours", "photos", "price_level", "types", "website", "formatted_phone_number", ], }, }); const place = response.data.result; // First check if the API call was successful if (response.data.status !== "OK") { throw new Error( `Google Places API error: ${response.data.status} - ${response.data.error_message || "Unknown error"}` ); } // Check if we have a result if (!place) { throw new Error( "No place data returned from Google Places API" ); } // Check for required fields with detailed error message if (!place.place_id || !place.name) { throw new Error( `Missing required place data - place_id: ${place.place_id || "undefined"}, name: ${place.name || "undefined"}, API status: ${response.data.status}` ); } return { success: true, data: { placeId: place.place_id, name: place.name, formattedAddress: place.formatted_address, location: place.geometry?.location, rating: place.rating, userRatingsTotal: place.user_ratings_total, openingHours: place.opening_hours ? { openNow: place.opening_hours.open_now, periods: place.opening_hours.periods?.map( (period) => ({ open: { day: period.open.day, time: period.open.time || "", }, close: period.close ? { day: period.close.day, time: period.close.time || "", } : { day: 0, time: "" }, }) ), } : undefined, photos: place.photos?.map((photo) => ({ photoReference: photo.photo_reference, height: photo.height, width: photo.width, })), priceLevel: place.price_level, types: place.types, website: place.website, phoneNumber: place.formatted_phone_number, }, }; } catch (error) { return handleError(error); } } async geocode(address: string): Promise<ServiceResponse<Location>> { try { validateRequiredString(address, "Address"); const response = await this.client.geocode({ params: { key: config.googleMapsApiKey, address: address, language: config.defaultLanguage as Language, region: config.defaultRegion, }, }); if (response.data.results.length === 0) { throw new Error("No results found for the given address"); } const location = response.data.results[0].geometry.location; return { success: true, data: { lat: location.lat, lng: location.lng, address: response.data.results[0].formatted_address, placeId: response.data.results[0].place_id, }, }; } catch (error) { return handleError(error); } } async reverseGeocode( latitude: number, longitude: number ): Promise<ServiceResponse<Location>> { try { validateCoordinates(latitude, longitude); const response = await this.client.reverseGeocode({ params: { key: config.googleMapsApiKey, latlng: { lat: latitude, lng: longitude }, language: config.defaultLanguage as Language, }, }); if (response.data.results.length === 0) { throw new Error("No results found for the given coordinates"); } const result = response.data.results[0]; return { success: true, data: { lat: latitude, lng: longitude, address: result.formatted_address, placeId: result.place_id, }, }; } catch (error) { return handleError(error); } } async calculateDistanceMatrix( origins: string[], destinations: string[], mode: TravelMode = TravelMode.driving ): Promise<ServiceResponse<DistanceMatrixResult>> { try { const response = await this.client.distancematrix({ params: { key: config.googleMapsApiKey, origins, destinations, mode, language: config.defaultLanguage as Language, }, }); return { success: true, data: { originAddresses: response.data.origin_addresses, destinationAddresses: response.data.destination_addresses, rows: response.data.rows, }, }; } catch (error) { return handleError(error); } } async getDirections( origin: string, destination: string, mode: TravelMode = TravelMode.driving ): Promise<ServiceResponse<DirectionsResult>> { try { validateRequiredString(origin, "Origin"); validateRequiredString(destination, "Destination"); const response = await this.client.directions({ params: { key: config.googleMapsApiKey, origin, destination, mode, language: config.defaultLanguage as Language, }, }); if (response.data.routes.length === 0) { throw new Error("No route found"); } return { success: true, data: { routes: response.data.routes.map((route) => ({ summary: route.summary, legs: route.legs.map((leg) => ({ distance: leg.distance, duration: leg.duration, startAddress: leg.start_address, endAddress: leg.end_address, steps: leg.steps.map((step) => ({ distance: step.distance, duration: step.duration, instructions: step.html_instructions, travelMode: step.travel_mode, })), })), })), }, }; } catch (error) { return handleError(error); } } async getElevation( locations: Array<{ latitude: number; longitude: number }> ): Promise<ServiceResponse<ElevationResult[]>> { try { const locations_array = locations.map((loc) => { validateCoordinates(loc.latitude, loc.longitude); return { lat: loc.latitude, lng: loc.longitude }; }); const response = await this.client.elevation({ params: { key: config.googleMapsApiKey, locations: locations_array, }, }); return { success: true, data: response.data.results.map((result) => ({ elevation: result.elevation, location: result.location, resolution: result.resolution, })), }; } catch (error) { return handleError(error); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BACH-AI-Tools/MCP-Google-Maps'

If you have feedback or need assistance with the MCP directory API, please join our Discord server