osmfeatures 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/geojson-feature.d.ts +25 -0
- package/dist/geojson-feature.js +108 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +497 -0
- package/dist/preset/buildings-min-area.d.ts +3 -0
- package/dist/preset/buildings-min-area.js +30 -0
- package/dist/preset/catalog.d.ts +13 -0
- package/dist/preset/catalog.js +127 -0
- package/dist/preset/index.d.ts +30 -0
- package/dist/preset/index.js +30 -0
- package/dist/preset/public-transport-zoom.d.ts +4 -0
- package/dist/preset/public-transport-zoom.js +39 -0
- package/dist/preset/resolve.d.ts +35 -0
- package/dist/preset/resolve.js +133 -0
- package/dist/preset/roads-paths-zoom.d.ts +4 -0
- package/dist/preset/roads-paths-zoom.js +37 -0
- package/dist/preset/waterways-zoom.d.ts +4 -0
- package/dist/preset/waterways-zoom.js +22 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MapLark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# MapLark OSM Features API client
|
|
2
|
+
|
|
3
|
+
Official TypeScript/JavaScript client for the [MapLark OSM Features API](https://maplark.com) (GeoJSON, FlatGeobuf, GeoParquet, CSV).
|
|
4
|
+
|
|
5
|
+
Query OpenStreetMap features such as buildings, streets, and POIs easily. Search for OSM features by bounding box, tags, and geometry shape and get GeoJSON back within less than 250ms (dependent on query size). No converting between formats manually. The API keeps OpenStreetMap semantics intact, like tags and ways, and returns GeoJSON Features you can feed straight into Leaflet, MapLibre, OpenLayers, or any geospatial toolchain. It is backed by postgis with tiered API keys and rate limiting to keep noisy neighbours out to give you predictable latency for real traffic. It also has self-host path for those willing to host complex infrastructure themselves.
|
|
6
|
+
|
|
7
|
+
The translation layer is very simple:
|
|
8
|
+
|
|
9
|
+
- `node` - GIS Point
|
|
10
|
+
- `way` - LineString or Polygon
|
|
11
|
+
- `relation` - MultiPolygon or grouped geometries
|
|
12
|
+
|
|
13
|
+
You filter with the same tags mappers already use (`amenity=cafe`, `building=yes`, and so on). Knowledge from OSM, Overpass, and tagging docs transfers immediately.
|
|
14
|
+
|
|
15
|
+
To narrow down between "open ways" and "closed ways", use the `shape` parameter:
|
|
16
|
+
|
|
17
|
+
- `shape=line` - open ways (roads, paths, rivers) or line-shaped relations (routes, boundaries)
|
|
18
|
+
- `shape=polygon` - closed ways (buildings, parks) or multipolygon relations.
|
|
19
|
+
- `shape=all` - both shapes (default when shape is omitted).
|
|
20
|
+
|
|
21
|
+
For example, to get all buildings in an area:
|
|
22
|
+
|
|
23
|
+
`type=way & tags=building`
|
|
24
|
+
|
|
25
|
+
This is the equivalent of the Overpass query `way[building]`.
|
|
26
|
+
|
|
27
|
+
Read the full API reference here [https://maplark.com/developer](https://maplark.com/developer).
|
|
28
|
+
|
|
29
|
+
# How to use it
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# From npm (when published):
|
|
33
|
+
npm install osmfeatures
|
|
34
|
+
|
|
35
|
+
# Until then, from git:
|
|
36
|
+
npm install git+https://github.com/MapLark/osmfeatures-ts.git
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { OSMFeatures } from 'osmfeatures';
|
|
41
|
+
|
|
42
|
+
const client = new OSMFeatures('sk-...');
|
|
43
|
+
const page = await client.query({
|
|
44
|
+
bbox: '18.06,59.32,18.09,59.34',
|
|
45
|
+
tags: ['building'],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Vanilla GeoJSON FeatureCollection (same shape as the HTTP body).
|
|
49
|
+
map.getSource('buildings').setData(page.data);
|
|
50
|
+
console.log(page.data.features.length);
|
|
51
|
+
|
|
52
|
+
// Header-derived meta (not part of GeoJSON): paging + usage.
|
|
53
|
+
console.log(page.meta.has_more, page.meta.next_cursor, page.meta.units_charged);
|
|
54
|
+
|
|
55
|
+
// Binary / table encodings via Accept — same { data, meta } shape.
|
|
56
|
+
const fgb = await client.query({
|
|
57
|
+
bbox: '18.06,59.32,18.09,59.34',
|
|
58
|
+
tags: ['building'],
|
|
59
|
+
accept: 'application/flatgeobuf',
|
|
60
|
+
});
|
|
61
|
+
console.log(fgb.data instanceof ArrayBuffer, fgb.meta.has_more, fgb.meta.next_cursor);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Talks to `https://api.maplark.com` by default.
|
|
65
|
+
|
|
66
|
+
Also exports layer presets (`resolveLayerFromQuery`, `OSM_FEATURES_LAYER_PRESETS`, …)
|
|
67
|
+
and GeoJSON payload helpers (`featureCentroid`, `geometryBounds`, `parseFeatureId`, …).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Helpers for MapLark OSM Features API GeoJSON payloads. */
|
|
2
|
+
export type GeometryGroupId = 'points' | 'lines' | 'polygons';
|
|
3
|
+
export type GeoJSONGeometryLike = {
|
|
4
|
+
type: string;
|
|
5
|
+
coordinates?: unknown;
|
|
6
|
+
} | null;
|
|
7
|
+
export type QueryFeatureLike = {
|
|
8
|
+
id?: string | number;
|
|
9
|
+
properties?: Record<string, unknown>;
|
|
10
|
+
geometry: GeoJSONGeometryLike;
|
|
11
|
+
};
|
|
12
|
+
export declare function geometryGroup(geometryType: string): GeometryGroupId | null;
|
|
13
|
+
export declare function parseFeatureId(id: string | number | undefined): {
|
|
14
|
+
type: string;
|
|
15
|
+
osmId: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function readTags(properties: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
18
|
+
/** `[[minLon, minLat], [maxLon, maxLat]]`, or null when the geometry has no coordinates. */
|
|
19
|
+
export declare function geometryBounds(geometry: GeoJSONGeometryLike): [[number, number], [number, number]] | null;
|
|
20
|
+
/**
|
|
21
|
+
* Feature center as `[lon, lat]`.
|
|
22
|
+
* Non-points: `properties.centroid` from PostGIS (`centroid=true`).
|
|
23
|
+
* Points: the Point coordinates themselves (API omits centroid on nodes).
|
|
24
|
+
*/
|
|
25
|
+
export declare function featureCentroid(feature: QueryFeatureLike): [number, number] | null;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** Helpers for MapLark OSM Features API GeoJSON payloads. */
|
|
2
|
+
export function geometryGroup(geometryType) {
|
|
3
|
+
if (geometryType === 'Point' || geometryType === 'MultiPoint') {
|
|
4
|
+
return 'points';
|
|
5
|
+
}
|
|
6
|
+
if (geometryType === 'LineString' || geometryType === 'MultiLineString') {
|
|
7
|
+
return 'lines';
|
|
8
|
+
}
|
|
9
|
+
if (geometryType === 'Polygon' || geometryType === 'MultiPolygon') {
|
|
10
|
+
return 'polygons';
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
export function parseFeatureId(id) {
|
|
15
|
+
if (typeof id === 'number' && Number.isFinite(id)) {
|
|
16
|
+
return { type: '', osmId: String(id) };
|
|
17
|
+
}
|
|
18
|
+
if (typeof id !== 'string' || id.trim() === '') {
|
|
19
|
+
return { type: '', osmId: '' };
|
|
20
|
+
}
|
|
21
|
+
const slash = id.indexOf('/');
|
|
22
|
+
if (slash <= 0 || slash === id.length - 1) {
|
|
23
|
+
return { type: '', osmId: id };
|
|
24
|
+
}
|
|
25
|
+
return { type: id.slice(0, slash), osmId: id.slice(slash + 1) };
|
|
26
|
+
}
|
|
27
|
+
export function readTags(properties) {
|
|
28
|
+
const tags = properties?.['tags'];
|
|
29
|
+
if (tags && typeof tags === 'object' && !Array.isArray(tags)) {
|
|
30
|
+
return tags;
|
|
31
|
+
}
|
|
32
|
+
if (typeof tags === 'string') {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(tags);
|
|
35
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
function collectPositions(coordinates, out) {
|
|
46
|
+
if (!Array.isArray(coordinates) || coordinates.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (typeof coordinates[0] === 'number') {
|
|
50
|
+
out.push(coordinates);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const item of coordinates) {
|
|
54
|
+
collectPositions(item, out);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** `[[minLon, minLat], [maxLon, maxLat]]`, or null when the geometry has no coordinates. */
|
|
58
|
+
export function geometryBounds(geometry) {
|
|
59
|
+
const positions = [];
|
|
60
|
+
collectPositions(geometry?.coordinates, positions);
|
|
61
|
+
if (positions.length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
let minLon = Infinity;
|
|
65
|
+
let minLat = Infinity;
|
|
66
|
+
let maxLon = -Infinity;
|
|
67
|
+
let maxLat = -Infinity;
|
|
68
|
+
for (const [lon, lat] of positions) {
|
|
69
|
+
if (lon < minLon)
|
|
70
|
+
minLon = lon;
|
|
71
|
+
if (lon > maxLon)
|
|
72
|
+
maxLon = lon;
|
|
73
|
+
if (lat < minLat)
|
|
74
|
+
minLat = lat;
|
|
75
|
+
if (lat > maxLat)
|
|
76
|
+
maxLat = lat;
|
|
77
|
+
}
|
|
78
|
+
return [[minLon, minLat], [maxLon, maxLat]];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Feature center as `[lon, lat]`.
|
|
82
|
+
* Non-points: `properties.centroid` from PostGIS (`centroid=true`).
|
|
83
|
+
* Points: the Point coordinates themselves (API omits centroid on nodes).
|
|
84
|
+
*/
|
|
85
|
+
export function featureCentroid(feature) {
|
|
86
|
+
const group = geometryGroup(feature.geometry?.type?.trim() ?? '');
|
|
87
|
+
if (group === 'points') {
|
|
88
|
+
const coords = feature.geometry?.coordinates;
|
|
89
|
+
if (feature.geometry?.type === 'Point' && Array.isArray(coords) && typeof coords[0] === 'number') {
|
|
90
|
+
return [coords[0], coords[1]];
|
|
91
|
+
}
|
|
92
|
+
if (feature.geometry?.type === 'MultiPoint' && Array.isArray(coords) && coords.length === 1) {
|
|
93
|
+
const point = coords[0];
|
|
94
|
+
if (typeof point?.[0] === 'number') {
|
|
95
|
+
return [point[0], point[1]];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const centroid = feature.properties?.['centroid'];
|
|
101
|
+
if (centroid && typeof centroid === 'object' && !Array.isArray(centroid)) {
|
|
102
|
+
const coords = centroid.coordinates;
|
|
103
|
+
if (Array.isArray(coords) && typeof coords[0] === 'number' && typeof coords[1] === 'number') {
|
|
104
|
+
return [coords[0], coords[1]];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Response metadata from headers (not part of GeoJSON body). */
|
|
2
|
+
export type OSMFeaturesMeta = {
|
|
3
|
+
returned: number;
|
|
4
|
+
has_more: boolean;
|
|
5
|
+
next_cursor: string | null;
|
|
6
|
+
/** From ``X-Usage-Units-Charged`` when present (authenticated API). */
|
|
7
|
+
units_charged?: number;
|
|
8
|
+
/** Set by ``query_all`` only (pages fetched). */
|
|
9
|
+
page_count?: number;
|
|
10
|
+
relay_partial?: boolean;
|
|
11
|
+
relay_partial_reason?: string;
|
|
12
|
+
};
|
|
13
|
+
/** Wire GeoJSON FeatureCollection (no pagination fields). */
|
|
14
|
+
export type OSMGeoJSONPayload = {
|
|
15
|
+
type: 'FeatureCollection';
|
|
16
|
+
features: unknown[];
|
|
17
|
+
};
|
|
18
|
+
/** GeoJSON page: FeatureCollection body + header-derived meta. */
|
|
19
|
+
export type OSMGeoJSONResult = {
|
|
20
|
+
data: OSMGeoJSONPayload;
|
|
21
|
+
meta: OSMFeaturesMeta;
|
|
22
|
+
};
|
|
23
|
+
/** Any Accept: GeoJSON page, or raw bytes in ``data`` for binary encodings. */
|
|
24
|
+
export type OSMFeaturesResult = {
|
|
25
|
+
data: OSMGeoJSONPayload | ArrayBuffer;
|
|
26
|
+
meta: OSMFeaturesMeta;
|
|
27
|
+
};
|
|
28
|
+
/** Layer filters from presets / custom resolve. Pass into `resolveRequest` or spread into `query`. */
|
|
29
|
+
export type OSMFeaturesLayer = {
|
|
30
|
+
bbox: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
orTags?: string[];
|
|
33
|
+
notTags?: string[];
|
|
34
|
+
type?: string;
|
|
35
|
+
shape?: 'line' | 'polygon' | 'all';
|
|
36
|
+
};
|
|
37
|
+
/** Flat query params (same idea as Python `query(**params)`). */
|
|
38
|
+
export type OSMFeaturesParams = OSMFeaturesLayer & {
|
|
39
|
+
limit?: number;
|
|
40
|
+
cursor?: string;
|
|
41
|
+
zoom?: number;
|
|
42
|
+
around?: string;
|
|
43
|
+
osmIds?: string;
|
|
44
|
+
minLengthM?: number;
|
|
45
|
+
maxLengthM?: number;
|
|
46
|
+
minAreaM2?: number;
|
|
47
|
+
maxAreaM2?: number;
|
|
48
|
+
disableBudgetWarning?: boolean;
|
|
49
|
+
centroid?: boolean;
|
|
50
|
+
/** Accept media type. Default application/geo+json; other types put bytes in ``data``. */
|
|
51
|
+
accept?: string;
|
|
52
|
+
};
|
|
53
|
+
type QueryValue = unknown;
|
|
54
|
+
export type OSMFeaturesQuery = Record<string, QueryValue>;
|
|
55
|
+
type OSMFeaturesDependencies = {
|
|
56
|
+
fetchFn?: typeof fetch;
|
|
57
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
58
|
+
nowFn?: () => number;
|
|
59
|
+
};
|
|
60
|
+
/** Parse `bbox_tiles` from a query map for `query_all` (client-side only). */
|
|
61
|
+
export declare function resolveBboxTiles(query?: OSMFeaturesQuery, fallback?: number): number;
|
|
62
|
+
/** Split bbox into `tileCount` tiles by repeated longest-side bisection. */
|
|
63
|
+
export declare function splitBbox(bbox: string, tileCount: number): string[];
|
|
64
|
+
export declare class OSMFeatures {
|
|
65
|
+
private readonly apiKey;
|
|
66
|
+
private readonly apiBaseUrl;
|
|
67
|
+
private readonly timeoutMs;
|
|
68
|
+
private readonly retryAttempts;
|
|
69
|
+
private readonly retryBaseMs;
|
|
70
|
+
private readonly retryMaxMs;
|
|
71
|
+
constructor(apiKey: string, { apiBaseUrl, timeoutMs, retryAttempts, retryBaseMs, retryMaxMs, }?: {
|
|
72
|
+
apiBaseUrl?: string;
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
retryAttempts?: number;
|
|
75
|
+
retryBaseMs?: number;
|
|
76
|
+
retryMaxMs?: number;
|
|
77
|
+
});
|
|
78
|
+
/** Map Express/query params + resolved layer into flat `query` / `query_all` params. */
|
|
79
|
+
resolveRequest(query: OSMFeaturesQuery, layer: OSMFeaturesLayer): OSMFeaturesParams;
|
|
80
|
+
private throwUpstreamError;
|
|
81
|
+
/** Single HTTP request with retry. Throws on non-OK (same role as Python `_raw_query`). */
|
|
82
|
+
private _rawQuery;
|
|
83
|
+
/** Single upstream page. Params map 1:1 to server query string (no tiling). */
|
|
84
|
+
query({ bbox, tags, orTags, notTags, type, shape, limit, cursor, zoom, around, osmIds, minLengthM, maxLengthM, minAreaM2, maxAreaM2, disableBudgetWarning, centroid, accept, }: OSMFeaturesParams, dependencies?: OSMFeaturesDependencies): Promise<OSMFeaturesResult>;
|
|
85
|
+
/** Auto-paginate (and optionally tile) until complete. Each page uses `_rawQuery`. */
|
|
86
|
+
query_all({ bbox, tags, orTags, notTags, type, shape, zoom, around, osmIds, minLengthM, maxLengthM, minAreaM2, maxAreaM2, disableBudgetWarning, centroid, accept, limitPerPage, bboxTiles, maxPages, maxFeatures, }: Omit<OSMFeaturesParams, 'limit' | 'cursor'> & {
|
|
87
|
+
/** Upstream `limit` per HTTP request (page size). */
|
|
88
|
+
limitPerPage?: number;
|
|
89
|
+
bboxTiles?: number;
|
|
90
|
+
maxPages?: number;
|
|
91
|
+
/** Cap on merged features. `null` = no cap. */
|
|
92
|
+
maxFeatures?: number | null;
|
|
93
|
+
}, dependencies?: OSMFeaturesDependencies): Promise<OSMGeoJSONResult>;
|
|
94
|
+
}
|
|
95
|
+
export * from './geojson-feature.js';
|
|
96
|
+
export * from './preset/index.js';
|