File size: 4,185 Bytes
dc8b750 17c0e3d b2ff92e 7d1cee1 b2ff92e 7d1cee1 b2ff92e dc8b750 b2ff92e 7d1cee1 dc8b750 7d1cee1 b2ff92e f360e0e b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 b2ff92e 038aba5 7d1cee1 038aba5 7d1cee1 038aba5 7d1cee1 17c0e3d 038aba5 17c0e3d |
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 |
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) |