Using names instead of geoloc
Browse files- app.py +76 -127
- camptocamp_api.py +34 -63
app.py
CHANGED
@@ -1,161 +1,110 @@
|
|
1 |
import gradio as gr
|
2 |
from camptocamp_api import CamptocampAPI
|
3 |
-
from typing import
|
4 |
|
5 |
-
|
6 |
-
# Instantiate the Camptocamp API wrapper
|
7 |
c2c = CamptocampAPI(language="en")
|
8 |
|
9 |
-
|
10 |
-
|
11 |
-
west: float,
|
12 |
-
south: float,
|
13 |
-
east: float,
|
14 |
-
north: float,
|
15 |
start_date: Optional[str] = None,
|
16 |
end_date: Optional[str] = None,
|
17 |
activity: Optional[str] = None,
|
18 |
limit: int = 10
|
19 |
) -> dict:
|
20 |
"""
|
21 |
-
Get recent outings
|
22 |
-
|
23 |
-
Args:
|
24 |
-
west: Western longitude of the bounding box.
|
25 |
-
south: Southern latitude of the bounding box.
|
26 |
-
east: Eastern longitude of the bounding box.
|
27 |
-
north: Northern latitude of the bounding box.
|
28 |
-
start_date: Start of the date range (YYYY-MM-DD).
|
29 |
-
end_date: End of the date range (YYYY-MM-DD).
|
30 |
-
activity: Optional activity filter (e.g. "hiking", "rock_climbing").
|
31 |
-
limit: Number of results to return.
|
32 |
-
|
33 |
-
Returns:
|
34 |
-
JSON dictionary of matching outings.
|
35 |
"""
|
36 |
-
bbox = (
|
|
|
|
|
37 |
date_range = (start_date, end_date) if start_date and end_date else None
|
38 |
return c2c.get_recent_outings(bbox, date_range, activity, limit)
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
west: float,
|
43 |
-
south: float,
|
44 |
-
east: float,
|
45 |
-
north: float,
|
46 |
activity: str,
|
47 |
limit: int = 10
|
48 |
) -> dict:
|
49 |
"""
|
50 |
-
Search for
|
51 |
-
|
52 |
-
Args:
|
53 |
-
west: Western longitude of the bounding box.
|
54 |
-
south: Southern latitude of the bounding box.
|
55 |
-
east: Eastern longitude of the bounding box.
|
56 |
-
north: Northern latitude of the bounding box.
|
57 |
-
activity: Activity type (e.g. "rock_climbing", "hiking").
|
58 |
-
limit: Number of routes to return.
|
59 |
-
|
60 |
-
Returns:
|
61 |
-
JSON dictionary of matching route metadata.
|
62 |
"""
|
63 |
-
bbox = (
|
|
|
|
|
64 |
return c2c.search_routes_by_activity(bbox, activity, limit)
|
65 |
|
66 |
-
|
67 |
def get_route_details(route_id: int) -> dict:
|
68 |
"""
|
69 |
Retrieve detailed route information by route ID.
|
70 |
-
|
71 |
-
Args:
|
72 |
-
route_id: Unique numeric ID of the route.
|
73 |
-
|
74 |
-
Returns:
|
75 |
-
JSON dictionary of route information including description, grade, etc.
|
76 |
"""
|
77 |
return c2c.get_route_details(route_id)
|
78 |
|
79 |
-
|
80 |
-
def search_waypoints(
|
81 |
-
west: float,
|
82 |
-
south: float,
|
83 |
-
east: float,
|
84 |
-
north: float,
|
85 |
-
limit: int = 10
|
86 |
-
) -> dict:
|
87 |
"""
|
88 |
-
Get known waypoints (e.g. huts, peaks
|
89 |
-
|
90 |
-
Args:
|
91 |
-
west: Western longitude of the bounding box.
|
92 |
-
south: Southern latitude of the bounding box.
|
93 |
-
east: Eastern longitude of the bounding box.
|
94 |
-
north: Northern latitude of the bounding box.
|
95 |
-
limit: Maximum number of results.
|
96 |
-
|
97 |
-
Returns:
|
98 |
-
JSON dictionary of waypoints.
|
99 |
"""
|
100 |
-
bbox = (
|
|
|
|
|
101 |
return c2c.search_waypoints(bbox, limit)
|
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 |
-
with gr.Tab("Route Details"):
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
|
|
160 |
|
161 |
demo.launch()
|
|
|
1 |
import gradio as gr
|
2 |
from camptocamp_api import CamptocampAPI
|
3 |
+
from typing import Optional
|
4 |
|
|
|
|
|
5 |
c2c = CamptocampAPI(language="en")
|
6 |
|
7 |
+
def get_recent_outings_by_location(
|
8 |
+
location: str,
|
|
|
|
|
|
|
|
|
9 |
start_date: Optional[str] = None,
|
10 |
end_date: Optional[str] = None,
|
11 |
activity: Optional[str] = None,
|
12 |
limit: int = 10
|
13 |
) -> dict:
|
14 |
"""
|
15 |
+
Get recent outings for a location name using geocoded bounding box.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
"""
|
17 |
+
bbox = c2c.get_bbox_from_location(location)
|
18 |
+
if not bbox:
|
19 |
+
return {"error": f"Could not resolve bounding box for location: {location}"}
|
20 |
date_range = (start_date, end_date) if start_date and end_date else None
|
21 |
return c2c.get_recent_outings(bbox, date_range, activity, limit)
|
22 |
|
23 |
+
def search_routes_by_location(
|
24 |
+
location: str,
|
|
|
|
|
|
|
|
|
25 |
activity: str,
|
26 |
limit: int = 10
|
27 |
) -> dict:
|
28 |
"""
|
29 |
+
Search for routes near a named location.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
"""
|
31 |
+
bbox = c2c.get_bbox_from_location(location)
|
32 |
+
if not bbox:
|
33 |
+
return {"error": f"Could not find bounding box for location: {location}"}
|
34 |
return c2c.search_routes_by_activity(bbox, activity, limit)
|
35 |
|
|
|
36 |
def get_route_details(route_id: int) -> dict:
|
37 |
"""
|
38 |
Retrieve detailed route information by route ID.
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
"""
|
40 |
return c2c.get_route_details(route_id)
|
41 |
|
42 |
+
def search_waypoints_by_location(location: str, limit: int = 10) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
"""
|
44 |
+
Get known waypoints (e.g. huts, peaks) in a given region.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
"""
|
46 |
+
bbox = c2c.get_bbox_from_location(location)
|
47 |
+
if not bbox:
|
48 |
+
return {"error": f"No bounding box found for: {location}"}
|
49 |
return c2c.search_waypoints(bbox, limit)
|
50 |
|
51 |
+
def lookup_bbox_from_location(location_name: str) -> Optional[dict]:
|
52 |
+
"""
|
53 |
+
Look up the bounding box for a given place name.
|
54 |
+
"""
|
55 |
+
bbox = c2c.get_bbox_from_location(location_name)
|
56 |
+
if not bbox:
|
57 |
+
return {"error": f"No bounding box found for: {location_name}"}
|
58 |
+
return {
|
59 |
+
"west": bbox[0],
|
60 |
+
"south": bbox[1],
|
61 |
+
"east": bbox[2],
|
62 |
+
"north": bbox[3]
|
63 |
+
}
|
64 |
+
|
65 |
+
with gr.Blocks(title="Camptocamp MCP Server") as demo:
|
66 |
+
gr.Markdown("# 🏔️ Camptocamp API via MCP")
|
67 |
+
|
68 |
+
with gr.Tab("📍 Recent Outings"):
|
69 |
+
loc = gr.Textbox(label="Location (e.g. Chamonix, La Grave)")
|
70 |
+
start = gr.Textbox(label="Start Date (YYYY-MM-DD)")
|
71 |
+
end = gr.Textbox(label="End Date (YYYY-MM-DD)")
|
72 |
+
act = gr.Textbox(label="Activity (e.g. hiking, climbing)")
|
73 |
+
limit = gr.Number(label="Result Limit", value=5)
|
74 |
+
out = gr.JSON()
|
75 |
+
gr.Button("Get Outings").click(get_recent_outings_by_location,
|
76 |
+
inputs=[loc, start, end, act, limit],
|
77 |
+
outputs=out)
|
78 |
+
|
79 |
+
with gr.Tab("🧗 Search Routes"):
|
80 |
+
rloc = gr.Textbox(label="Location (e.g. Alps)")
|
81 |
+
ract = gr.Textbox(label="Activity (e.g. rock_climbing)")
|
82 |
+
rlim = gr.Number(label="Result Limit", value=5)
|
83 |
+
rout = gr.JSON()
|
84 |
+
gr.Button("Search Routes").click(search_routes_by_location,
|
85 |
+
inputs=[rloc, ract, rlim],
|
86 |
+
outputs=rout)
|
87 |
+
|
88 |
+
with gr.Tab("📄 Route Details"):
|
89 |
+
rid = gr.Number(label="Route ID")
|
90 |
+
rdet = gr.JSON()
|
91 |
+
gr.Button("Get Route Details").click(get_route_details,
|
92 |
+
inputs=[rid],
|
93 |
+
outputs=rdet)
|
94 |
+
|
95 |
+
with gr.Tab("⛰ Waypoints"):
|
96 |
+
wloc = gr.Textbox(label="Location (e.g. Mont Blanc)")
|
97 |
+
wlim = gr.Number(label="Result Limit", value=5)
|
98 |
+
wout = gr.JSON()
|
99 |
+
gr.Button("Get Waypoints").click(search_waypoints_by_location,
|
100 |
+
inputs=[wloc, wlim],
|
101 |
+
outputs=wout)
|
102 |
+
|
103 |
+
with gr.Tab("🌍 Location → BBox"):
|
104 |
+
lstr = gr.Textbox(label="Location Name")
|
105 |
+
lout = gr.JSON()
|
106 |
+
gr.Button("Lookup BBox").click(lookup_bbox_from_location,
|
107 |
+
inputs=[lstr],
|
108 |
+
outputs=lout)
|
109 |
|
110 |
demo.launch()
|
camptocamp_api.py
CHANGED
@@ -11,28 +11,11 @@ class CamptocampAPI:
|
|
11 |
BASE_URL = "https://api.camptocamp.org"
|
12 |
|
13 |
def __init__(self, language: str = "en") -> None:
|
14 |
-
"""
|
15 |
-
Initialize the API client.
|
16 |
-
|
17 |
-
Args:
|
18 |
-
language (str): Language code for results ('en', 'fr', etc.).
|
19 |
-
"""
|
20 |
self.language = language
|
21 |
|
22 |
def _request(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
23 |
-
"""
|
24 |
-
Internal method to send a GET request to the Camptocamp API.
|
25 |
-
|
26 |
-
Args:
|
27 |
-
endpoint (str): API endpoint (e.g., "/outings").
|
28 |
-
params (Dict): Dictionary of query parameters.
|
29 |
-
|
30 |
-
Returns:
|
31 |
-
Dict: Parsed JSON response.
|
32 |
-
"""
|
33 |
params["pl"] = self.language
|
34 |
-
|
35 |
-
response = requests.get(url, params=params)
|
36 |
response.raise_for_status()
|
37 |
return response.json()
|
38 |
|
@@ -41,86 +24,74 @@ class CamptocampAPI:
|
|
41 |
bbox: Tuple[float, float, float, float],
|
42 |
date_range: Optional[Tuple[str, str]] = None,
|
43 |
activity: Optional[str] = None,
|
44 |
-
limit: int = 10
|
45 |
) -> Dict[str, Any]:
|
46 |
-
"""
|
47 |
-
Get recent outings around a specific location and date.
|
48 |
-
|
49 |
-
Args:
|
50 |
-
bbox (Tuple[float, float, float, float]): Bounding box as (west, south, east, north).
|
51 |
-
date_range (Optional[Tuple[str, str]]): Date range (start, end) in YYYY-MM-DD format.
|
52 |
-
activity (Optional[str]): Activity filter (e.g., "climbing", "hiking").
|
53 |
-
limit (int): Max number of results.
|
54 |
-
|
55 |
-
Returns:
|
56 |
-
Dict: API response containing outings.
|
57 |
-
"""
|
58 |
params = {
|
59 |
"bbox": ",".join(map(str, bbox)),
|
60 |
"limit": limit,
|
61 |
-
"orderby": "-date"
|
62 |
}
|
63 |
if date_range:
|
64 |
params["date"] = f"{date_range[0]},{date_range[1]}"
|
65 |
if activity:
|
66 |
params["act"] = activity
|
67 |
-
|
68 |
return self._request("/outings", params)
|
69 |
|
70 |
def search_routes_by_activity(
|
71 |
self,
|
72 |
bbox: Tuple[float, float, float, float],
|
73 |
activity: str,
|
74 |
-
limit: int = 10
|
75 |
) -> Dict[str, Any]:
|
76 |
-
"""
|
77 |
-
Search for routes in a given area and activity type.
|
78 |
-
|
79 |
-
Args:
|
80 |
-
bbox (Tuple[float, float, float, float]): Bounding box as (west, south, east, north).
|
81 |
-
activity (str): Activity filter (e.g., "rock_climbing").
|
82 |
-
limit (int): Max number of results.
|
83 |
-
|
84 |
-
Returns:
|
85 |
-
Dict: API response containing route data.
|
86 |
-
"""
|
87 |
params = {
|
88 |
"bbox": ",".join(map(str, bbox)),
|
89 |
"act": activity,
|
90 |
"limit": limit,
|
91 |
-
"orderby": "-date"
|
92 |
}
|
93 |
return self._request("/routes", params)
|
94 |
|
95 |
def get_route_details(self, route_id: int) -> Dict[str, Any]:
|
96 |
-
"""
|
97 |
-
Retrieve full route information by its ID.
|
98 |
-
|
99 |
-
Args:
|
100 |
-
route_id (int): Camptocamp route ID.
|
101 |
-
|
102 |
-
Returns:
|
103 |
-
Dict: Full route document with metadata and description.
|
104 |
-
"""
|
105 |
return self._request(f"/routes/{route_id}/{self.language}", {})
|
106 |
|
107 |
def search_waypoints(
|
108 |
self,
|
109 |
bbox: Tuple[float, float, float, float],
|
110 |
-
limit: int = 10
|
111 |
) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
"""
|
113 |
-
|
114 |
|
115 |
Args:
|
116 |
-
|
117 |
-
limit (int): Max number of results.
|
118 |
|
119 |
Returns:
|
120 |
-
|
121 |
"""
|
|
|
122 |
params = {
|
123 |
-
"
|
124 |
-
"
|
|
|
125 |
}
|
126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
BASE_URL = "https://api.camptocamp.org"
|
12 |
|
13 |
def __init__(self, language: str = "en") -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
self.language = language
|
15 |
|
16 |
def _request(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
params["pl"] = self.language
|
18 |
+
response = requests.get(f"{self.BASE_URL}{endpoint}", params=params)
|
|
|
19 |
response.raise_for_status()
|
20 |
return response.json()
|
21 |
|
|
|
24 |
bbox: Tuple[float, float, float, float],
|
25 |
date_range: Optional[Tuple[str, str]] = None,
|
26 |
activity: Optional[str] = None,
|
27 |
+
limit: int = 10
|
28 |
) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
params = {
|
30 |
"bbox": ",".join(map(str, bbox)),
|
31 |
"limit": limit,
|
32 |
+
"orderby": "-date"
|
33 |
}
|
34 |
if date_range:
|
35 |
params["date"] = f"{date_range[0]},{date_range[1]}"
|
36 |
if activity:
|
37 |
params["act"] = activity
|
|
|
38 |
return self._request("/outings", params)
|
39 |
|
40 |
def search_routes_by_activity(
|
41 |
self,
|
42 |
bbox: Tuple[float, float, float, float],
|
43 |
activity: str,
|
44 |
+
limit: int = 10
|
45 |
) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
params = {
|
47 |
"bbox": ",".join(map(str, bbox)),
|
48 |
"act": activity,
|
49 |
"limit": limit,
|
50 |
+
"orderby": "-date"
|
51 |
}
|
52 |
return self._request("/routes", params)
|
53 |
|
54 |
def get_route_details(self, route_id: int) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
return self._request(f"/routes/{route_id}/{self.language}", {})
|
56 |
|
57 |
def search_waypoints(
|
58 |
self,
|
59 |
bbox: Tuple[float, float, float, float],
|
60 |
+
limit: int = 10
|
61 |
) -> Dict[str, Any]:
|
62 |
+
params = {
|
63 |
+
"bbox": ",".join(map(str, bbox)),
|
64 |
+
"limit": limit
|
65 |
+
}
|
66 |
+
return self._request("/waypoints", params)
|
67 |
+
|
68 |
+
@staticmethod
|
69 |
+
def get_bbox_from_location(query: str) -> Optional[Tuple[float, float, float, float]]:
|
70 |
"""
|
71 |
+
Geocode a location string and return a bounding box.
|
72 |
|
73 |
Args:
|
74 |
+
query: Name of the place or location (e.g., "Chamonix, France").
|
|
|
75 |
|
76 |
Returns:
|
77 |
+
Bounding box as (west, south, east, north) or None if not found.
|
78 |
"""
|
79 |
+
url = "https://nominatim.openstreetmap.org/search"
|
80 |
params = {
|
81 |
+
"q": query,
|
82 |
+
"format": "json",
|
83 |
+
"limit": 1
|
84 |
}
|
85 |
+
headers = {"User-Agent": "camptocamp-api-wrapper"}
|
86 |
+
response = requests.get(url, params=params, headers=headers)
|
87 |
+
response.raise_for_status()
|
88 |
+
results = response.json()
|
89 |
+
if not results:
|
90 |
+
return None
|
91 |
+
bbox = results[0]["boundingbox"] # ['south', 'north', 'west', 'east']
|
92 |
+
return (
|
93 |
+
float(bbox[2]), # west
|
94 |
+
float(bbox[0]), # south
|
95 |
+
float(bbox[3]), # east
|
96 |
+
float(bbox[1]) # north
|
97 |
+
)
|