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)