Skip to content

Points of Interest

A .yoromaps bundle carries the places people actually look for — pharmacies, schools, markets, bus stations — alongside the road graph. They come from two sources that live side by side in the same table, and each one is addressed by its own Yoro code, so a client with no network can still say where a place is.

Where POIs come from

Source pois.source Written by
OpenStreetMap osm extract_pois(), run by build() and update()
Anything else local (default) add_poi(), or your own importer

extract_pois() deletes only the rows carrying its own source before inserting. A rebuild therefore refreshes OpenStreetMap without ever destroying a POI that came from somewhere else. The delete and the insert share one transaction, so a failure mid-extraction leaves the previous set in place.

OSM extraction

Extraction runs as part of a build, on by default:

yoromaps download ML --output mali.yoromaps          # roads + POIs
yoromaps download ML --output mali.yoromaps --no-pois  # roads only
from yoromaps.download import build

build("ML", "mali.yoromaps")  # roads + POIs
build("ML", "mali.yoromaps", include_pois=False)

Or on its own, against a bundle you already have:

from yoromaps import extract_pois, open_db

conn = open_db("mali.yoromaps")
stats = extract_pois("mali-latest.osm.pbf", conn)
print(stats["pois"], "POIs from", stats["nodes"], "nodes and", stats["ways"], "ways")

Extraction walks nodes and ways alike. A school is as often a polygon as a point, and a bundle that only read nodes would miss most of them; a way is reduced to the centroid of its vertices.

Features with no name are skipped. A pin with no label is noise on a map whose whole purpose is telling someone where to go.

Categories are an allowlist

categorize() maps OSM tags onto a fixed, explicit list. Anything not on the list is ignored, never guessed at:

>>> from yoromaps import categorize
>>> categorize({"amenity": "pharmacy"})
'pharmacy'
>>> categorize({"shop": "bakery"})
'shop'
>>> categorize({"highway": "residential"}) is None
True

The eleven categories are government, hospital, landmark, market, other, pharmacy, place_of_worship, restaurant, school, shop, transport.

A denylist would let every new tag in OSM leak into the bundle as a other-shaped surprise. The allowlist keeps the taxonomy small enough to put in front of a user as a filter — but it only stays useful if it grows, so what was skipped is counted and handed back:

stats = extract_pois("mali-latest.osm.pbf", conn)
for (key, value), count in stats["unmapped"][:10]:
    print(f"{key}={value}: {count}")

Grow the allowlist from that list, not from intuition. Counting is limited to the keys a taxonomy would plausibly grow into (amenity, tourism, leisure, office) — reporting every rejected key would just report roads back at you.

Precision

Extracted POIs are addressed at precision 19 (~1.4 m cells), not yoro's default of 12 (~400 m). At 12, every shop on a block answers to the same code: fine for a coarse cell query, useless as an address. Since the bundle's code is the only address a disconnected client has, each POI gets its own.

extract_pois(pbf, conn, precision=12)  # deliberately share codes between neighbours

Adding your own

from yoromaps import add_poi, delete_poi

poi = add_poi(conn, lat=12.6392, lon=-8.0029, name="Pharmacie du Fleuve", category="pharmacy")
print(poi["yoro_code"])
delete_poi(conn, poi["id"])

The Yoro code is computed at insertion time in the database's country domain, falling back to the worldwide XX domain for a point outside the country's bounding box.

Querying

from yoromaps import pois_near, pois_in_cell, search_pois

pois_near(conn, lat=12.6392, lon=-8.0029, radius_m=500, category="pharmacy")
pois_in_cell(conn, "ML-4H7A3B")
search_pois(conn, query="fleuve", category="pharmacy")

Every result carries the same keys — id, lat, lon, yoro_code, name, category, source, osm_id, distance_m — with osm_id and distance_m set to None where they do not apply. A caller that has to test whether a key exists before reading it ends up writing that test at every call site.

From the CLI:

yoromaps poi list mali.yoromaps --near 12.6392,-8.0029 --radius 500
yoromaps poi list mali.yoromaps --cell ML-4H7A3B --category pharmacy
yoromaps poi list mali.yoromaps --query fleuve

See File Format for the pois table schema.