Skip to content

Commit 1b66697

Browse files
author
Alfiya Tarasenko
committed
Add Optimal Route example
1 parent 8c59dfb commit 1b66697

5 files changed

Lines changed: 435 additions & 1 deletion

File tree

python/calculate-and-visualize-isoline/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Description
44
This project demonstrates how to generate and display an **isochrone** (time-based isoline) or **isodistance** (distance-based isoline) on an interactive map using the [Geoapify Isoline API](https://www.geoapify.com/isoline-api/) and Folium in Python.
55

6-
![60-min dring Isochrone example](https://github.com/geoapify/maps-api-code-samples/blob/main/python/calculate-and-visualize-isoline/isochrone-driving-60min.png?raw=true)
6+
![60-min driving Isochrone example](https://github.com/geoapify/maps-api-code-samples/blob/main/python/calculate-and-visualize-isoline/isochrone-driving-60min.png?raw=true)
77

88
## Requirements
99
- Python 3.11 or higher
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# Find the Optimal Route and Visualize It on a Map
2+
3+
## Description
4+
This example demonstrates how to calculate an **optimal route** using the **Geoapify Route Planner API** and visualize it with **Folium**. It optionally supports skipping optimization and generating a route from coordinates in their original order.
5+
6+
![Optimal Route Example, in Limassol, Cyprus](https://github.com/geoapify/maps-api-code-samples/blob/main/python/optimize-route-with-route-planner-api/optimal-route-example.png?raw=true)
7+
8+
## Requirements
9+
- Python 3.11 or higher
10+
- pip
11+
12+
## Setup Instructions
13+
14+
### 1. Clone the Repository
15+
```bash
16+
git clone https://geoapify.github.io/maps-api-code-samples/
17+
cd maps-api-code-samples/python/
18+
```
19+
20+
### 2. Create a Virtual Environment (Optional)
21+
```bash
22+
python -m venv env
23+
source env/bin/activate # On Windows: env\Scripts\activate
24+
```
25+
26+
### 3. Install Dependencies
27+
```bash
28+
pip install folium requests
29+
```
30+
31+
---
32+
33+
## Running the Example
34+
35+
Go to the folder:
36+
37+
```bash
38+
cd optimize-route-with-route-planner-api
39+
```
40+
41+
Run the script:
42+
43+
```bash
44+
python optimal_route.py \
45+
--api_key YOUR_API_KEY \
46+
--input input.txt \
47+
--map map.html \
48+
--coord_order lonlat \
49+
--route_mode drive \
50+
--route_type balanced \
51+
--route_traffic free_flow \
52+
--start_location 33.055045935635796,34.683768
53+
```
54+
55+
Skip Optimization Example:
56+
57+
```bash
58+
python optimal_route.py --api_key YOUR_API_KEY --input input.txt --skip_optimization
59+
```
60+
61+
## Command-Line Arguments
62+
| Argument | Required | Description |
63+
|----------------------|----------|-------------|
64+
| `--api_key` | Yes | Geoapify API key |
65+
| `--input` | Yes | Input file with coordinates |
66+
| `--output` | No | Output file with optimized coordinates (default: `optimized.txt`) |
67+
| `--map` | No | Output HTML map file (default: `map.html`) |
68+
| `--coord_order` | No | Coordinate format: `latlon` (default) or `lonlat` |
69+
| `--skip_optimization`| No | Bypass Route Planner API and use given order |
70+
| `--route_mode` | No | Travel mode (`drive`, `walk`, `bike`, etc.) |
71+
| `--route_type` | No | Routing strategy: `balanced`, `short`, `less_maneuvers` |
72+
| `--route_traffic` | No | Traffic model: `free_flow`, `approximated` |
73+
| `--start_location` | Required*| Start location in same format as coord_order |
74+
| `--end_location` | Required*| End location in same format as coord_order |
75+
76+
> *At least one of `--start_location` or `--end_location` must be provided.*
77+
78+
79+
## Features
80+
- Optimizes stop sequence using **Geoapify Route Planner API**
81+
- Plots route using **Geoapify Routing API**
82+
- Supports both `optimized` and `original` coordinate order
83+
- Saves route as sorted coordinates and HTML map
84+
- Configurable routing parameters
85+
- Adds numbered markers using Geoapify Marker API or built-in icons
86+
87+
88+
## Output Files
89+
- `optimized.txt`: List of reordered coordinates (one per line)
90+
- `map.html`: Folium map displaying the full route
91+
92+
## APIs Used
93+
- [Geoapify Route Planner API](https://apidocs.geoapify.com/playground/route-planner/)
94+
- [Geoapify Routing API](https://apidocs.geoapify.com/playground/routing/)
95+
- [Geoapify Map Marker API](https://apidocs.geoapify.com/playground/icon/)
96+
- [Folium Library](https://python-visualization.github.io/folium/)
97+
98+
99+
## Function-by-Function Breakdown
100+
101+
### `optimize_route(...)`
102+
103+
```python
104+
def optimize_route(api_key, coordinates, start_location, end_location, route_mode):
105+
url = ROUTE_PLANNER_URL.format(api_key=api_key)
106+
agents = [{key: value for key, value in zip(['start_location', 'end_location'],
107+
[start_location, end_location]) if value}]
108+
jobs = [{"location": coord} for coord in coordinates]
109+
payload = {
110+
"mode": route_mode,
111+
"agents": agents,
112+
"jobs": jobs
113+
}
114+
response = requests.post(url, json=payload, params={'apiKey': api_key})
115+
if response.status_code == 200:
116+
data = response.json()
117+
waypoints = data['features'][0]['properties']['waypoints']
118+
# Return waypoints only if they have certainly one action
119+
return [wp['location'] for wp in waypoints if len(wp['actions']) == 1]
120+
else:
121+
raise Exception(f"Failed to optimize route: {response.text}")
122+
```
123+
124+
Sends a POST request to **Geoapify Route Planner API**.
125+
126+
- Constructs a task payload with:
127+
- Agent: `start_location`, `end_location` (if provided)
128+
- Jobs: the list of locations
129+
- Mode: travel type (`drive`, `walk`, etc.)
130+
- Returns only the waypoints that are part of the optimized visit plan.
131+
- Filters out non-"visit" actions like `start` or `end`.
132+
133+
**Why?** The Route Planner API returns a complete task solution with all actions — we keep only those with `"visit"` action to get the optimized order.
134+
135+
### `get_route(...)`
136+
137+
```python
138+
def get_route(api_key, waypoints, route_mode, route_type, route_traffic):
139+
waypoints_str = '|'.join([f"lonlat:{lon},{lat}" for lon, lat in waypoints])
140+
url = ROUTING_URL.format(waypoints_str=waypoints_str,
141+
route_mode=route_mode,
142+
route_type=route_type,
143+
route_traffic=route_traffic)
144+
response = requests.get(url, params={
145+
'waypoints': waypoints_str,
146+
'mode': route_mode,
147+
'type': route_type,
148+
'traffic': route_traffic,
149+
'apiKey': api_key
150+
})
151+
if response.status_code == 200:
152+
return response.json()
153+
else:
154+
raise Exception(f"Failed to retrieve route: {response.text}")
155+
```
156+
157+
Sends a GET request to the **Geoapify Routing API** using the ordered list of coordinates.
158+
159+
- Converts waypoints into `lonlat:` format string, separated by `|`.
160+
- Returns GeoJSON route geometry.
161+
162+
### `generate_map(...)`
163+
164+
```python
165+
def generate_map(route_data, output_map, waypoints, start_location, end_location, api_key):
166+
m = folium.Map(location=[0, 0], zoom_start=13)
167+
168+
# Construct the tile URL with the selected map style and API Key
169+
tile_url = BASE_MAP_TILE_URL.format(map_style="osm-bright-grey", api_key=api_key)
170+
171+
# Add the Geoapify raster tiles to the map
172+
folium.TileLayer(
173+
tiles=tile_url,
174+
name='Geoapify Map',
175+
attr="""Powered by <a href="https://www.geoapify.com/" target="_blank">Geoapify</a>
176+
| <a href="https://openmaptiles.org/" rel="nofollow" target="_blank">© OpenMapTiles</a> contributors""",
177+
overlay=True,
178+
control=True
179+
).add_to(m)
180+
181+
route = folium.GeoJson(route_data, style_function=lambda f: {
182+
'color': 'red',
183+
'weight': 7,
184+
'opacity': 0.8
185+
})
186+
route.add_to(m)
187+
m.fit_bounds(route.get_bounds())
188+
# Render markers that present every waypoint of route
189+
for num, coords in enumerate(waypoints):
190+
make_icon_marker(num + 1, coords, api_key).add_to(m)
191+
# Mark start and end locations
192+
for label, coords in zip(['Start', 'End'], [start_location, end_location]):
193+
if coords:
194+
make_icon_marker(label, coords, api_key).add_to(m)
195+
196+
m.save(output_map)
197+
```
198+
199+
Builds a Folium map and adds:
200+
- **Geoapify tile layer** as the background map
201+
- The **route geometry** as a red polyline
202+
- **Custom markers** for each stop using the Geoapify Map Marker API (with number or "Start"/"End" labels)
203+
204+
Also auto-fits the map to the full route.
205+
206+
### `make_icon_marker(label, coords, api_key)`
207+
208+
```python
209+
def make_icon_marker(label, coords, api_key) -> folium.Marker:
210+
# Create custom marker from icon API
211+
marker_url = 'https://api.geoapify.com/v1/icon/?type=circle&color=red&size=large&text={label}&noShadow&noWhiteCircle&scaleFactor=2&apiKey={api_key}'
212+
icon = folium.CustomIcon(marker_url.format(api_key=api_key, label=label),
213+
icon_size=(31, 31),
214+
icon_anchor=(15, 42),
215+
popup_anchor=(0, -42))
216+
return folium.Marker(location=coords[::-1], icon=icon)
217+
```
218+
219+
Generates a numbered marker using the **Geoapify Map Marker API** and returns a `folium.Marker`.
220+
221+
- Applies red circle style with the `label` as text.
222+
- Marker icons are visually distinct and scalable.
223+
224+
### `main()`
225+
226+
The script’s entry point.
227+
228+
1. Parses args
229+
2. Validates that at least `--start_location` or `--end_location` is present
230+
3. Reads and optionally reorders the coordinates
231+
4. Saves the reordered list to `optimized.txt`
232+
5. Calls Routing API and saves the HTML route map
233+
234+
### Highlights
235+
236+
- Works with both optimized and static orders
237+
- Easy configuration with CLI flags
238+
- Uses Route Planner for TSP-style optimization
239+
- Uses Routing API for navigable polyline
240+
- Beautiful map rendering with Geoapify tiles and marker icons
241+
242+
243+
## License
244+
MIT License
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
33.0405986,34.6950754
2+
33.0476894,34.6827582
3+
33.0442777,34.6847945
4+
33.0506284,34.6837694
5+
33.0353603,34.69226
6+
33.0378201,34.6849975
7+
33.030999,34.6918311
8+
33.0453118,34.68111
9+
33.0439222,34.6834278
10+
33.0517755,34.6868048
11+
33.0232147,34.6815873
12+
33.0531031,34.6880047
13+
33.0449253,34.6937631
14+
33.0352637,34.6810349
15+
33.0312541,34.685698
16+
33.0355928,34.6798928
17+
33.04247,34.6812868
18+
33.0288175,34.6844045
19+
33.0431995,34.6808369
20+
33.0530528,34.6857644
21+
33.0312713,34.6881078
22+
33.0382761,34.6827522
23+
33.0386436,34.6821304
24+
33.0543948,34.6872076
25+
33.0552523,34.6878006
26+
33.0405009,34.6834935
27+
33.0420922,34.6815037
28+
33.0432493,34.6809679
29+
33.0471802,34.6876472
30+
33.0359304,34.6901302
6.28 MB
Loading

0 commit comments

Comments
 (0)