npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

osmfeatures

v0.5.0

Published

Get OpenStreetMap features such as buildings, roads, or points of interest from dedicated servers with a single API call in GeoJSON, FlatGeobuf, Geoparquet, and CSV formats.

Readme

TypeScript OSM Features client

npm package for the MapLark OSM Features API. Fetch OpenStreetMap buildings, roads, parks, and POIs as GeoJSON, FlatGeobuf, GeoParquet, or CSV from Node.js or the browser without standing up Overpass or converting extracts by hand. Simply search by tag and bounding box or location + radius. The API keeps OSM semantics intact, like node, way, relation, and returns GeoJSON Features you can feed straight into Leaflet, MapLibre, OpenLayers, or any geospatial toolchain. Use this lib to build geospatial apps on OSM easily without hitting rate limits or setting up complex and expensive infrastructure yourself.

The backend is dedicated PostGIS, not the public Overpass endpoint, with API keys and rate limits so map tiles and POI queries stay fast under load. This SDK also covers local-search and mobility: amenity lookup, OSM opening hours, and walk or bicycle routing.

Contents

OSM types map to GeoJSON the way GIS tools expect:

  • node → Point
  • way → LineString or Polygon
  • relation → MultiPolygon or a bundle of geometries

Filters use ordinary OSM tags (amenity=cafe, building=yes). If you already write Overpass or edit OSM, the same keys work here. Drop a FeatureCollection into Leaflet, MapLibre, OpenLayers, or Turf.

Use way_shape when you need lines vs areas:

  • way_shape=line — unclosed ways (streets, footpaths, rivers) and line-like relations (routes, some boundaries)
  • way_shape=polygon — closed ways (building footprints, parks) and multipolygon relations
  • way_shape=all — both (the default if you leave it off)

Buildings in a box: type=way & tags=building — the same idea as Overpass way[building].

Quick start

npm install osmfeatures
import { OSMFeatures } from 'osmfeatures';

const client = new OSMFeatures('sk-...');
const page = await client.query({
  bbox: '18.06,59.32,18.09,59.34',
  tags: ['building'],
});

// Get GeoJSON FeatureCollection
console.log(page.data.features.length);

// Header meta for paging + usage
console.log(page.meta.has_more, page.meta.next_cursor, page.meta.units_charged);

// Binary / table encodings via Accept param
const fgb = await client.query({
  bbox: '18.06,59.32,18.09,59.34',
  tags: ['building'],
  accept: 'application/flatgeobuf',
});
console.log(fgb.data);
console.log(fgb.meta.has_more, fgb.meta.next_cursor);

Talks to https://api.maplark.com by default.

Functions and Parameters

query()

Fetches a single page from the API. Returns { data, meta } where data is a GeoJSON FeatureCollection (default) or an ArrayBuffer for binary encodings.

Spatial anchors (required)

The geographical area for the request in terms of GPS coordinates or specific OSM ids.

| Param | Type | Description | | -------- | -------- | -------------------------------------------------- | | bbox | string | Bounding box as min_lon,min_lat,max_lon,max_lat. | | location | string | Point for a radius search as lat,lng. Requires radius. | | radius | number | Search radius in metres. Requires location. | | within | string | Polygon spatial anchor as way/<id> or relation/<id>. Mutually exclusive with bbox / location. | | osmIds | string | Comma-separated OSM IDs to fetch by id. |

Tags

The feature tags to filter on.

| Param | Type | Description | | --------- | ---------- | -------------------------------------------------------------------------------- | | tags | string[] | Tag filters that must all match (AND). Values like building or amenity=cafe. | | orTags | string[] | Tag filters where any may match (OR). | | notTags | string[] | Tag filters to exclude. |

Geometry

Geometric filters such specific OSM element type, min length, or including centroid.

| Param | Type | Default | Description | | ------------ | ---------------------- | ------- | --------------------------------------------------------------------------------------- | | type | string | all | OSM element types, e.g. node, way, relation, or comma-separated (way,relation). | | wayShape | line | polygon | all | all | Geometry class for ways and relations. shape is a deprecated alias. | | centroid | boolean | false | When true, include a centroid on non-point features. | | clipGeometry | boolean | true | When true, clip returned geometry to the requested bbox. Set false for full geometry. | | minLengthM | number | | Minimum length in metres (lines). | | maxLengthM | number | | Maximum length in metres (lines). | | minAreaM2 | number | | Minimum area in square metres (polygons). | | maxAreaM2 | number | | Maximum area in square metres (polygons). |

Other

Extra filters to for pagination, output format (accept),

| Param | Type | Default | Description | | ---------------------- | --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | accept | string | application/geo+json | Response media type in header. Options - application/geo+json, text/csv, text/tab-separated-values, application/flatgeobuf, and application/vnd.apache.parquet. | | limit | number | API 1000 | Page size. Omit to use the API default. Max 6000. | | cursor | string | | Pagination cursor from a previous meta.next_cursor. | | disableBudgetWarning | boolean | false | Ignore warnings for large queries that consume budget quotas. | | zoom | number | | Map zoom hint (used by presets / server-side simplification policies). |

Meta

Fields for pagination and usage.

| Field | Description | | --------------- | --------------------------------------------------------------------- | | returned | Features in this page. | | has_more | Whether more pages exist. | | next_cursor | Pass as cursor on the next query call, or null when done. | | units_charged | Usage for this request when present in terms of cpu and ram consumed. |

query_all

Auto-paginates (and optionally tiles the bbox) until the result is complete or a client-side cap is hit. GeoJSON only — for FlatGeobuf / other encodings, use query with accept.

Does not take limit or cursor; paging is handled internally. Does not tile when within is set (bboxTiles is ignored).

const all = await client.query_all({
  bbox: '18.06,59.32,18.09,59.34',
  tags: ['building'],
  limitPerPage: 1000,
  bboxTiles: 2,
  maxPages: 15,
  maxFeatures: 55_000,
});

console.log(all.data.features.length);
console.log(all.meta.page_count, all.meta.has_more, all.meta.units_charged);

Params

Same filter params as query (bbox, tags, orTags, notTags, type, wayShape, zoom, location, radius, within, osmIds, minLengthM, maxLengthM, minAreaM2, maxAreaM2, centroid, clipGeometry, disableBudgetWarning), plus:

| Param | Type | Default | Description | | -------------- | --------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | limitPerPage | number | API 1000 | Upstream limit per HTTP request (page size). Omit to use the API default. | | bboxTiles | number | 2 | Split bbox into this many tiles (must be a power of 2: 1, 2, 4, 8, …). Each tile is paginated separately, then features are merged and deduped. | | maxPages | number | 15 | Max pages fetched per tile. | | maxFeatures | number | null | 55000 | Cap on merged features after dedupe. Pass null for no cap. | | accept | string | application/geo+json | Must be GeoJSON (or omitted). Non-GeoJSON throws. |

Meta

Same fields as query, plus:

| Field | Description | | ---------------------- | ------------------------------------------------------------------------------------ | | page_count | Total upstream pages fetched. | | has_more | true if stopped early (caps), or upstream still had more, or a partial relay stop. | | next_cursor | Last cursor when incomplete; otherwise the final page cursor. | | units_charged | Sum of units charged across pages when present. | | relay_partial | true if paging stopped after a mid-stream 400/429 (partial result kept). | | relay_partial_reason | e.g. upstream_rejected_cursor or upstream_rate_limited_after_retries. |

Also exports layer presets (resolveLayerFromQuery, OSM_FEATURES_LAYER_PRESETS, ...), GeoJSON payload helpers (featureCentroid, geometryBounds, parseFeatureId, ...), and local helpers (nearest_within, point_in_geometry, isOpenNow).

stats

Count features grouped by a tag key via GET /v2/osm_features/stats. Returns { groups, total, truncated }. Spatial windows are larger than query (country-scale on every tier) and billed count-only. limit is max histogram buckets (API default 100), not a scan cap. groupBy is required. Same tag filters as query; no osmIds, cursor, zoom, centroid, or clipGeometry. Map Express/query strings with resolveStatsRequest (requires group_by).

const histogram = await client.stats({
  groupBy: 'amenity',
  bbox: '18.05,59.32,18.10,59.34',
  type: 'node',
  tags: ['amenity'],
});
console.log(histogram.total, histogram.groups);

City boundary:

const mix = await client.stats({
  groupBy: 'amenity',
  within: 'relation/398021',
  tags: ['amenity'],
});

estimate_cost

Preflight credit cost via GET /v2/osm_features/cost. Same filter params as query. No OSM data is fetched.

const estimate = await client.estimate_cost({
  bbox: '18.06,59.32,18.09,59.34',
  tags: ['building'],
});
console.log(estimate.estimated_credits);

usage

This month's unit-budget usage via GET /v1/usage.

const usage = await client.usage();
console.log(usage.tier, usage.usage_this_month, usage.remaining_this_month);

Places and routes

query() is the raw OSM layer: footprints, highways, park polygons, any tag and geometry class. Places and routes sit on the same planet extract but answer product questions: amenities in a box, ranked POIs from a pin, opening hours, walk/bike isochrones, and multi-stop paths. You supply tags, extent, time, and WALK or BICYCLE. The API returns coordinates, openNow, distances, and network geometry.

Unset fields are omitted so server defaults apply (places_search limit 100, places_nearby radius 1000 m and limit 100, loop true). HTTP docs: maplark.com/developer.

Places search

places_search() looks up POIs inside a bounding box or around { lat, lon } + radius (pick one). tags is AND; orTags is OR; both use the same OSM keys as query(). Leave limit off for the API default (100, max 10_000).

const origin = { lat: 59.316, lon: 18.075 };

const cafes = await client.places_search({
  location: origin,
  radius: 800,
  orTags: ['amenity=cafe'],
  openNow: true,
  asOf: '2026-08-10T18:00:00+02:00',
});

Nearby (ranked from a point)

places_nearby() is “what is closest to this coordinate?”. You must pass tags or orTags. Hits are ordered by straight-line spheroid distance. Defaults if omitted: 1000 m radius, 100 results.

const nearby = await client.places_nearby({
  location: origin,
  orTags: ['amenity=cafe'],
  limit: 5,
  openNow: true,
  asOf: '2026-08-10T18:00:00+02:00',
});

Place details

places_details() reloads a single OSM place by the id search or nearby gave you (node/123), or as { osmType, osmId }.

const first = (cafes.features as { id: string }[])[0];
const details = await client.places_details({ osmType: first.id });
// same as: client.places_details({ osmType: 'node', osmId: 123 })

Hours are evaluated at request time in that place’s timezone.

Opening hours

When OSM opening_hours can be parsed, the feature gets properties.openNow as true or false. Missing or junk hours omit the field. isOpenNow(feature) keeps known-open places and unwraps the details { feature } envelope.

Timezone is inferred from coordinates (IANA). There is no timezone request field.

  • openNow: true drops closed and unknown-hours POIs (Google Places–style openNow).
  • asOf is the evaluation instant (default: now). An offset (Z, +02:00) is an absolute instant. A naive 2026-08-10T20:00:00 is local clock at the search point or bbox centre.
  • asOf or openNow also require an opening_hours tag, so untagged amenities do not pad the page.
  • Places that are closed but tagged still appear unless openNow is set.

"X near Y" (local join)

places_nearby ranks against one origin. “Restaurants within 150 m of a station” is two searches plus an in-process join. nearest_within does not hit the API.

import { nearest_within } from 'osmfeatures';

const bbox = '18.05,59.33,18.10,59.36';
const restaurants = await client.places_search({ bbox, orTags: ['amenity=restaurant'] });
const stations = await client.places_search({ bbox, orTags: ['railway=station'] });
const pairs = nearest_within(restaurants, stations, 150, { limit: 20 });

for (const pair of pairs) {
  console.log(pair.distance_m, pair.feature, 'near', pair.nearest);
}

Each pair is { feature, distance_m, nearest }. The point is geometry when it is a Point, otherwise properties.centroid (same as featureCentroid). Neither present throws. Empty secondary → []. Distances are spherical haversine (mean Earth radius 6371000 m). limit keeps the closest pairs (default 20); { limit: null } returns every primary with a match. More than 500000 comparisons throws — lower places_search / places_nearby limit, do not use query_all. { data, meta } from query() / query_all() is accepted.

Walk and bike routes

Paths follow OSM walk and bicycle ways. Default travelMode is WALK; pass 'BICYCLE' for bikes. Driving is not offered yet.

Coordinates take lon or lng. For routes_isochrone, set exactly one of maxDistanceM or durationS. searchBufferM widens the highway fetch if the default corridor cannot form a path.

const origin = { lon: 18.075, lat: 59.316 };
const cafe = { lon: 18.08, lat: 59.318 };

const iso = await client.routes_isochrone({
  origin,
  durationS: 600,
});

const path = await client.routes_path({
  stops: [origin, cafe],
});

const tour = await client.routes_optimized_path({
  start: origin,
  stops: [cafe],
});

const office = { lon: 18.08, lat: 59.318 };
point_in_geometry(office.lon, office.lat, iso);

In-process (no HTTP): nearest_within(primary, secondary, maxDistanceM) for proximity joins, point_in_geometry(lon, lat, geom) for isochrone containment. The latter accepts a Polygon/MultiPolygon, a Feature, a GeometryCollection, or { geometry } from the isochrone response.

MCP server

Maplark has an MCP server to integrate OpenStreetMap data into AI and LLMs such as Claude, Cursor, and Copilot. However, it is implemented in another Python sister repo. See maplark.com/products/mcp-server for more details.