c2c / camptocamp_api.py
Jikkii's picture
Rename
f360e0e
from urllib.parse import urlencode
from pyproj import Transformer
import requests
import logging
from typing import Tuple, Optional, Dict, Any
logger = logging.getLogger("CamptocampAPI")
class CamptocampAPI:
"""
A Python wrapper for the Camptocamp.org REST API v6.
Supports querying outings, routes, waypoints, and more.
"""
BASE_URL = "https://api.camptocamp.org"
def __init__(self, language: str = "en") -> None:
self.language = language
from urllib.parse import urlencode
def _request(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
params["pl"] = self.language
url = f"{self.BASE_URL}{endpoint}"
full_url = f"{url}?{urlencode(params)}"
logger.info(f"[API REQUEST] {url} with params: {params}")
logger.info(f"[DEBUG URL] curl '{full_url}'")
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
def get_outings(
self,
bbox: Tuple[float, float, float, float],
date_range: Optional[Tuple[str, str]] = None,
activity: Optional[str] = None,
limit: int = 10
) -> Dict[str, Any]:
params = {
"bbox": ",".join(map(str, bbox)),
"limit": limit,
"orderby": "-date"
}
if date_range:
params["date"] = f"{date_range[0]},{date_range[1]}"
if activity:
params["act"] = activity
return self._request("/outings", params)
def search_routes_by_activity(
self,
bbox: Tuple[float, float, float, float],
activity: str,
limit: int = 10
) -> Dict[str, Any]:
params = {
"bbox": ",".join(map(str, bbox)),
"act": activity,
"limit": limit,
"orderby": "-date"
}
return self._request("/routes", params)
def get_route_details(self, route_id: int) -> Dict[str, Any]:
return self._request(f"/routes/{route_id}/{self.language}", {})
def search_waypoints(
self,
bbox: Tuple[float, float, float, float],
limit: int = 10
) -> Dict[str, Any]:
params = {
"bbox": ",".join(map(str, bbox)),
"limit": limit
}
return self._request("/waypoints", params)
@staticmethod
def get_bbox_from_location(query: str) -> Optional[Tuple[float, float, float, float]]:
"""
Geocode a location string and return a bounding box.
Args:
query: Name of the place or location (e.g., "Chamonix, France").
Returns:
Bounding box as (west, south, east, north) or None if not found.
"""
url = "https://nominatim.openstreetmap.org/search"
params = {
"q": query,
"format": "json",
"limit": 1
}
headers = {"User-Agent": "camptocamp-api-wrapper"}
logger.info(f"Geocoding location: {query}")
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
results = response.json()
if not results:
logger.warning(f"No results found for: {query}")
return None
bbox = results[0]["boundingbox"]
logger.info(f"BBox for '{query}': {bbox}")
return CamptocampAPI.convert_bbox_to_webmercator((
float(bbox[2]), # west
float(bbox[0]), # south
float(bbox[3]), # east
float(bbox[1]) # north
))
@staticmethod
def convert_bbox_to_webmercator(bbox: Tuple[float, float, float, float]) -> Tuple[int, int, int, int]:
"""
Convert a WGS84 bbox (lon/lat) to EPSG:3857 (Web Mercator) in meters.
Args:
bbox: (west, south, east, north) in degrees
Returns:
(west, south, east, north) in meters
"""
transformer = Transformer.from_crs("epsg:4326", "epsg:3857", always_xy=True)
west, south = transformer.transform(bbox[0], bbox[1])
east, north = transformer.transform(bbox[2], bbox[3])
return int(west), int(south), int(east), int(north)