Source code for langchain_google_community.geocoding
"""Wrapper and Tool for the Google Maps Geocoding API. """importosfromtypingimportAny,Dict,List,Literal,Optional,Tuple,Typeimportaiohttpimportrequestsfromlangchain_core.callbacksimport(AsyncCallbackManagerForToolRun,CallbackManagerForToolRun,)fromlangchain_core.toolsimportBaseToolfromlangchain_core.utilsimportget_from_dict_or_envfrompydanticimportBaseModel,ConfigDict,Field,SecretStr,model_validatorGOOGLE_MAPS_API_URL="https://maps.googleapis.com/maps/api/geocode/json"
[docs]classGoogleGeocodingAPIWrapper(BaseModel):"""Wrapper for Google Maps Geocoding API."""# Requiredgoogle_api_key:SecretStr# Configurationinclude_bounds:bool=Field(default=True)include_navigation:bool=Field(default=True)include_metadata:bool=Field(default=True)include_address_components:bool=Field(default=True)# Default parameterslanguage:Optional[str]=Field(default="en")region:Optional[str]=Field(default="us")max_retries:int=Field(default=2)timeout:int=Field(default=30)model_config=ConfigDict(extra="forbid")def_add_components(self,cleaned:Dict,result:Dict)->None:"""Add address components if configured."""ifnotself.include_address_components:returnaddress_types=["street_number","route","locality","country","postal_code",]forcomponentinresult.get("address_components",[]):types=component.get("types",[])name=component.get("long_name")fortype_intypes:iftype_inaddress_types:cleaned["address"][type_]=nameeliftype_=="administrative_area_level_1":state_name=component.get("short_name")cleaned["address"]["state"]=state_namedef_add_geometry(self,cleaned:Dict,result:Dict)->None:"""Add geometry details if configured."""geometry=result.get("geometry",{})ifself.include_bounds:ifgeometry.get("viewport"):cleaned["geometry"]["viewport"]=geometry["viewport"]ifgeometry.get("bounds"):cleaned["geometry"]["bounds"]=geometry["bounds"]def_add_metadata(self,cleaned:Dict,result:Dict)->None:"""Add metadata if configured."""ifself.include_metadata:geometry=result.get("geometry",{})cleaned["metadata"]={"place_id":result.get("place_id"),"types":result.get("types",[]),"location_type":(geometry.get("location_type")),}def_add_navigation(self,cleaned:Dict,result:Dict)->None:"""Add navigation points if configured."""ifself.include_navigationandresult.get("navigation_points"):cleaned["navigation"]=[{"location":point["location"],"restrictions":point.get("restricted_travel_modes",[]),}forpointinresult.get("navigation_points",[])]
[docs]defclean_results(self,results:List[Dict])->List[Dict]:"""Clean and format results."""cleaned_results=[]forresultinresults:ifnotresult:continuecleaned={"address":{"full":result.get("formatted_address","")},"geometry":{"location":result.get("geometry",{}).get("location",{})},}# Add optional componentsself._add_components(cleaned,result)self._add_geometry(cleaned,result)self._add_metadata(cleaned,result)self._add_navigation(cleaned,result)cleaned_results.append(cleaned)returncleaned_results
[docs]defresults(self,query:str,language:Optional[str]=None,region:Optional[str]=None,max_results:int=10,)->Dict[str,Any]:"""Process geocoding request and return comprehensive results. This method handles both single and batch geocoding requests, returning detailed location information with optional components. Args: query: Location(s) to geocode. Examples: - "Eiffel Tower" - "Times Square, Central Park" language: Optional language code for results (e.g., "en", "fr", "ja") region: Optional region bias (e.g., "us", "fr", "jp") max_results: Maximum number of results to return (default: 10) Returns: Dict containing: status: Status of the request ("OK" or error status) total_results: Number of locations found results: List of dictionaries containing location data: address: { full: Complete formatted address street_number: Building number (if available) route: Street name locality: City/Town state: State/Province country: Country postal_code: Postal/ZIP code } geometry: { location: {lat, lng} coordinates viewport: Recommended viewport bounds: Geographic bounds (if available) } metadata: { place_id: Unique Google place identifier types: Categories (e.g., ["establishment", "point_of_interest"]) location_type:(e.g., "ROOFTOP", "GEOMETRIC_CENTER") } navigation: List of navigation points with: location: {latitude, longitude} restrictions: Travel mode restrictions query_info: { original_query: Input query language: Language used region: Region bias used } Example Response: { "status": "OK", "total_results": 2, "results": [ { "address": { "full": "Street, Country..", "route": "Avenue Gustave Eiffel", "locality": "Paris", "country": "France" }, "geometry": { "location": {"lat": 48.8584, "lng": 2.2945} } }, ... ], "query_info": { "original_query": "Eiffel Tower, Big Ben", "language": "en", "region": "us" } } Raises: ValueError: If query is empty or invalid Exception: For API errors or connection issues """try:ifnotquery.strip():return{"status":"ERROR","message":"Empty query provided","results":[],}# Handle batch queriesqueries=[q.strip()forqinquery.split(",")ifq.strip()]iflen(queries)>max_results:queries=queries[:max_results]raw_results=self.raw_results(query=query,language=language,region=region)ifraw_results.get("status")!="OK":return{"status":raw_results.get("status","ERROR"),"message":raw_results.get("error_message","No results found"),"results":[],}return{"status":"OK","total_results":len(raw_results.get("results",[])),"results":self.clean_results(raw_results.get("results",[])),"query_info":{"original_query":query,"language":languageorself.language,"region":regionorself.region,},}exceptExceptionase:return{"status":"ERROR","message":str(e),"results":[],"query_info":{"original_query":query,"language":languageorself.language,"region":regionorself.region,},}
[docs]defbatch_geocode(self,locations:List[str],language:Optional[str]=None,region:Optional[str]=None,components:Optional[Dict[str,str]]=None,)->Dict[str,Any]:"""Process multiple locations in a single structured request. Efficiently handles multiple location queries, processing them as a batch while maintaining individual result integrity. Args: locations: List of location strings to geocode Examples: ["Eiffel Tower", "Times Square", "ζ±δΊ¬γΉγ«γ€γγͺγΌ"] language: Optional language code for results region: Optional region bias components: Optional filters (e.g., {"country": "US"}) Returns: Dict containing: status: Overall batch status total_results: Number of successful geocoding results results: List of location data (same structure as single results) errors: List of any errors encountered: query: The location query that failed status: Error status code message: Detailed error message query_info: { total_queries: Total locations processed successful: Number of successful queries failed: Number of failed queries language: Language used region: Region bias used } Example: batch_geocode( locations=["Eiffel Tower", "Big Ben"], language="en", components={"country": "FR"} ) """ifnotlocations:return{"status":"ERROR","message":"No locations provided","results":[],}results=[]errors=[]forlocationinlocations:try:result=self.raw_results(query=location,language=language,region=region,components=components,)ifresult.get("status")=="OK":results.append(result)else:errors.append({"query":location,"status":result.get("status"),"message":result.get("error_message"),})exceptExceptionase:errors.append({"query":location,"status":"ERROR","message":str(e)})return{"status":"OK"ifresultselse"ERROR","total_results":len(results),"results":self.clean_results([r.get("results",[])[0]forrinresultsifr.get("results")]),"errors":errors,"query_info":{"total_queries":len(locations),"successful":len(results),"failed":len(errors),"language":languageorself.language,"region":regionorself.region,},}
[docs]defraw_results(self,query:str,language:Optional[str]=None,region:Optional[str]=None,components:Optional[Dict[str,str]]=None,)->Dict:"""Get raw results with improved error handling."""try:# Input validationifnotquery.strip():return{"status":"ERROR","error_message":"Empty query provided","results":[],}# Build parametersparams={"address":query.strip(),"key":self.google_api_key.get_secret_value(),"language":languageorself.language,"region":regionorself.region,}# Add component filtering if providedifcomponents:params["components"]="|".join(f"{k}:{v}"fork,vincomponents.items())forattemptinrange(self.max_retries):try:response=requests.get(GOOGLE_MAPS_API_URL,params=params,timeout=self.timeout)response.raise_for_status()data=response.json()ifdata.get("status")=="OK":returndataelifattempt==self.max_retries-1:return{"status":data.get("status"),"error_message":self._get_error_message(data.get("status")),"results":[],}exceptrequests.exceptions.RequestExceptionase:ifattempt==self.max_retries-1:return{"status":"REQUEST_ERROR","error_message":f"Request failed: {str(e)}","results":[],}exceptExceptionase:return{"status":"ERROR","error_message":f"Processing error: {str(e)}","results":[],}# Add explicit return for the case when all retries failreturn{"status":"ERROR","error_message":"All retry attempts failed","results":[],}
def_get_error_message(self,status:str)->str:"""Get detailed error message based on status code."""error_messages={"ZERO_RESULTS":"No results found for this query","OVER_DAILY_LIMIT":"API key quota exceeded","OVER_QUERY_LIMIT":"Query limit exceeded","REQUEST_DENIED":"Request was denied, check API key","INVALID_REQUEST":"Invalid request parameters","MAX_ELEMENTS_EXCEEDED":"Too many locations in request","UNKNOWN_ERROR":"Server error, please try again",}returnerror_messages.get(status,f"API Error: {status}")@model_validator(mode="before")@classmethoddefvalidate_environment(cls,values:Dict)->Any:"""Validate that api key exists in environment."""google_api_key=get_from_dict_or_env(values,"google_api_key","GOOGLE_MAPS_API_KEY")values["google_api_key"]=google_api_keyreturnvalues
[docs]asyncdefgeocode_async(self,query:str,language:Optional[str]=None,region:Optional[str]=None,)->Dict[str,Any]:"""Run query through Google Maps Geocoding API asynchronously. Args: query: The location(s) to geocode language: Optional language code for results region: Optional region bias Returns: Dict containing: status: Status of the request results: List of geocoding results query_info: Metadata about the request """try:params:Dict[str,str]={"address":query.strip(),"key":self.google_api_key.get_secret_value(),"language":languageorself.languageor"","region":regionorself.regionor"",}timeout_obj=aiohttp.ClientTimeout(total=self.timeout)asyncwithaiohttp.ClientSession()assession:asyncwithsession.get(GOOGLE_MAPS_API_URL,params=params,timeout=timeout_obj)asresponse:data=awaitresponse.json()ifdata.get("status")=="OK":returnself.results(query,language,region)return{"status":data.get("status","ERROR"),"error_message":data.get("error_message","Request failed"),"results":[],}exceptExceptionase:return{"status":"ERROR","error_message":f"Async request failed: {str(e)}","results":[],}
[docs]classGoogleGeocodeInput(BaseModel):"""Input for the Geocoding tool."""query:str=Field(description="Locations for query.")
[docs]classGoogleGeocodingTool(BaseTool):"""Tool that queries the Google Maps Geocoding API for batch location lookups. Instantiate: .. code-block:: python from tools.geocoding_wrapper import GoogleGeocodingTool tool = GoogleGeocodingTool( max_results=5, include_bounds=True, include_navigation=True, include_metadata=True, language="en" ) Invoke directly: .. code-block:: python result = tool.invoke({ "query": "Eiffel Tower, Empire State Building" }) Invoke with agent: .. code-block:: python agent.invoke({ "input": "Find coordinates of Times Square and Central Park" }) Returns: Tuple containing: - List of location data with coordinates and addresses - Raw response data with query information """name:str="google_geocode"description:str=("A geocoding tool for multiple locations. ""Input: comma-separated locations. ""Returns: location data.")args_schema:Type[BaseModel]=GoogleGeocodeInput# Configurationmax_results:int=5include_bounds:bool=Field(default=True)include_navigation:bool=Field(default=True)include_metadata:bool=Field(default=True)language:Optional[str]=Field(default="en")region:Optional[str]=Field(default=None)api_wrapper:GoogleGeocodingAPIWrapper=Field(default_factory=lambda:GoogleGeocodingAPIWrapper(google_api_key=SecretStr(os.getenv("GOOGLE_MAPS_API_KEY",""))))response_format:Literal["content_and_artifact"]="content_and_artifact"def_run(self,query:str,run_manager:Optional[CallbackManagerForToolRun]=None,)->Tuple[List[Dict[str,Any]],Dict[str,Any]]:"""Use the tool."""try:locations=[loc.strip()forlocinquery.split(",")ifloc.strip()]iflen(locations)>self.max_results:locations=locations[:self.max_results]iflen(locations)>1:results=self.api_wrapper.batch_geocode(locations=locations,language=self.language,region=self.region)else:raw_results=self.api_wrapper.raw_results(query=locations[0],language=self.language,region=self.region)results={"status":raw_results.get("status"),"total_results":1,"results":self.api_wrapper.clean_results(raw_results.get("results",[])),"query_info":{"total_queries":1,"successful":1ifraw_results.get("status")=="OK"else0,"failed":0ifraw_results.get("status")=="OK"else1,},}ifresults.get("status")!="OK":return[],{"error":results.get("error_message","Geocoding failed")}returnresults["results"],resultsexceptExceptionase:return[],{"error":str(e)}asyncdef_arun(self,query:str,run_manager:Optional[AsyncCallbackManagerForToolRun]=None,)->Tuple[List[Dict[str,Any]],Dict[str,Any]]:"""Use the tool asynchronously."""try:result=awaitself.api_wrapper.geocode_async(query=query,language=self.language,region=self.region)ifresult.get("status")!="OK":return[],{"error":result.get("error_message","Geocoding failed")}returnresult.get("results",[]),resultexceptExceptionase:return[],{"error":str(e)}