OSM Features PostGIS Schema

OpenStreetMap lives in three tables: one for points, one for lines, one for polygons. A cafe is a point. A road is a line. A park is a polygon. Each row is one feature: an OSM id, a geometry, common tags as their own columns, and the rest in tags.

For fast, low-latency queries, you need to narrow the search to a specific table. Set type and way_shape to do so. Geometry is EPSG:4326. The rows below are examples of what is stored.

Geometry Tables

Points

Nodes. Always a Point. Cafes, shops, benches, peaks, bus stops. Query with type=node.

osm.osm_point
osm_idosm_typetagsgeom
412883101N{"name": "Vete-Katten", "amenity": "cafe"}POINT(18.0649 59.3326)
266091882N{"name": "Fabrique", "shop": "bakery"}POINT(18.0712 59.3341)
178220340N{"amenity": "bench", "backrest": "yes"}POINT(18.0731 59.3388)

Lines

Roads, paths, rivers, route relations, and the line copy of a boundary. Closed loops such as roundabouts stay here. Query with type=way&way_shape=line. Filter length with min_length_m and max_length_m.

osm.osm_line
osm_idosm_typelength_mtagsgeom
231501210W142.3{"name": "Drottninggatan", "highway": "residential"}LINESTRING(18.061 59.333, 18.064 59.332)
88441209W87.1{"highway": "footway", "surface": "paving_stones"}LINESTRING(18.072 59.339, 18.073 59.338)
15600344W2410.5{"name": "Norrström", "waterway": "river"}LINESTRING(18.069 59.328, 18.072 59.327)

Polygons

Buildings, parks, lakes, landuse, and boundary areas. Query with type=way&way_shape=polygon. Filter area with min_area_m2 and max_area_m2.

osm.osm_polygon
osm_idosm_typebuildingarea_m2tagsgeom
24876102Wyes186.4{"building": "yes", "addr:housenumber": "12"}POLYGON((18.070 59.334, 18.071 59.334, 18.071 59.333, 18.070 59.333, 18.070 59.334))
99120411WNULL45200{"name": "Humlegården", "leisure": "park"}POLYGON((18.072 59.339, 18.078 59.339, 18.078 59.336, 18.072 59.336, 18.072 59.339))
4401288RNULL85000{"name": "Brunnsviken", "natural": "water"}POLYGON((18.043 59.361, 18.055 59.361, 18.055 59.354, 18.043 59.354, 18.043 59.361))

Every row has osm_id, osm_type (N, W, or R), tags, and geom. API response ids look like node/123 or way/456. Filter any key that is not its own column through tags: key, key=value, or a numeric compare such as ele>500.

How to pick a table

type is the OSM element: node, way, or relation. way_shape is the geometry class for ways and relations: line or polygon. Nodes are always points, so they ignore way_shape. Omit both parameters to read all three tables.

You ask forThe API reads
type=nodeosm_point
type=way & way_shape=line or type=relation & way_shape=lineosm_line
type=way & way_shape=polygonosm_polygon
type=node,way & way_shape=polygonpoints plus polygons
omit bothpoints, lines, and polygons

limit is the maximum number of features in the response (free tier is 75000 features). A mixed-geometry query reads each matching table, unions the rows, and applies that cap once. Set way_shape when you know the shape so the query reads one table. Parameter reference: OSM Features API.

Indexes

Filters that match these stay fast. Common tag keys use their own column index. Other keys use the tags index.

IndexTableWhat it speeds up
GiST on geompoint, line, polygonBounding box and around searches.
GIN on tagspoint, line, polygonTag filters for keys without their own column.
btree on osm idpoint, line, polygonLookup by OSM id, and paging.
btree on namepoint, line, polygonName filters.
btree on length_mlineLine length filters.
btree on area_m2polygonPolygon area filters.
btree on common tagspoint, line, polygonFilters such as amenity, highway, and building.

How to plan a query

  1. Bound the search: a viewport bbox, location plus radius, within an OSM polygon, or osm_ids. Geocode named places first.
  2. Set type and way_shape when the shape is known (buildings: polygon; roads: line; benches: node).
  3. Filter with native OSM tags. Prefer key=value over a bare key. There is no proprietary category tree.
  4. Keep a spatial bound. Country-wide tag scans are not a supported query shape.
  5. Count with /v2/osm_features/count.
  6. If an OR group has five or more tags (tags=a|b|c|d|e), split it into separate queries with a few tags each and merge the results. One large OR is much slower.
  7. Regex (name~"^Star") cannot use an index. It filters rows already found by your bbox and indexed tags, so pair it with a tag such as amenity=cafe&name~"^Star". A regex-only query stays broad and gets the no-tag area cap.

See also