13
13
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
14
14
import os
15
15
from functools import wraps
16
- from typing import Any , Callable , List , Optional , Tuple , Union
16
+ from typing import Any , Callable , List , Optional , Union
17
17
18
18
from camel .toolkits .base import BaseToolkit
19
19
from camel .toolkits .openai_function import OpenAIFunction
20
+ from camel .utils import dependencies_required
20
21
21
22
22
23
def handle_googlemaps_exceptions (
@@ -74,76 +75,65 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
74
75
return wrapper
75
76
76
77
78
+ def _format_offset_to_natural_language (offset : int ) -> str :
79
+ r"""Converts a time offset in seconds to a more natural language
80
+ description using hours as the unit, with decimal places to represent
81
+ minutes and seconds.
82
+
83
+ Args:
84
+ offset (int): The time offset in seconds. Can be positive,
85
+ negative, or zero.
86
+
87
+ Returns:
88
+ str: A string representing the offset in hours, such as
89
+ "+2.50 hours" or "-3.75 hours".
90
+ """
91
+ # Convert the offset to hours as a float
92
+ hours = offset / 3600.0
93
+ hours_str = f"{ hours :+.2f} hour{ 's' if abs (hours ) != 1 else '' } "
94
+ return hours_str
95
+
96
+
77
97
class GoogleMapsToolkit (BaseToolkit ):
78
98
r"""A class representing a toolkit for interacting with GoogleMaps API.
79
-
80
99
This class provides methods for validating addresses, retrieving elevation,
81
100
and fetching timezone information using the Google Maps API.
82
101
"""
83
102
84
- def _import_googlemaps_or_raise (self ) -> Any :
85
- r"""Attempts to import the `googlemaps` library and returns it.
103
+ @dependencies_required ('googlemaps' )
104
+ def __init__ (self ) -> None :
105
+ import googlemaps
86
106
87
- Returns:
88
- module: The `googlemaps` module if successfully imported.
89
-
90
- Raises:
91
- ImportError: If the `googlemaps` library is not installed, this
92
- error is raised with a message instructing how to install the
93
- library using pip.
94
- """
95
- try :
96
- import googlemaps
97
-
98
- return googlemaps
99
- except ImportError :
100
- raise ImportError (
101
- "Please install `googlemaps` first. You can install "
102
- "it by running `pip install googlemaps`."
103
- )
104
-
105
- def _get_googlemaps_api_key (self ) -> str :
106
- r"""Retrieve the Google Maps API key from environment variables.
107
-
108
- Returns:
109
- str: The Google Maps API key.
110
-
111
- Raises:
112
- ValueError: If the API key is not found in the environment
113
- variables.
114
- """
115
- # Get `GOOGLEMAPS_API_KEY` here:
116
- # https://console.cloud.google.com/apis/credentials
117
- GOOGLEMAPS_API_KEY = os .environ .get ('GOOGLEMAPS_API_KEY' )
118
- if not GOOGLEMAPS_API_KEY :
107
+ api_key = os .environ .get ('GOOGLE_API_KEY' )
108
+ if not api_key :
119
109
raise ValueError (
120
- "`GOOGLEMAPS_API_KEY` not found in environment "
121
- "variables. `GOOGLEMAPS_API_KEY` API keys are "
122
- "generated in the `Credentials` page of the "
123
- "`APIs & Services` tab of "
110
+ "`GOOGLE_API_KEY` not found in environment variables. "
111
+ "`GOOGLE_API_KEY` API keys are generated in the `Credentials` "
112
+ "page of the `APIs & Services` tab of "
124
113
"https://console.cloud.google.com/apis/credentials."
125
114
)
126
- return GOOGLEMAPS_API_KEY
127
115
116
+ self .gmaps = googlemaps .Client (key = api_key )
117
+
118
+ @handle_googlemaps_exceptions
128
119
def get_address_description (
129
120
self ,
130
121
address : Union [str , List [str ]],
131
122
region_code : Optional [str ] = None ,
132
123
locality : Optional [str ] = None ,
133
124
) -> str :
134
125
r"""Validates an address via Google Maps API, returns a descriptive
135
- summary.
136
-
137
- Validates an address using Google Maps API, returning a summary that
138
- includes information on address completion, formatted address, location
139
- coordinates, and metadata types that are true for the given address.
126
+ summary. Validates an address using Google Maps API, returning a
127
+ summary that includes information on address completion, formatted
128
+ address, location coordinates, and metadata types that are true for
129
+ the given address.
140
130
141
131
Args:
142
132
address (Union[str, List[str]]): The address or components to
143
133
validate. Can be a single string or a list representing
144
134
different parts.
145
135
region_code (str, optional): Country code for regional restriction,
146
- helps narrowing down results. (default: :obj:`None`)
136
+ helps narrow down results. (default: :obj:`None`)
147
137
locality (str, optional): Restricts validation to a specific
148
138
locality, e.g., "Mountain View". (default: :obj:`None`)
149
139
@@ -157,100 +147,78 @@ def get_address_description(
157
147
ImportError: If the `googlemaps` library is not installed.
158
148
Exception: For unexpected errors during the address validation.
159
149
"""
160
- googlemaps = self ._import_googlemaps_or_raise ()
161
- google_maps_api_key = self ._get_googlemaps_api_key ()
162
- try :
163
- gmaps = googlemaps .Client (key = google_maps_api_key )
164
- except Exception as e :
165
- return f"Error: { e !s} "
166
-
167
- try :
168
- addressvalidation_result = gmaps .addressvalidation (
169
- [address ],
170
- regionCode = region_code ,
171
- locality = locality ,
172
- enableUspsCass = False ,
173
- ) # Always False as per requirements
174
-
175
- # Check if the result contains an error
176
- if 'error' in addressvalidation_result :
177
- error_info = addressvalidation_result ['error' ]
178
- error_message = error_info .get (
179
- 'message' , 'An unknown error occurred'
180
- )
181
- error_status = error_info .get ('status' , 'UNKNOWN_STATUS' )
182
- error_code = error_info .get ('code' , 'UNKNOWN_CODE' )
183
- return (
184
- f"Address validation failed with error: { error_message } "
185
- f"Status: { error_status } , Code: { error_code } "
186
- )
187
-
188
- # Assuming the successful response structure
189
- # includes a 'result' key
190
- result = addressvalidation_result ['result' ]
191
- verdict = result .get ('verdict' , {})
192
- address_info = result .get ('address' , {})
193
- geocode = result .get ('geocode' , {})
194
- metadata = result .get ('metadata' , {})
195
-
196
- # Construct the descriptive string
197
- address_complete = (
198
- "Yes" if verdict .get ('addressComplete' , False ) else "No"
199
- )
200
- formatted_address = address_info .get (
201
- 'formattedAddress' , 'Not available'
150
+ addressvalidation_result = self .gmaps .addressvalidation (
151
+ [address ],
152
+ regionCode = region_code ,
153
+ locality = locality ,
154
+ enableUspsCass = False ,
155
+ ) # Always False as per requirements
156
+
157
+ # Check if the result contains an error
158
+ if 'error' in addressvalidation_result :
159
+ error_info = addressvalidation_result ['error' ]
160
+ error_message = error_info .get (
161
+ 'message' , 'An unknown error occurred'
202
162
)
203
- location = geocode .get ('location' , {})
204
- latitude = location .get ('latitude' , 'Not available' )
205
- longitude = location .get ('longitude' , 'Not available' )
206
- true_metadata_types = [
207
- key for key , value in metadata .items () if value
208
- ]
209
- true_metadata_types_str = (
210
- ', ' .join (true_metadata_types )
211
- if true_metadata_types
212
- else 'None'
163
+ error_status = error_info .get ('status' , 'UNKNOWN_STATUS' )
164
+ error_code = error_info .get ('code' , 'UNKNOWN_CODE' )
165
+ return (
166
+ f"Address validation failed with error: { error_message } "
167
+ f"Status: { error_status } , Code: { error_code } "
213
168
)
214
169
215
- description = (
216
- f"Address completion status: { address_complete } . "
217
- f"Formatted address: { formatted_address } . "
218
- f"Location (latitude, longitude): ({ latitude } , { longitude } ). "
219
- f"Metadata indicating true types: { true_metadata_types_str } ."
220
- )
170
+ # Assuming the successful response structure
171
+ # includes a 'result' key
172
+ result = addressvalidation_result ['result' ]
173
+ verdict = result .get ('verdict' , {})
174
+ address_info = result .get ('address' , {})
175
+ geocode = result .get ('geocode' , {})
176
+ metadata = result .get ('metadata' , {})
177
+
178
+ # Construct the descriptive string
179
+ address_complete = (
180
+ "Yes" if verdict .get ('addressComplete' , False ) else "No"
181
+ )
182
+ formatted_address = address_info .get (
183
+ 'formattedAddress' , 'Not available'
184
+ )
185
+ location = geocode .get ('location' , {})
186
+ latitude = location .get ('latitude' , 'Not available' )
187
+ longitude = location .get ('longitude' , 'Not available' )
188
+ true_metadata_types = [key for key , value in metadata .items () if value ]
189
+ true_metadata_types_str = (
190
+ ', ' .join (true_metadata_types ) if true_metadata_types else 'None'
191
+ )
221
192
222
- return description
223
- except Exception as e :
224
- return f"An unexpected error occurred: { e !s} "
193
+ description = (
194
+ f"Address completion status: { address_complete } . "
195
+ f"Formatted address: { formatted_address } . "
196
+ f"Location (latitude, longitude): ({ latitude } , { longitude } ). "
197
+ f"Metadata indicating true types: { true_metadata_types_str } ."
198
+ )
199
+
200
+ return description
225
201
226
202
@handle_googlemaps_exceptions
227
- def get_elevation (self , lat_lng : Tuple ) -> str :
203
+ def get_elevation (self , lat : float , lng : float ) -> str :
228
204
r"""Retrieves elevation data for a given latitude and longitude.
229
-
230
205
Uses the Google Maps API to fetch elevation data for the specified
231
206
latitude and longitude. It handles exceptions gracefully and returns a
232
207
description of the elevation, including its value in meters and the
233
208
data resolution.
234
209
235
210
Args:
236
- lat_lng (Tuple[ float, float] ): The latitude and longitude for
237
- which to retrieve elevation data .
211
+ lat ( float): The latitude of the location to query.
212
+ lng (float): The longitude of the location to query .
238
213
239
214
Returns:
240
215
str: A description of the elevation at the specified location(s),
241
216
including the elevation in meters and the data resolution. If
242
217
elevation data is not available, a message indicating this is
243
218
returned.
244
219
"""
245
- googlemaps = self ._import_googlemaps_or_raise ()
246
- google_maps_api_key = self ._get_googlemaps_api_key ()
247
- try :
248
- gmaps = googlemaps .Client (key = google_maps_api_key )
249
- except Exception as e :
250
- return f"Error: { e !s} "
251
-
252
220
# Assuming gmaps is a configured Google Maps client instance
253
- elevation_result = gmaps .elevation (lat_lng )
221
+ elevation_result = self . gmaps .elevation (( lat , lng ) )
254
222
255
223
# Extract the elevation data from the first
256
224
# (and presumably only) result
@@ -273,52 +241,26 @@ def get_elevation(self, lat_lng: Tuple) -> str:
273
241
274
242
return description
275
243
276
- def _format_offset_to_natural_language (self , offset : int ) -> str :
277
- r"""Converts a time offset in seconds to a more natural language
278
- description using hours as the unit, with decimal places to represent
279
- minutes and seconds.
280
-
281
- Args:
282
- offset (int): The time offset in seconds. Can be positive,
283
- negative, or zero.
284
-
285
- Returns:
286
- str: A string representing the offset in hours, such as
287
- "+2.50 hours" or "-3.75 hours".
288
- """
289
- # Convert the offset to hours as a float
290
- hours = offset / 3600.0
291
- hours_str = f"{ hours :+.2f} hour{ 's' if abs (hours ) != 1 else '' } "
292
- return hours_str
293
-
294
244
@handle_googlemaps_exceptions
295
- def get_timezone (self , lat_lng : Tuple ) -> str :
245
+ def get_timezone (self , lat : float , lng : float ) -> str :
296
246
r"""Retrieves timezone information for a given latitude and longitude.
297
-
298
247
This function uses the Google Maps Timezone API to fetch timezone
299
248
data for the specified latitude and longitude. It returns a natural
300
249
language description of the timezone, including the timezone ID, name,
301
250
standard time offset, daylight saving time offset, and the total
302
251
offset from Coordinated Universal Time (UTC).
303
252
304
253
Args:
305
- lat_lng (Tuple[ float, float] ): The latitude and longitude for
306
- which to retrieve elevation data .
254
+ lat ( float): The latitude of the location to query.
255
+ lng (float): The longitude of the location to query .
307
256
308
257
Returns:
309
258
str: A descriptive string of the timezone information,
310
- including the timezone ID and name, standard time offset,
311
- daylight saving time offset, and total offset from UTC.
259
+ including the timezone ID and name, standard time offset,
260
+ daylight saving time offset, and total offset from UTC.
312
261
"""
313
- googlemaps = self ._import_googlemaps_or_raise ()
314
- google_maps_api_key = self ._get_googlemaps_api_key ()
315
- try :
316
- gmaps = googlemaps .Client (key = google_maps_api_key )
317
- except Exception as e :
318
- return f"Error: { e !s} "
319
-
320
262
# Get timezone information
321
- timezone_dict = gmaps .timezone (lat_lng )
263
+ timezone_dict = self . gmaps .timezone (( lat , lng ) )
322
264
323
265
# Extract necessary information
324
266
dst_offset = timezone_dict [
@@ -330,10 +272,10 @@ def get_timezone(self, lat_lng: Tuple) -> str:
330
272
timezone_id = timezone_dict ['timeZoneId' ]
331
273
timezone_name = timezone_dict ['timeZoneName' ]
332
274
333
- raw_offset_str = self . _format_offset_to_natural_language (raw_offset )
334
- dst_offset_str = self . _format_offset_to_natural_language (dst_offset )
275
+ raw_offset_str = _format_offset_to_natural_language (raw_offset )
276
+ dst_offset_str = _format_offset_to_natural_language (dst_offset )
335
277
total_offset_seconds = dst_offset + raw_offset
336
- total_offset_str = self . _format_offset_to_natural_language (
278
+ total_offset_str = _format_offset_to_natural_language (
337
279
total_offset_seconds
338
280
)
339
281
@@ -343,9 +285,8 @@ def get_timezone(self, lat_lng: Tuple) -> str:
343
285
f"The standard time offset is { raw_offset_str } . "
344
286
f"Daylight Saving Time offset is { dst_offset_str } . "
345
287
f"The total offset from Coordinated Universal Time (UTC) is "
346
- f"{ total_offset_str } , including any "
347
- "Daylight Saving Time adjustment "
348
- f"if applicable. "
288
+ f"{ total_offset_str } , including any Daylight Saving Time "
289
+ f"adjustment if applicable. "
349
290
)
350
291
351
292
return description
0 commit comments