Cartons
A lightweight Python toolkit for OSRM routing, route geometry conversion, and interactive Folium map visualization.
Cartons gives you a small Python API for sending coordinate sequences to an OSRM routing server through routingpy, inspecting the returned route, converting route geometry into Shapely / GeoJSON forms, and rendering interactive maps with Folium.
The project deliberately stays small: it is not a complete GIS framework, geocoder, routing server, or navigation application. It focuses on the path from coordinates → route → geometry → interactive map.
[!IMPORTANT] This README is the official documentation for Cartons 1.3.0. All installation instructions, signatures, examples, coordinate conventions, and API behavior below document the 1.3.0 API. Routing functions use coordinate sequences such as
[[lon, lat], [lon, lat]].
Preview
Geneva → Zürich
A long-distance route rendered across Switzerland.
Bern → Zürich
A shorter intercity route.
Zoomed route detail
A close-up view of route geometry on the interactive map.
Table of contents
- What Cartons does
- What's new in 1.3.0
- Requirements
- Installation
- Quick start
- Coordinate order
- How Cartons works
- Public API
- Examples
- OSRM servers and transport profiles
- Map tiles and attribution
- Return types and data model
- Error handling
- Performance and network behavior
- Privacy considerations
- Project structure
- Development
- Testing status
- Release and PyPI publishing
- Migrating from 1.2.x to 1.3.0
- Known limitations
- Roadmap
- Troubleshooting
- Contributing
- License
- Acknowledgements
What Cartons does
Cartons currently provides six root-level public functions:
| Function | Purpose | Network request? | Main return type |
|---|---|---|---|
cartons.route() |
Calculate an OSRM route | Yes | routingpy route/direction object |
cartons.draw() |
Calculate and draw a configurable route | Yes | folium.Map |
cartons.simpleroute() |
Calculate and draw a route with fixed styling | Yes | folium.Map |
cartons.simpledraw() |
Draw an already-existing coordinate path | No OSRM request | folium.Map |
cartons.line_string_route() |
Calculate a route and convert its geometry to Shapely | Yes | shapely.LineString |
cartons.geo_json_geometry() |
Calculate a route and export its geometry as GeoJSON text | Yes | str |
The public names above are exported by cartons/__init__.py, so normal usage is simply:
import cartons
Features
- OSRM route requests through
routingpy.OSRM - Full route overview geometry (
overview="full") - Multiple input coordinates accepted as a coordinate sequence
- Access to route metadata such as distance, duration, and geometry from the routingpy result
- Interactive Folium map output
- Automatic map fitting around the route
- Optional start/end route markers in
draw() - Custom route color, line weight, tile source, and tile attribution in
draw() - Fast local drawing of precomputed
[lat, lon]coordinate paths withsimpledraw() - Shapely
LineStringconversion - GeoJSON geometry serialization
- Pure-Python package interface and OS-independent packaging metadata
What it does not provide
Cartons currently does not provide geocoding, its own routing engine, offline map tiles, turn-by-turn navigation UI, traffic data, route-alternative controls, built-in retries, caching, or a web server. Those responsibilities belong to the configured OSRM/tile services or to your application.
What's new in 1.3.0
Cartons 1.3.0 standardizes routing around an ordered coordinate-list API and expands geometry output options.
Coordinate-list routing API
Route-calculating functions use an ordered list of [longitude, latitude] pairs:
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
route = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
This format supports two-point routes as well as routes containing additional ordered waypoints.
Geometry conversion helpers
Cartons 1.3.0 exposes:
cartons.line_string_route(...)
cartons.geo_json_geometry(...)
These helpers convert routed OSRM geometry into a Shapely LineString or GeoJSON geometry text.
Python requirement
Cartons 1.3.0 requires Python 3.10 or newer.
Documentation
This README is the primary user and developer documentation for the 1.3.0 release and documents the code shipped by this version.
Requirements
Cartons 1.3.0 requires:
- Python 3.10+
routingpyfoliumshapely- access to an OSRM-compatible routing server for functions that calculate routes
The dependencies are currently unpinned in pyproject.toml, so a normal install resolves the latest compatible versions available to your Python environment.
Installation
Install Cartons 1.3.0 from PyPI
python -m pip install cartons
To explicitly install this release:
python -m pip install "cartons==1.3.0"
Install from GitHub
python -m pip install "git+https://github.com/AndPan3/cartons.git"
Use this when you intentionally want the repository version rather than the packaged PyPI release.
Development install
git clone https://github.com/AndPan3/cartons.git
cd cartons
python -m pip install -e .
Check the installed version
Cartons 1.3.0 does not define cartons.__version__. Use Python package metadata:
from importlib.metadata import version
print(version("cartons"))
Quick start
The 1.3.0 routing API accepts coordinates as a list of [longitude, latitude] pairs.
import cartons
BASE_URL = "https://router.project-osrm.org"
# Bern -> Zürich
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
route = cartons.route(
BASE_URL,
coords,
"driving",
)
print(f"Distance: {route.distance / 1000:.2f} km")
print(f"Duration: {route.duration / 60:.1f} min")
print(f"Geometry points: {len(route.geometry)}")
To calculate and display the same route on a Folium map:
import cartons
BASE_URL = "https://router.project-osrm.org"
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
m = cartons.draw(
BASE_URL,
coords,
col="red",
weight=5,
tiles="CartoDB Positron",
attribution="© OpenStreetMap contributors © CARTO",
transport="driving",
marker=True,
)
m.save("route.html")
Open route.html in a browser, or display m directly in a notebook environment that renders Folium maps.
Coordinate order
This is the most important convention in the package.
Routing functions: [longitude, latitude]
The following functions eventually pass coords directly to routingpy.OSRM.directions() and therefore expect routing coordinates in [lon, lat] order:
coords = [
[7.4442153, 46.94686], # Bern: [lon, lat]
[8.5431302, 47.3668725], # Zürich: [lon, lat]
]
Use [lon, lat] with:
route()draw()simpleroute()line_string_route()geo_json_geometry()
simpledraw(): [latitude, longitude]
Folium uses [lat, lon] coordinates. Because simpledraw() does not call OSRM and does not swap coordinate order, you provide display-ready coordinates directly:
coords_for_folium = [
[46.94686, 7.4442153], # Bern: [lat, lon]
[47.3668725, 8.5431302], # Zürich: [lat, lon]
]
m = cartons.simpledraw(coords_for_folium)
Why the order differs
OSRM/routingpy geometry is represented as longitude/latitude pairs, while Folium/Leaflet location arrays are latitude/longitude. draw() and simpleroute() convert route geometry internally:
OSRM geometry [lon, lat]
|
| internal swap
v
Folium geometry [lat, lon]
simpledraw() skips routing entirely, so the caller supplies the Folium form.
How Cartons works
[lon, lat] coordinate list
|
v
cartons.route()
|
v
routingpy.OSRM
|
v
OSRM server
|
v
routingpy route object
| | |
| | +--> distance / duration / metadata
| |
| +----------> route.geometry ([lon, lat])
|
+------------------> cartons.draw() / cartons.simpleroute()
|
+--> swap to [lat, lon]
+--> Folium PolyLine
+--> fit_bounds()
+--> folium.Map
route.geometry
|
+--> cartons.line_string_route() --> Shapely LineString
|
+--> cartons.geo_json_geometry() --> GeoJSON geometry string
Every route-calculating helper creates an OSRM client from the supplied base_url and makes a route request. simpledraw() is the exception: it only creates a Folium map from coordinates you already have.
Public API
route()
Calculate a route with OSRM and return the routingpy result object.
cartons.route(base_url, coords, transport)
Signature
def route(base_url, coords: list, transport):
...
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
base_url |
str |
Yes | Base URL of the OSRM server. |
coords |
list |
Yes | Ordered route locations, normally [[lon, lat], ...]. |
transport |
str |
Yes | Profile forwarded to routingpy.OSRM.directions(). Profile behavior depends on the OSRM server. |
Internal request
Cartons 1.3.0 calls:
router.directions(
overview="full",
profile=transport,
locations=coords,
)
This means Cartons explicitly requests the full route overview geometry but does not currently expose options such as alternatives, steps, annotations, radiuses, or bearings.
Returns
A routingpy route/direction object. Commonly useful attributes include:
result.geometry
result.distance
result.duration
geometry is used by the rest of Cartons and is expected to contain [lon, lat] coordinate pairs.
Example
import cartons
coords = [
[6.143158, 46.204391], # Geneva
[8.541694, 47.376887], # Zürich
]
result = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
print(result.distance)
print(result.duration)
print(result.geometry[:3])
Multiple waypoints
Cartons 1.3.0 forwards the entire coordinate list to routingpy, so you can provide more than two locations when supported by the backend:
coords = [
[6.143158, 46.204391], # Geneva
[7.447447, 46.948271], # Bern
[8.541694, 47.376887], # Zürich
]
result = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
The order of coordinates is the visit order; Cartons does not optimize waypoint order.
draw()
Calculate a route and immediately draw it on a configurable Folium map.
cartons.draw(
base_url,
coords,
col,
weight,
tiles,
attribution,
transport,
marker=True,
)
Signature
def draw(
base_url,
coords: list,
col: str,
weight: str,
tiles: str,
attribution: str,
transport: str,
marker=True,
):
...
The source annotates weight as str, but Folium line weight is conventionally supplied as a number; examples in this README use an integer.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
base_url |
str |
Yes | OSRM server base URL. |
coords |
list |
Yes | Route locations in [lon, lat] order. |
col |
str |
Yes | Folium/Leaflet route line color. |
weight |
number/string | Yes | Route line thickness passed to folium.PolyLine. |
tiles |
str |
Yes | Folium tile provider name or tile URL. |
attribution |
str |
Yes | Attribution for the configured tile source. |
transport |
str |
Yes | OSRM/routingpy profile string. |
marker |
bool |
No | Add markers at the first and last points of the returned route geometry. Defaults to True. |
What it does
- Calls
route(base_url, coords, transport). - Reads
getroute.geometry. - Converts every
[lon, lat]point to[lat, lon]for Folium. - Creates a
folium.Mapwith the requested tiles and attribution. - If
marker == True, adds markers to the first and last routed geometry points. - Adds a
folium.PolyLine. - Calls
fit_bounds()so the map frames the route. - Returns the
folium.Map.
[!NOTE] The markers are attached to the first and last points of the route geometry returned by OSRM. Those points may be snapped to the road network and therefore may differ slightly from the raw coordinates you supplied.
Example
import cartons
coords = [
[6.143158, 46.204391],
[8.541694, 47.376887],
]
m = cartons.draw(
"https://router.project-osrm.org",
coords,
col="red",
weight=6,
tiles="CartoDB Positron",
attribution="© OpenStreetMap contributors © CARTO",
transport="driving",
marker=True,
)
m.save("geneva-zurich.html")
Disable markers
m = cartons.draw(
"https://router.project-osrm.org",
coords,
col="blue",
weight=5,
tiles="CartoDB Positron",
attribution="© OpenStreetMap contributors © CARTO",
transport="driving",
marker=False,
)
Custom tile URL
m = cartons.draw(
"https://router.project-osrm.org",
coords,
col="red",
weight=5,
tiles="https://tiles.openfreemap.org/styles/liberty/{z}/{x}/{y}.png",
attribution="OpenFreeMap / OpenStreetMap contributors",
transport="driving",
)
When using a custom provider, use the attribution wording required by that provider and its underlying data sources.
simpleroute()
Calculate and display a route with minimal arguments and fixed map styling.
cartons.simpleroute(base_url, coords, transport)
Signature
def simpleroute(base_url, coords: list, transport: str):
...
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
base_url |
str |
Yes | OSRM server base URL. |
coords |
list |
Yes | Ordered [lon, lat] route coordinates. |
transport |
str |
Yes | OSRM/routingpy profile string. |
Fixed styling
Cartons 1.3.0 uses:
- tiles:
CartoDB Positron - attribution:
Copyright: CartoDB Positron - route color:
red - line weight:
5 control_scale=True- no start/end markers
- automatic
fit_bounds()
Returns
A folium.Map.
Example
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
m = cartons.simpleroute(
"https://router.project-osrm.org",
coords,
"driving",
)
m.save("simple-route.html")
Use simpleroute() when you want routing + visualization with almost no styling decisions. Use draw() when you need control over the line, tiles, attribution, or markers.
simpledraw()
Draw an existing path without requesting a route from OSRM.
cartons.simpledraw(
coords,
col="blue",
weight=5,
tiles="CartoDB Positron",
attribution="© CartoDB Positron",
)
Signature
def simpledraw(
coords: list,
col="blue",
weight=5,
tiles="CartoDB Positron",
attribution="© CartoDB Positron",
):
...
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
coords |
list |
Yes | — | Display coordinates in [lat, lon] order. |
col |
str |
No | "blue" |
Polyline color. |
weight |
number | No | 5 |
Polyline thickness. |
tiles |
str |
No | "CartoDB Positron" |
Folium tile source. |
attribution |
str |
No | "© CartoDB Positron" |
Tile attribution text. |
Behavior
simpledraw():
- does not import or call an OSRM server during the function call
- creates a Folium map
- draws your supplied coordinates as a polyline exactly in the order provided
- fits the map bounds around those points
- does not add endpoint markers
Example
import cartons
coords = [
[46.94686, 7.4442153],
[47.0, 7.7],
[47.15, 8.0],
[47.3668725, 8.5431302],
]
m = cartons.simpledraw(
coords,
col="red",
weight=5,
)
m.save("custom-path.html")
Good use cases
- visualizing cached route geometry
- drawing GPS tracks you have already converted to
[lat, lon] - drawing custom paths that are not road routes
- avoiding another OSRM request when geometry is already available
line_string_route()
Calculate a route and convert its OSRM geometry to a Shapely LineString.
cartons.line_string_route(coords, transport, base_url)
[!CAUTION] The argument order differs from
route():coordsandtransportcome beforebase_url.
Signature
def line_string_route(coords: list, transport: str, base_url: str):
...
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
coords |
list |
Yes | Route locations in [lon, lat] order. |
transport |
str |
Yes | OSRM/routingpy profile. |
base_url |
str |
Yes | OSRM server base URL. |
Returns
A shapely.LineString created from the route geometry.
Example
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
line = cartons.line_string_route(
coords,
"driving",
"https://router.project-osrm.org",
)
print(line.geom_type) # LineString
print(line.bounds)
print(line.wkt[:120])
Geographic distance warning
The LineString contains longitude/latitude coordinates. Shapely operates in Cartesian coordinate space and does not automatically know that these are geographic degrees. Do not interpret line.length as road distance in meters or kilometers. Use the original routing result's distance when you need OSRM route distance, or project geometry into a suitable coordinate reference system before metric geometric calculations.
geo_json_geometry()
Calculate a route and serialize its geometry as GeoJSON text through Shapely.
cartons.geo_json_geometry(coords, transport, base_url)
[!CAUTION] Like
line_string_route(), this function uses the argument order(coords, transport, base_url).
Signature
def geo_json_geometry(coords: list, transport: str, base_url: str):
...
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
coords |
list |
Yes | Route locations in [lon, lat] order. |
transport |
str |
Yes | OSRM/routingpy profile. |
base_url |
str |
Yes | OSRM server base URL. |
Returns
A string containing GeoJSON for the route geometry. It is not a complete GeoJSON Feature or FeatureCollection and does not include route properties such as distance or duration.
Typical shape:
{
"type": "LineString",
"coordinates": [
[7.44, 46.94],
[7.45, 46.95]
]
}
Example
import json
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
geojson_text = cartons.geo_json_geometry(
coords,
"driving",
"https://router.project-osrm.org",
)
print(geojson_text)
# Parse the JSON text if you need a Python dictionary.
geojson = json.loads(geojson_text)
print(geojson["type"])
Create a full GeoJSON Feature
Cartons currently returns geometry only. You can wrap it yourself:
import json
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
result = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
geometry = json.loads(
cartons.geo_json_geometry(
coords,
"driving",
"https://router.project-osrm.org",
)
)
feature = {
"type": "Feature",
"geometry": geometry,
"properties": {
"distance_m": result.distance,
"duration_s": result.duration,
},
}
print(json.dumps(feature, indent=2))
The example above performs two routing requests because route() and geo_json_geometry() each route independently. If request efficiency matters, use route() once and convert result.geometry yourself with Shapely.
Examples
Calculate a route and inspect metadata
import cartons
coords = [
[6.143158, 46.204391],
[8.541694, 47.376887],
]
r = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
print(f"Distance: {r.distance / 1000:.2f} km")
print(f"Duration: {r.duration / 3600:.2f} h")
print(f"Geometry points: {len(r.geometry)}")
Create a browser-ready HTML map
import webbrowser
import cartons
coords = [
[6.143158, 46.204391],
[8.541694, 47.376887],
]
m = cartons.draw(
"https://router.project-osrm.org",
coords,
col="red",
weight=5,
tiles="CartoDB Positron",
attribution="© OpenStreetMap contributors © CARTO",
transport="driving",
)
filename = "route.html"
m.save(filename)
webbrowser.open(filename)
Route through an intermediate waypoint
import cartons
coords = [
[6.143158, 46.204391], # Geneva
[7.447447, 46.948271], # Bern
[8.541694, 47.376887], # Zürich
]
m = cartons.simpleroute(
"https://router.project-osrm.org",
coords,
"driving",
)
m.save("geneva-bern-zurich.html")
Draw OSRM geometry without routing a second time
If you already called route(), convert its geometry once and use simpledraw():
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
r = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
# routingpy/OSRM geometry: [lon, lat]
# Folium/simpledraw geometry: [lat, lon]
folium_coords = [[lat, lon] for lon, lat in r.geometry]
m = cartons.simpledraw(folium_coords, col="red", weight=5)
m.save("cached-route.html")
Build a Shapely object without a second network request
from shapely import LineString
import cartons
coords = [
[7.4442153, 46.94686],
[8.5431302, 47.3668725],
]
r = cartons.route(
"https://router.project-osrm.org",
coords,
"driving",
)
line = LineString(r.geometry)
print(line)
This pattern is more efficient than calling route() and line_string_route() separately when you need both route metadata and a Shapely geometry.
OSRM servers and transport profiles
Cartons does not ship an OSRM server. You provide a base_url for one.
For simple experiments, the examples use:
https://router.project-osrm.org
For production applications, consider operating your own OSRM instance or using a routing provider appropriate to your traffic volume, availability, data region, and terms of service.
Transport/profile behavior
Cartons passes your transport value to routingpy's OSRM adapter as profile=transport.
cartons.route(base_url, coords, "driving")
Profile support depends on the server. OSRM itself can be built with different routing profiles, and some deployments encode a routing mode in the server/base URL or otherwise ignore a client-supplied profile value. Do not assume that every OSRM endpoint supports every mode string.
If routing fails for one profile:
- check the documentation for the OSRM endpoint you are using;
- verify the expected profile string;
- verify whether the deployment uses separate base URLs for car, bicycle, or foot routing.
Map tiles and attribution
draw() and simpledraw() let you select a Folium tile source. simpleroute() uses CartoDB Positron internally.
Named provider
tiles="CartoDB Positron"
Custom tile URL
tiles="https://example.com/tiles/{z}/{x}/{y}.png"
When using a custom tile server, you are responsible for:
- using a valid Leaflet/Folium tile URL template;
- supplying any required access token or query parameters;
- following the provider's usage limits and terms;
- displaying the required attribution.
A saved Folium HTML file can still need network access when opened because the browser loads map tiles from the configured provider.
Return types and data model
routingpy result
route() returns routingpy's direction/route result rather than converting it into a Cartons-specific class. This keeps the wrapper small and lets callers use the upstream result directly.
Common fields used by Cartons/examples:
| Attribute | Meaning |
|---|---|
geometry |
Route path coordinates, used by all conversion/drawing helpers. |
distance |
Route distance from the routing backend, typically in meters for OSRM/routingpy. |
duration |
Route duration from the routing backend, typically in seconds for OSRM/routingpy. |
Additional fields depend on routingpy and the backend response.
folium.Map
draw(), simpleroute(), and simpledraw() return normal Folium map objects. You can continue customizing them with Folium after Cartons returns them:
m = cartons.simpledraw(coords)
# Add any other Folium layers, controls, markers, etc. here.
m.save("map.html")
Shapely LineString
line_string_route() returns a standard Shapely geometry and can therefore be used with Shapely operations. Remember that its coordinates remain geographic longitude/latitude unless you transform them yourself.
GeoJSON string
geo_json_geometry() returns serialized JSON text. Parse it with json.loads() when you need a Python dictionary.
Error handling
Cartons currently contains very little validation or exception handling. Errors are allowed to propagate from routingpy, the HTTP stack, Folium, Shapely, or Python itself.
Typical failure causes include:
- unreachable OSRM server
- invalid base URL
- unsupported transport/profile
- malformed coordinate list
- reversed latitude/longitude
- coordinates outside the routing server's loaded dataset
- no routable road near a waypoint
- tile-provider configuration problems
- invalid geometry or incompatible dependency versions
For an application, wrap network calls with your own error handling:
import cartons
try:
r = cartons.route(
"https://router.project-osrm.org",
[[7.4442153, 46.94686], [8.5431302, 47.3668725]],
"driving",
)
except Exception as exc:
print(f"Routing failed: {exc}")
For production code, prefer catching specific upstream exception types once you have chosen the routingpy/HTTP versions used by your project.
Performance and network behavior
Cartons is small, but understanding where work happens helps avoid unnecessary requests.
Functions that call OSRM
Each call to one of these functions creates an OSRM client and performs routing:
route()draw()simpleroute()line_string_route()geo_json_geometry()
There is currently no built-in route cache or shared persistent client in Cartons.
Function that does not call OSRM
simpledraw() only constructs a Folium map from the coordinates supplied to it. It is the correct choice when route geometry is already available.
Avoid duplicate route requests
If you need several representations of the same route, call route() once and derive the rest locally:
import json
from shapely import LineString, to_geojson
import cartons
r = cartons.route(base_url, coords, "driving")
line = LineString(r.geometry)
geojson_text = to_geojson(line)
folium_coords = [[lat, lon] for lon, lat in r.geometry]
m = cartons.simpledraw(folium_coords)
This produces route metadata, a Shapely line, GeoJSON geometry, and a Folium map from one routing request.
Privacy considerations
Coordinates passed to routing functions are sent to the OSRM service identified by base_url. If route locations are sensitive, choose a routing service whose privacy and retention policies fit your application, or host OSRM yourself.
Interactive maps can also make browser requests to the configured tile provider when the HTML map is viewed. The tile provider can therefore receive information such as requested tile areas and normal web-request metadata.
Project structure
Current repository layout:
cartons/
├── .github/
│ └── workflows/
│ └── python-publish.yml
├── cartons/
│ ├── __init__.py
│ ├── display.py
│ ├── formats.py
│ ├── routing.py
│ └── simpleroute.py
├── img/
│ ├── img1.png
│ ├── img2.png
│ └── img3.png
├── otherfiles/
│ ├── Testing/
│ │ └── test1.py
│ ├── LICENSE
│ └── next.txt
├── pyproject.toml
└── README.md
cartons/__init__.py
Defines the root public API by exporting:
route
draw
simpledraw
simpleroute
line_string_route
geo_json_geometry
cartons/routing.py
The routing layer. It constructs routingpy.OSRM(base_url=...) and calls directions() with:
overview="full"profile=transportlocations=coords
cartons/display.py
Contains:
draw()— route + configurable Folium mapsimpledraw()— direct coordinate visualization without routing
cartons/simpleroute.py
Contains the fixed-style simpleroute() convenience function.
cartons/formats.py
Contains route-format conversion helpers:
- internal
routing()pass-through helper - public
line_string_route() - public
geo_json_geometry()
The internal routing() helper is not exported from cartons.__init__.py and is not needed for normal use; cartons.route() already provides the same high-level routing result.
img/
Repository screenshots/examples used by this README.
otherfiles/Testing/test1.py
A manual browser demo script, not an automated unit-test suite. See Testing status.
otherfiles/next.txt
Maintainer roadmap / TODO notes. The authoritative description of features shipped in 1.3.0 is this README; see Roadmap for documented future ideas.
otherfiles/LICENSE
MIT License, copyright 2026 Andrej Bajusic.
pyproject.toml
Setuptools-based package metadata. Version 1.3.0 declares:
- package name:
cartons - version:
1.3.0 - Python:
>=3.10 - dependencies:
routingpy,folium,shapely - package discovery:
cartons*
.github/workflows/python-publish.yml
Builds and publishes package distributions when a GitHub Release is published.
Development
Clone and install
git clone https://github.com/AndPan3/cartons.git
cd cartons
python -m pip install -e .
Build distributions locally
The release workflow uses the standard build package:
python -m pip install --upgrade pip
python -m pip install build
python -m build
Successful output is written to dist/, normally including a source distribution and wheel.
Recommended development checks
The repository currently has no committed automated test/lint workflow, so contributors should at minimum check:
python -m build
python -m compileall cartons
Then run a small routing smoke test against a suitable OSRM endpoint and inspect the generated Folium output.
For serious development, adding pytest, API-signature tests, mocked routing tests, and a CI test job would substantially improve regression protection.
Testing status
The repository includes a manual demo under:
otherfiles/Testing/test1.py
For Cartons 1.3.0, a routing smoke test should use the coordinate-list API:
import webbrowser
import cartons
coords = [
[8.5431473, 47.3669154], # Zürich
[6.1426172, 46.2086997], # Geneva
]
m = cartons.draw(
"https://router.project-osrm.org",
coords,
col="red",
weight=5,
tiles="https://tiles.openfreemap.org/styles/liberty/{z}/{x}/{y}.png",
attribution="OpenFreeMap / OpenStreetMap contributors",
transport="driving",
)
filename = "route.html"
m.save(filename)
webbrowser.open(filename)
This is a manual smoke test rather than a unit test because it depends on a live routing endpoint, a tile provider, and a browser.
The repository does not currently include a committed automated unit-test suite. Contributors should therefore verify API changes carefully and keep examples in this README synchronized with the 1.3.0 signatures.
Release and PyPI publishing
The repository has one GitHub Actions workflow: .github/workflows/python-publish.yml.
Trigger
It runs when a GitHub Release is published:
on:
release:
types: [published]
Build job
The workflow:
- runs on
ubuntu-latest; - checks out the repository;
- sets up Python
3.11; - installs/updates
pip; - installs
build; - runs
python -m build; - uploads
dist/as a GitHub Actions artifact namedrelease-dists.
Publish job
The second job:
- waits for the build job;
- downloads
release-dists; - requests
id-token: writepermission; - publishes
dist/to PyPI withpypa/gh-action-pypi-publish@release/v1.
This is a Trusted Publishing / OIDC-style workflow and does not store a traditional PyPI API token in the shown workflow.
What the workflow does not currently do
There is no test, lint, type-check, dependency audit, or smoke-test step before publication. A broken API example or stale manual test can therefore coexist with a package build that still publishes successfully.
Migrating from 1.2.x to 1.3.0
The main compatibility change from 1.2.x is coordinate handling.
Old style
Cartons 1.2.x used separate coordinate arguments such as:
# Cartons 1.2.x style (no longer valid in 1.3.0)
cartons.route(base_url, lon1, lat1, lon2, lat2, transport="car")
and similarly for draw().
1.3.0 style
Use a list of [lon, lat] pairs:
coords = [
[lon1, lat1],
[lon2, lat2],
]
r = cartons.route(base_url, coords, "driving")
For draw():
m = cartons.draw(
base_url,
coords,
col="red",
weight=5,
tiles="CartoDB Positron",
attribution="© OpenStreetMap contributors © CARTO",
transport="driving",
marker=True,
)
New geometry helpers
Cartons 1.3.0 also exports:
cartons.line_string_route(...)
cartons.geo_json_geometry(...)
These helpers are part of the 1.3.0 public API.
Python requirement
Cartons 1.3.0 requires Python >=3.10. Cartons 1.2.x supported older Python versions, so upgrade Python before upgrading Cartons if necessary.
Known limitations
- No built-in geocoding: provide numeric coordinates yourself.
- No built-in OSRM server: you must configure one.
- No offline route calculation unless you point Cartons at a local/self-hosted OSRM service.
- No caching or request deduplication.
- No retries/timeouts exposed by the Cartons API.
- No explicit route-alternatives parameter.
- No turn-by-turn steps option exposed.
- No traffic-aware routing feature in Cartons itself.
- No waypoint-order optimization in the Cartons API.
- No automatic conversion between
[lon, lat]and[lat, lon]forsimpledraw()inputs. geo_json_geometry()returns geometry only, not a Feature/FeatureCollection.line_string_route()andgeo_json_geometry()have a parameter order inconsistent withroute().draw()requires all styling/tile parameters exceptmarker; it currently has no convenience defaults for them.draw()only marks the route's first/last returned geometry points, not every input waypoint.- Dependency versions are unpinned.
- Public API functions currently have minimal type hints and no docstrings in source.
- The project does not currently expose
__version__from the package module. - There is no automated test suite or CI test job in the repository.
Roadmap
Cartons 1.3.0 already includes:
- full route geometry retrieval through OSRM
- coordinate-list routing with ordered waypoints
- Folium route rendering
- automatic
fit_bounds() - direct coordinate drawing with
simpledraw() - Shapely
LineStringconversion - GeoJSON geometry export
Potential future areas include:
- stronger input validation and error messages
- automated tests and CI verification
- route-alternative controls
- richer OSRM options such as steps and annotations
- waypoint-order optimization
- caching/retry controls
- traffic-aware routing when supported by an appropriate routing backend/data source
- source-level docstrings, return annotations, and more complete typing
These are future ideas and are not part of the Cartons 1.3.0 API unless explicitly documented elsewhere in this README.
Troubleshooting
TypeError after upgrading from an older Cartons version
You are probably using the old separate-coordinate signature. Convert coordinates to a list:
coords = [[lon1, lat1], [lon2, lat2]]
Then call:
cartons.route(base_url, coords, "driving")
My route appears in the wrong country
Check coordinate order. Routing helpers use:
[longitude, latitude]
simpledraw() uses:
[latitude, longitude]
simpledraw() works, but route() does not
simpledraw() does not contact OSRM. Verify:
- internet/network access to your OSRM endpoint
base_url- server profile configuration
- that your coordinates fall inside the server's routing dataset
- that the points can snap to routable ways
The map route works but the basemap is blank
The generated HTML can load route geometry correctly while failing to load tiles. Check:
- tile URL/provider name
- browser network access
- provider access token, if needed
- provider request limits
- correct
{z}/{x}/{y}URL template - required attribution
Markers do not match my exact input coordinates
draw() marks the first and last returned route geometry positions, which can be snapped to the road network. This is expected from the current implementation.
line.length is much smaller than the road distance
A Shapely LineString built from longitude/latitude coordinates has length in coordinate-space degrees, not meters. Use route.distance for OSRM route distance or reproject geometry before performing metric Shapely calculations.
My old 1.2.x script fails after upgrading to 1.3.0
Check the installed version:
from importlib.metadata import version
print(version("cartons"))
Cartons 1.3.0 uses the coordinate-list API. Update old calls that supplied separate longitude/latitude arguments to pass an ordered coords list instead:
coords = [
[lon1, lat1],
[lon2, lat2],
]
route = cartons.route(base_url, coords, "driving")
See Migrating from 1.2.x to 1.3.0 for the compatibility notes.
Contributing
Issues, bug reports, documentation improvements, and pull requests are welcome.
A useful contribution flow is:
- fork the repository;
- create a focused branch;
- install the project in editable mode;
- update code and documentation together;
- manually verify routing and map output when the change touches route/display behavior;
- add or update automated tests if a test suite is introduced;
- submit a pull request describing the behavior change and compatibility impact.
Repository links:
For API-breaking changes, update this README in the same pull request so PyPI/GitHub documentation stays aligned with the code.
AI disclosure
AI Disclosure: This README was created with AI assistance. All code inside
cartons/cartonswas written by a human.
License
Cartons is licensed under the MIT License.
See otherfiles/LICENSE.
Copyright (c) 2026 Andrej Bajusic.
Acknowledgements
Cartons builds on excellent open-source geospatial tools:
- OSRM — route computation engine and API
- routingpy — Python routing-service interface
- Folium — interactive Leaflet map generation from Python
- Shapely — geometry objects and GeoJSON serialization
- OpenStreetMap and compatible tile/data providers used by routing/map deployments
Documentation scope
This README is intentionally both the project landing page and the primary documentation for Cartons 1.3.0. It documents the 1.3.0 API, packaging, architecture, coordinate conventions, examples, development workflow, release workflow, and known limitations in one place so users do not need a separate documentation site for a package of this size.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cartons-1.3.0.tar.gz.
File metadata
- Download URL: cartons-1.3.0.tar.gz
- Upload date:
- Size: 38.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2d538ac61e3125899dffb71ef10688a7322ebfb7c71774267f314cc59bbcd6a
|
|
| MD5 |
2d50f440c405545c38713eff9cf2e423
|
|
| BLAKE2b-256 |
2b69945668ae34f7ae884089f24d1de067d82e13f984b6e73d10ebc15701e331
|
Provenance
The following attestation bundles were made for cartons-1.3.0.tar.gz:
Publisher:
python-publish.yml on AndPan3/cartons
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cartons-1.3.0.tar.gz -
Subject digest:
a2d538ac61e3125899dffb71ef10688a7322ebfb7c71774267f314cc59bbcd6a - Sigstore transparency entry: 2784834969
- Sigstore integration time:
-
Permalink:
AndPan3/cartons@88db3d3320c9c64387ac3d5b723878d6ed229c0b -
Branch / Tag:
refs/tags/1.3.0 - Owner: https://github.com/AndPan3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@88db3d3320c9c64387ac3d5b723878d6ed229c0b -
Trigger Event:
release
-
Statement type:
File details
Details for the file cartons-1.3.0-py3-none-any.whl.
File metadata
- Download URL: cartons-1.3.0-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a0a7b71e728521534e91b7a7ac1c185f0dba84be805df71e41573752437d968
|
|
| MD5 |
1ae244378fce671b64db8c19e253b713
|
|
| BLAKE2b-256 |
5e47a05382022fa990b2afb1f4c404a718c47c0ad96b2de50536cf8b4e62e137
|
Provenance
The following attestation bundles were made for cartons-1.3.0-py3-none-any.whl:
Publisher:
python-publish.yml on AndPan3/cartons
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cartons-1.3.0-py3-none-any.whl -
Subject digest:
5a0a7b71e728521534e91b7a7ac1c185f0dba84be805df71e41573752437d968 - Sigstore transparency entry: 2784835005
- Sigstore integration time:
-
Permalink:
AndPan3/cartons@88db3d3320c9c64387ac3d5b723878d6ed229c0b -
Branch / Tag:
refs/tags/1.3.0 - Owner: https://github.com/AndPan3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@88db3d3320c9c64387ac3d5b723878d6ed229c0b -
Trigger Event:
release
-
Statement type: