File size: 5,798 Bytes
7b6b271
23a9367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b6b271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23a9367
 
 
 
 
7b6b271
 
 
 
 
 
23a9367
 
 
 
 
 
 
 
7b6b271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""
Location Services Tool for Address Resolution and Geocoding.

This module provides comprehensive location services including:
    - Address to coordinates conversion (geocoding)
    - Support for various input formats (addresses, coordinates, place names)
    - Distance calculations between coordinates
    - Intelligent parsing of location strings

The tool integrates with Nominatim (OpenStreetMap) for geocoding services
and includes fallback mechanisms for reliable location resolution.

Supported input formats:
    - "Málaga, Spain" (city, country)
    - "123 Main St, New York, NY" (full address)
    - "36.7156,-4.4044" (decimal coordinates)
    - "Tarifa Beach" (landmark/place name)

Example:
    >>> tool = LocationTool()
    >>> result = tool.run(LocationInput(location_query="Lisbon, Portugal"))
    >>> print(f"Coordinates: {result.coordinates}")

Author: Surf Spot Finder Team
License: MIT
"""

from typing import Dict, Optional
from pydantic import BaseModel, Field
from geopy.geocoders import Nominatim
from geopy.distance import geodesic
import logging

# Import from utils - this will work when run as a proper package
try:
    from utils.location_parser import get_coordinates_from_location
except ImportError:
    # Fallback for development/testing
    import sys
    import os
    sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
    from utils.location_parser import get_coordinates_from_location

logger = logging.getLogger(__name__)


# ---------------------- Input / Output Schemas ----------------------

class LocationInput(BaseModel):
    """Input schema for location resolution.
    
    Attributes:
        location_query: Location string in any supported format.
    """
    location_query: str = Field(
        description="Location name, address, or description (e.g., 'Malaga, Spain')"
    )


class LocationOutput(BaseModel):
    """Output schema for location resolution results.
    
    Attributes:
        success: Whether location was successfully resolved.
        coordinates: Dict with 'lat' and 'lon' keys if successful.
        formatted_address: Standardized address string from geocoder.
        error: Error message if resolution failed.
    """
    success: bool
    coordinates: Optional[Dict[str, float]] = None
    formatted_address: str = ""
    error: str = ""


class DistanceInput(BaseModel):
    """Input schema for distance calculation"""
    origin_lat: float = Field(description="Origin latitude")
    origin_lon: float = Field(description="Origin longitude")
    dest_lat: float = Field(description="Destination latitude")
    dest_lon: float = Field(description="Destination longitude")


class DistanceOutput(BaseModel):
    """Output schema for distance calculation"""
    distance_km: float
    distance_miles: float


# ---------------------- Tools ----------------------

class LocationTool:
    """MCP-compatible tool for location services"""

    name = "resolve_location"
    description = "Convert a location name or address into geographic coordinates"

    def __init__(self):
        self.geolocator = Nominatim(user_agent="surf-spot-finder")

    def run(self, input_data: LocationInput) -> LocationOutput:
        """Execute location resolution"""

        try:
            # Try Google Maps first
            try:
                coordinates = get_coordinates_from_location(input_data.location_query)
                if coordinates:
                    if "lng" in coordinates:
                        coordinates["lon"] = coordinates["lng"]

                    return LocationOutput(
                        success=True,
                        coordinates=coordinates,
                        formatted_address=input_data.location_query
                    )
            except Exception:
                logger.info("Google Maps unavailable, using Nominatim")

            # Fallback to Nominatim
            location = self.geolocator.geocode(input_data.location_query, timeout=10)
            if location:
                return LocationOutput(
                    success=True,
                    coordinates={
                        "lat": location.latitude,
                        "lon": location.longitude,
                    },
                    formatted_address=location.address,
                )

            return LocationOutput(
                success=False,
                error=f"Could not find location: {input_data.location_query}",
            )

        except Exception as e:
            logger.error(f"Location tool error: {e}")
            return LocationOutput(success=False, error=str(e))


class DistanceTool:
    """MCP-compatible tool for distance calculations"""

    name = "calculate_distance"
    description = "Calculate distance between two geographic points"

    def run(self, input_data: DistanceInput) -> DistanceOutput:
        origin = (input_data.origin_lat, input_data.origin_lon)
        dest = (input_data.dest_lat, input_data.dest_lon)

        distance = geodesic(origin, dest)

        return DistanceOutput(
            distance_km=round(distance.kilometers, 2),
            distance_miles=round(distance.miles, 2),
        )


# ---------------------- Registration ----------------------

def create_location_tool():
    """Factory function to create the location tool"""
    tool = LocationTool()
    return {
        "name": tool.name,
        "description": tool.description,
        "input_schema": LocationInput.schema(),
        "function": tool.run,
    }


def create_distance_tool():
    """Factory function to create the distance tool"""
    tool = DistanceTool()
    return {
        "name": tool.name,
        "description": tool.description,
        "input_schema": DistanceInput.schema(),
        "function": tool.run,
    }