Build a Restaurant Discovery App

Fetch restaurants in any area with names, cuisine types, and map coordinates using live OpenStreetMap data. This example shows how to handle the two ways OSM mappers represent restaurants - as a single point node or as a full building footprint - and how the API's centroid field makes placing map pins effortless in both cases.

amenity=restaurantcentroidnodes + areasPython

What you'll build

A restaurant discovery card list with name, cuisine type, and a map pin coordinate for every restaurant in a given bounding box. The same pattern powers food-finder apps, travel guides, and local search features. Because OSM mappers model restaurants in two different ways - a simple point node, or a building footprint polygon - you need to handle both geometry types. The MapLark API makes this trivial: every polygon feature includes a precomputed properties.centroid (a GeoJSON Point) so you always have a pin coordinate without any extra computation.

Prerequisites

Run these commands on a Bash terminal.

Python
pip install osmfeatures

Script

View full source code: test_restaurant_guide.py

Python
"""
App idea: restaurant discovery card list with name, cuisine, and a map pin.

Gamla Stan is Stockholm's old town and a popular tourist dining destination.
Restaurants can be mapped as nodes (a single point) or as building footprints
(ways); the centroid is used as the display coordinate for polygons.

API call: ``type=node,way&tags=amenity=restaurant``
"""

from collections import defaultdict

from osmfeatures import OSMFeature, OSMFeatureCollection, OSMFeaturesClient

GAMLA_STAN_BBOX = "18.063,59.322,18.082,59.332"


def test_restaurant_guide(client: OSMFeaturesClient):
    data = client.query(
        bbox=GAMLA_STAN_BBOX,
        type="node,way",
        tags="amenity=restaurant",
        limit=50,
    )
    assert isinstance(data, OSMFeatureCollection)
    assert len(data["features"]) > 0, "Expected restaurants in Gamla Stan"

    named = []
    cuisines: dict[str, int] = defaultdict(int)

    for f in data["features"]:
        assert isinstance(f, OSMFeature)
        tags = f.tags
        assert tags.get("amenity") == "restaurant"

        # Derive a display coordinate: use the centroid for polygon restaurants,
        # or the point geometry directly for node restaurants.
        if f["geometry"]["type"] == "Point":
            display_coord = f["geometry"]["coordinates"]
        else:
            assert f.centroid is not None
            display_coord = f.centroid["coordinates"]

        assert len(display_coord) == 2

        if tags.get("name"):
            named.append(tags["name"])
        if tags.get("cuisine"):
            cuisines[tags["cuisine"]] += 1

    assert len(named) > 0, "Expected at least some named restaurants"

    print(f"\n[restaurant guide] {len(data['features'])} restaurants, {len(named)} named")
    print(f"  Sample names: {named[:5]}")
    print(f"  Cuisines: {dict(sorted(cuisines.items(), key=lambda x: -x[1])[:5])}")

How it works

This query sets type=node,way so the API returns point nodes and way footprints, and skips relations. That matches how restaurants are usually mapped in OSM. Filtering to type=node alone would silently miss any restaurant mapped as a building footprint.

ParameterValueWhat it does
bboxmin_lon,min_lat,max_lon,max_latSpatial anchor - required when using tag filters without osm_ids.
tagsamenity=restaurantExact tag match. Multiple tags values are AND-combined - useful for querying restaurants of a specific cuisine.
typenode,wayNodes (points) and ways (footprints). Excludes relations. Needed so polygon-mapped restaurants are included.
The centroid pattern: Every non-point feature (Polygon, MultiPolygon, LineString) returned by the API includes properties.centroid - a precomputed GeoJSON Point at the geometric centre. Use it directly as the map pin location. For type=node Point features, centroid is absent; read from geometry.coordinates instead.

Adapt it

Swap the tags value to build any dining or hospitality guide:

Tags valueWhat you get
amenity=cafeCoffee shops and cafés
amenity=barBars and pubs
amenity=fast_foodFast food outlets
amenity=food_courtFood courts
tourism=hotelHotels (use centroid for the map pin)

Try it live

Run this query against real Stockholm data - no setup required.