@osm-editor-kit/osm-coverage 0.1.0-alpha.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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # `@osm-editor-kit/osm-coverage`
2
+
3
+ **Status:** First npm **alpha** (`0.1.0-alpha.0`). Publish with `bun run packages:release -- --publish-only` after `npm login`.
4
+
5
+ ## What it does
6
+
7
+ TanStack Query–backed OSM viewport coverage for map editors: incremental downloads, merged `ParsedOsmData` graphs, coverage polygons, and fetch-history GeoJSON. Includes zoom-aware missing-area detection (`computeMissingFetchRequests`, `unionIntoCoverage`), Overpass URL helpers (`boundsToOverpassBbox`, `buildOverpassInterpreterUrl`, `overpassDeUrl`, `overpassVkUrl`), and an `OsmDataSource` enum for download backends.
8
+
9
+ The `@osm-editor-kit/osm-coverage/dev-osm-map-fixture` subpath is **editor-dev only** — local OSM Map API fixture handler, bbox filtering, and fixture index loader.
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { QueryClient } from '@tanstack/react-query'
15
+ import { createOsmCoverageApi, OsmDataSource } from '@osm-editor-kit/osm-coverage'
16
+
17
+ const api = createOsmCoverageApi<{ source: OsmDataSource }>({
18
+ getSessionKey: ({ source }) => ['osm', source] as const,
19
+ minZoom: 14,
20
+ getDownloadUrl: (bounds, { source }) =>
21
+ /* build Overpass or Map API URL */ '',
22
+ })
23
+
24
+ const queryClient = new QueryClient()
25
+ const params = { source: OsmDataSource.OverpassVk }
26
+
27
+ await api.ensureCoverage(queryClient, {
28
+ bounds: { south: 52.47, west: 13.44, north: 52.48, east: 13.45 },
29
+ zoom: 18,
30
+ mapSizePx: { width: 1024, height: 768 },
31
+ ...params,
32
+ })
33
+
34
+ const data = queryClient.getQueryData(api.sessionKey(params))
35
+ // { graph, coverage, fetchHistory }
36
+ ```
@@ -0,0 +1,38 @@
1
+ import type { MapBounds } from '@osm-editor-kit/osm-data';
2
+ import type { Feature, FeatureCollection, MultiPolygon, Polygon } from 'geojson';
3
+ export declare const BUFFER_PX_AT_Z13 = 200;
4
+ export declare const BUFFER_PX_AT_Z17 = 350;
5
+ export declare const MIN_MISSING_AREA_RATIO = 0.005;
6
+ export declare const FULL_FETCH_RATIO = 0.85;
7
+ export declare const MAX_STRIP_REQUESTS = 2;
8
+ export declare const MAX_HISTORY_FEATURES = 40;
9
+ export declare const MAX_HISTORY_GROUPS = 20;
10
+ export type MapSizePx = {
11
+ width: number;
12
+ height: number;
13
+ };
14
+ export type CoverageFetchKind = 'initial' | 'strip' | 'full';
15
+ export type CoverageFetchRequest = {
16
+ bounds: MapBounds;
17
+ kind: CoverageFetchKind;
18
+ };
19
+ export type CoverageFetchProps = {
20
+ groupId: string;
21
+ fetchedAt: string;
22
+ requestIndex: number;
23
+ requestCount: number;
24
+ kind: CoverageFetchKind;
25
+ };
26
+ export declare function emptyFetchHistory(): FeatureCollection<Polygon, CoverageFetchProps>;
27
+ export declare function bufferPxAtZoom(zoom: number): number;
28
+ export declare function boundsToPolygon(bounds: MapBounds): Feature<Polygon>;
29
+ export declare function polygonToBounds(feature: Feature<Polygon | MultiPolygon>): MapBounds;
30
+ export declare function isValidBounds(bounds: MapBounds): boolean;
31
+ export declare function boundsArea(bounds: MapBounds): number;
32
+ export declare function bufferBoundsInViewport(request: MapBounds, viewport: MapBounds, zoom: number, mapSizePx: MapSizePx): MapBounds;
33
+ export declare function clampBoundsToViewport(request: MapBounds, viewport: MapBounds): MapBounds;
34
+ export declare function createViewportFetchRequest(viewport: MapBounds, zoom: number, mapSizePx: MapSizePx, kind: CoverageFetchKind): CoverageFetchRequest | null;
35
+ export declare function computeMissingFetchRequests(viewport: MapBounds, coverage: Feature<Polygon | MultiPolygon> | null, zoom: number, mapSizePx: MapSizePx): CoverageFetchRequest[];
36
+ export declare function unionIntoCoverage(coverage: Feature<Polygon | MultiPolygon> | null, requestBounds: MapBounds): Feature<Polygon | MultiPolygon>;
37
+ export declare function capFetchHistory(history: FeatureCollection<Polygon, CoverageFetchProps>): FeatureCollection<Polygon, CoverageFetchProps>;
38
+ export declare function appendFetchHistory(history: FeatureCollection<Polygon, CoverageFetchProps>, groupId: string, fetchedAt: string, requests: CoverageFetchRequest[]): FeatureCollection<Polygon, CoverageFetchProps>;
@@ -0,0 +1,36 @@
1
+ import { type MapBounds, type ParsedOsmData } from '@osm-editor-kit/osm-data';
2
+ import { type QueryClient } from '@tanstack/react-query';
3
+ import type { Feature, FeatureCollection, MultiPolygon, Polygon } from 'geojson';
4
+ import { type CoverageFetchProps, type MapSizePx } from './coverage-geometry';
5
+ export type { CoverageFetchProps, MapSizePx } from './coverage-geometry';
6
+ export type OsmCoverageQueryData = {
7
+ graph: ParsedOsmData;
8
+ coverage: Feature<Polygon | MultiPolygon> | null;
9
+ fetchHistory: FeatureCollection<Polygon, CoverageFetchProps>;
10
+ };
11
+ export type CreateOsmCoverageApiOptions<TSessionParams> = {
12
+ getSessionKey: (params: TSessionParams) => readonly unknown[];
13
+ minZoom: number;
14
+ getDownloadUrl: (bounds: MapBounds, params: TSessionParams) => string;
15
+ download?: (url: string) => Promise<ParsedOsmData>;
16
+ /** When false, session queries stay enabled=false and ensureCoverage skips network. */
17
+ isNetworkEnabled?: () => boolean;
18
+ };
19
+ export declare function createOsmCoverageApi<TSessionParams>({ getSessionKey, minZoom, getDownloadUrl, download, isNetworkEnabled, }: CreateOsmCoverageApiOptions<TSessionParams>): {
20
+ emptyData: () => OsmCoverageQueryData;
21
+ sessionKey: (params: TSessionParams) => readonly unknown[];
22
+ coverageKey: (params: TSessionParams) => readonly [...unknown[], "coverage"];
23
+ ensureCoverage: (queryClient: QueryClient, { bounds, zoom, mapSizePx, force, ...sessionParams }: {
24
+ bounds: MapBounds;
25
+ zoom: number;
26
+ mapSizePx: MapSizePx;
27
+ force?: boolean;
28
+ } & TSessionParams) => Promise<{
29
+ skipped: boolean;
30
+ }>;
31
+ createUseQuery: (useSessionParams: () => TSessionParams) => <TData = OsmCoverageQueryData>(options?: {
32
+ select?: (data: OsmCoverageQueryData) => TData;
33
+ }) => import("@tanstack/react-query").DefinedUseQueryResult<NoInfer<TData>, Error>;
34
+ createUseIsFetching: (useSessionParams: () => TSessionParams) => () => boolean;
35
+ createUseResetOnSessionChange: (useSessionParams: () => TSessionParams) => () => void;
36
+ };
@@ -0,0 +1,4 @@
1
+ import type { MapBounds } from '@osm-editor-kit/osm-data';
2
+ export declare const DEV_OSM_MAP_FIXTURE_ROUTE = "dev-osm-map-fixture";
3
+ /** Browser-safe URL for a local dev Map API bbox slice (no Node/fs imports). */
4
+ export declare function devOsmMapFixtureMapUrl(baseUrl: string, bounds: MapBounds): string;
@@ -0,0 +1,6 @@
1
+ export { DEV_OSM_MAP_FIXTURE_ROUTE, devOsmMapFixtureMapUrl, } from './dev-osm-map-fixture/dev-osm-map-fixture-url';
2
+ export { createOsmCoverageApi, type OsmCoverageQueryData } from './create-osm-coverage-api';
3
+ export { type CoverageFetchProps, type CoverageFetchKind, type MapSizePx, boundsToPolygon, bufferPxAtZoom, computeMissingFetchRequests, unionIntoCoverage, } from './coverage-geometry';
4
+ export { OsmDataSource } from './osm-data-source';
5
+ export { boundsToOverpassBbox, buildOverpassInterpreterUrl } from './overpass-url';
6
+ export { overpassDeUrl, overpassVkUrl } from './overpass-urls';
package/dist/index.js ADDED
@@ -0,0 +1,361 @@
1
+ import { downloadOsmData, emptyParsedOsmData, mergeParsedOsm } from '@osm-editor-kit/osm-data';
2
+ import { useQuery, useIsFetching, useQueryClient } from '@tanstack/react-query';
3
+ import { useEffect } from 'react';
4
+ import area from '@turf/area';
5
+ import bbox from '@turf/bbox';
6
+ import bboxPolygon from '@turf/bbox-polygon';
7
+ import difference from '@turf/difference';
8
+ import flatten from '@turf/flatten';
9
+ import { featureCollection } from '@turf/helpers';
10
+ import intersect from '@turf/intersect';
11
+ import union from '@turf/union';
12
+
13
+ // src/dev-osm-map-fixture/dev-osm-map-fixture-url.ts
14
+ var DEV_OSM_MAP_FIXTURE_ROUTE = "dev-osm-map-fixture";
15
+ function devOsmMapFixtureMapUrl(baseUrl, bounds) {
16
+ const base = baseUrl.replace(/\/$/, "");
17
+ const bbox2 = [bounds.west, bounds.south, bounds.east, bounds.north].join(",");
18
+ return `${base}/${DEV_OSM_MAP_FIXTURE_ROUTE}/api/0.6/map?bbox=${bbox2}`;
19
+ }
20
+ var BUFFER_PX_AT_Z13 = 200;
21
+ var MIN_MISSING_AREA_RATIO = 5e-3;
22
+ var FULL_FETCH_RATIO = 0.85;
23
+ var MAX_STRIP_REQUESTS = 2;
24
+ var MAX_HISTORY_FEATURES = 40;
25
+ var MAX_HISTORY_GROUPS = 20;
26
+ var HIGH_FILL_RATIO = 0.7;
27
+ function emptyFetchHistory() {
28
+ return { type: "FeatureCollection", features: [] };
29
+ }
30
+ function bufferPxAtZoom(zoom) {
31
+ return BUFFER_PX_AT_Z13 + 37.5 * (zoom - 13);
32
+ }
33
+ function boundsToPolygon(bounds) {
34
+ return bboxPolygon([bounds.west, bounds.south, bounds.east, bounds.north]);
35
+ }
36
+ function polygonToBounds(feature) {
37
+ const [west, south, east, north] = bbox(feature);
38
+ return { west, south, east, north };
39
+ }
40
+ function isValidBounds(bounds) {
41
+ return bounds.west < bounds.east && bounds.south < bounds.north;
42
+ }
43
+ function boundsArea(bounds) {
44
+ return area(boundsToPolygon(bounds));
45
+ }
46
+ function bufferBoundsInViewport(request, viewport, zoom, mapSizePx) {
47
+ const px = bufferPxAtZoom(zoom);
48
+ const lonBuffer = px * (viewport.east - viewport.west) / mapSizePx.width;
49
+ const latBuffer = px * (viewport.north - viewport.south) / mapSizePx.height;
50
+ return {
51
+ west: request.west - lonBuffer,
52
+ south: request.south - latBuffer,
53
+ east: request.east + lonBuffer,
54
+ north: request.north + latBuffer
55
+ };
56
+ }
57
+ function clampBoundsToViewport(request, viewport) {
58
+ return {
59
+ west: Math.max(request.west, viewport.west),
60
+ south: Math.max(request.south, viewport.south),
61
+ east: Math.min(request.east, viewport.east),
62
+ north: Math.min(request.north, viewport.north)
63
+ };
64
+ }
65
+ function prepareFetchBounds(request, viewport, zoom, mapSizePx) {
66
+ const buffered = clampBoundsToViewport(
67
+ bufferBoundsInViewport(request, viewport, zoom, mapSizePx),
68
+ viewport
69
+ );
70
+ if (!isValidBounds(buffered)) return null;
71
+ const viewportArea = boundsArea(viewport);
72
+ if (boundsArea(buffered) < MIN_MISSING_AREA_RATIO * viewportArea) return null;
73
+ return buffered;
74
+ }
75
+ function missingParts(viewport, coverage) {
76
+ const viewportPoly = boundsToPolygon(viewport);
77
+ const missing = difference(featureCollection([viewportPoly, coverage]));
78
+ if (!missing) return [];
79
+ return flatten(missing).features.filter(
80
+ (feature) => feature.geometry.type === "Polygon"
81
+ );
82
+ }
83
+ function stripCandidates(viewport, coverage, missingPart) {
84
+ const viewportPoly = boundsToPolygon(viewport);
85
+ const covered = intersect(featureCollection([viewportPoly, coverage]));
86
+ if (!covered) return [];
87
+ const coveredBbox = polygonToBounds(covered);
88
+ const candidates = [
89
+ {
90
+ west: viewport.west,
91
+ south: viewport.south,
92
+ east: coveredBbox.west,
93
+ north: viewport.north
94
+ },
95
+ {
96
+ west: coveredBbox.east,
97
+ south: viewport.south,
98
+ east: viewport.east,
99
+ north: viewport.north
100
+ },
101
+ {
102
+ west: viewport.west,
103
+ south: coveredBbox.north,
104
+ east: viewport.east,
105
+ north: viewport.north
106
+ },
107
+ {
108
+ west: viewport.west,
109
+ south: viewport.south,
110
+ east: viewport.east,
111
+ north: coveredBbox.south
112
+ }
113
+ ];
114
+ return candidates.filter((strip) => {
115
+ if (!isValidBounds(strip)) return false;
116
+ const overlap = intersect(featureCollection([boundsToPolygon(strip), missingPart]));
117
+ return overlap !== null && area(overlap) > 0;
118
+ });
119
+ }
120
+ function chooseBoundsForMissingPart(viewport, coverage, missingPart) {
121
+ const partArea = area(missingPart);
122
+ const partBbox = polygonToBounds(missingPart);
123
+ const fillRatio = partArea / area(boundsToPolygon(partBbox));
124
+ if (fillRatio >= HIGH_FILL_RATIO) {
125
+ return [partBbox];
126
+ }
127
+ const strips = stripCandidates(viewport, coverage, missingPart);
128
+ const stripAreas = strips.map((strip) => ({ bounds: strip, area: boundsArea(strip) })).sort((a, b) => b.area - a.area);
129
+ const stripTotalArea = stripAreas.reduce((sum, strip) => sum + strip.area, 0);
130
+ const bboxArea = boundsArea(partBbox);
131
+ if (strips.length > 0 && stripTotalArea < bboxArea) {
132
+ return stripAreas.slice(0, MAX_STRIP_REQUESTS).map((strip) => strip.bounds);
133
+ }
134
+ return [partBbox];
135
+ }
136
+ function createViewportFetchRequest(viewport, zoom, mapSizePx, kind) {
137
+ const prepared = prepareFetchBounds(viewport, viewport, zoom, mapSizePx);
138
+ return prepared ? { bounds: prepared, kind } : null;
139
+ }
140
+ function computeMissingFetchRequests(viewport, coverage, zoom, mapSizePx) {
141
+ const viewportArea = boundsArea(viewport);
142
+ if (!coverage) {
143
+ const initial = createViewportFetchRequest(viewport, zoom, mapSizePx, "initial");
144
+ return initial ? [initial] : [];
145
+ }
146
+ const parts = missingParts(viewport, coverage);
147
+ const significantParts = parts.filter(
148
+ (part) => area(part) >= MIN_MISSING_AREA_RATIO * viewportArea
149
+ );
150
+ if (significantParts.length === 0) return [];
151
+ const rawBounds = significantParts.flatMap(
152
+ (part) => chooseBoundsForMissingPart(viewport, coverage, part)
153
+ );
154
+ let requests = rawBounds.map((request) => {
155
+ const prepared = prepareFetchBounds(request, viewport, zoom, mapSizePx);
156
+ if (!prepared) return null;
157
+ return { bounds: prepared, area: boundsArea(prepared), kind: "strip" };
158
+ }).filter(
159
+ (request) => request !== null
160
+ );
161
+ if (requests.length === 0) return [];
162
+ requests.sort((a, b) => b.area - a.area);
163
+ const sumArea = requests.reduce((sum, request) => sum + request.area, 0);
164
+ if (sumArea >= FULL_FETCH_RATIO * viewportArea) {
165
+ const full = createViewportFetchRequest(viewport, zoom, mapSizePx, "full");
166
+ return full ? [full] : [];
167
+ }
168
+ requests = requests.slice(0, MAX_STRIP_REQUESTS);
169
+ return requests.map((request) => ({
170
+ bounds: request.bounds,
171
+ kind: request.kind
172
+ }));
173
+ }
174
+ function unionIntoCoverage(coverage, requestBounds) {
175
+ const requestPoly = boundsToPolygon(requestBounds);
176
+ if (!coverage) return requestPoly;
177
+ const merged = union(featureCollection([coverage, requestPoly]));
178
+ return merged ?? requestPoly;
179
+ }
180
+ function countDistinctGroups(features) {
181
+ return new Set(features.map((feature) => feature.properties.groupId)).size;
182
+ }
183
+ function capFetchHistory(history) {
184
+ let features = [...history.features];
185
+ while (features.length > MAX_HISTORY_FEATURES) {
186
+ features.shift();
187
+ }
188
+ while (countDistinctGroups(features) > MAX_HISTORY_GROUPS) {
189
+ const oldestGroupId = features[0]?.properties.groupId;
190
+ if (!oldestGroupId) break;
191
+ features = features.filter((feature) => feature.properties.groupId !== oldestGroupId);
192
+ }
193
+ return { type: "FeatureCollection", features };
194
+ }
195
+ function appendFetchHistory(history, groupId, fetchedAt, requests) {
196
+ const newFeatures = requests.map((request, requestIndex) => ({
197
+ type: "Feature",
198
+ geometry: boundsToPolygon(request.bounds).geometry,
199
+ properties: {
200
+ groupId,
201
+ fetchedAt,
202
+ requestIndex,
203
+ requestCount: requests.length,
204
+ kind: request.kind
205
+ }
206
+ }));
207
+ return capFetchHistory({
208
+ features: [...history.features, ...newFeatures]
209
+ });
210
+ }
211
+
212
+ // src/create-osm-coverage-api.ts
213
+ function createOsmCoverageApi({
214
+ getSessionKey,
215
+ minZoom,
216
+ getDownloadUrl,
217
+ download = downloadOsmData,
218
+ isNetworkEnabled
219
+ }) {
220
+ const getCoverageKey = (params) => [...getSessionKey(params), "coverage"];
221
+ const networkEnabled = () => isNetworkEnabled?.() ?? true;
222
+ const coverageChains = /* @__PURE__ */ new Map();
223
+ function emptyData() {
224
+ return {
225
+ graph: emptyParsedOsmData(),
226
+ coverage: null,
227
+ fetchHistory: emptyFetchHistory()
228
+ };
229
+ }
230
+ async function ensureCoverage(queryClient, {
231
+ bounds,
232
+ zoom,
233
+ mapSizePx,
234
+ force = false,
235
+ ...sessionParams
236
+ }) {
237
+ if (!networkEnabled()) {
238
+ return { skipped: true };
239
+ }
240
+ const params = sessionParams;
241
+ const sessionKey = getSessionKey(params);
242
+ const coverageKey = getCoverageKey(params);
243
+ const chainKey = JSON.stringify(sessionKey);
244
+ const run = async () => {
245
+ const requestKey = [...coverageKey, crypto.randomUUID()];
246
+ return queryClient.fetchQuery({
247
+ queryKey: requestKey,
248
+ queryFn: async () => {
249
+ const current = force ? emptyData() : queryClient.getQueryData(sessionKey) ?? emptyData();
250
+ if (zoom < minZoom) {
251
+ return { skipped: true };
252
+ }
253
+ const requests = force ? (() => {
254
+ const full = createViewportFetchRequest(bounds, zoom, mapSizePx, "full");
255
+ return full ? [full] : [];
256
+ })() : computeMissingFetchRequests(bounds, current.coverage, zoom, mapSizePx);
257
+ if (requests.length === 0) {
258
+ return { skipped: true };
259
+ }
260
+ const groupId = crypto.randomUUID();
261
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
262
+ let coverage = force ? null : current.coverage;
263
+ let fetchHistory = force ? emptyFetchHistory() : current.fetchHistory;
264
+ const fetchedGraphs = [];
265
+ for (const request of requests) {
266
+ const url = getDownloadUrl(request.bounds, params);
267
+ fetchedGraphs.push(await download(url));
268
+ coverage = unionIntoCoverage(coverage, request.bounds);
269
+ }
270
+ fetchHistory = appendFetchHistory(fetchHistory, groupId, fetchedAt, requests);
271
+ queryClient.setQueryData(sessionKey, (latest) => {
272
+ const base = force ? emptyData() : latest ?? emptyData();
273
+ let graph = base.graph;
274
+ for (const newGraph of fetchedGraphs) {
275
+ graph = mergeParsedOsm(graph, newGraph);
276
+ }
277
+ return { graph, coverage, fetchHistory };
278
+ });
279
+ return { skipped: false };
280
+ },
281
+ staleTime: 0,
282
+ gcTime: 0
283
+ });
284
+ };
285
+ const previous = coverageChains.get(chainKey) ?? Promise.resolve();
286
+ const next = previous.then(run, run);
287
+ coverageChains.set(
288
+ chainKey,
289
+ next.then(
290
+ () => void 0,
291
+ () => void 0
292
+ )
293
+ );
294
+ return next;
295
+ }
296
+ function createUseQuery(useSessionParams) {
297
+ return function useOsmQuery(options) {
298
+ const params = useSessionParams();
299
+ return useQuery({
300
+ queryKey: getSessionKey(params),
301
+ queryFn: () => emptyData(),
302
+ initialData: emptyData(),
303
+ staleTime: Number.POSITIVE_INFINITY,
304
+ enabled: networkEnabled(),
305
+ select: options?.select
306
+ });
307
+ };
308
+ }
309
+ function createUseIsFetching(useSessionParams) {
310
+ return function useIsOsmFetching() {
311
+ const params = useSessionParams();
312
+ return useIsFetching({ queryKey: getCoverageKey(params) }) > 0;
313
+ };
314
+ }
315
+ function createUseResetOnSessionChange(useSessionParams) {
316
+ return function useResetOsmOnSessionChange() {
317
+ const queryClient = useQueryClient();
318
+ const params = useSessionParams();
319
+ useEffect(
320
+ function resetOsmDataOnSessionChange() {
321
+ queryClient.setQueryData(getSessionKey(params), emptyData());
322
+ queryClient.removeQueries({
323
+ queryKey: getCoverageKey(params)
324
+ });
325
+ },
326
+ [params, queryClient]
327
+ );
328
+ };
329
+ }
330
+ return {
331
+ emptyData,
332
+ sessionKey: getSessionKey,
333
+ coverageKey: getCoverageKey,
334
+ ensureCoverage,
335
+ createUseQuery,
336
+ createUseIsFetching,
337
+ createUseResetOnSessionChange
338
+ };
339
+ }
340
+
341
+ // src/osm-data-source.ts
342
+ var OsmDataSource = /* @__PURE__ */ ((OsmDataSource2) => {
343
+ OsmDataSource2[OsmDataSource2["OverpassDe"] = 0] = "OverpassDe";
344
+ OsmDataSource2[OsmDataSource2["OsmOrg"] = 1] = "OsmOrg";
345
+ OsmDataSource2[OsmDataSource2["OverpassVk"] = 2] = "OverpassVk";
346
+ return OsmDataSource2;
347
+ })(OsmDataSource || {});
348
+
349
+ // src/overpass-url.ts
350
+ function boundsToOverpassBbox(bounds) {
351
+ return [bounds.south, bounds.west, bounds.north, bounds.east].join(",");
352
+ }
353
+ function buildOverpassInterpreterUrl(server, query) {
354
+ return server + encodeURIComponent(query);
355
+ }
356
+
357
+ // src/overpass-urls.ts
358
+ var overpassDeUrl = "https://overpass-api.de/api/interpreter?data=";
359
+ var overpassVkUrl = "https://maps.mail.ru/osm/tools/overpass/api/interpreter?data=";
360
+
361
+ export { DEV_OSM_MAP_FIXTURE_ROUTE, OsmDataSource, boundsToOverpassBbox, boundsToPolygon, bufferPxAtZoom, buildOverpassInterpreterUrl, computeMissingFetchRequests, createOsmCoverageApi, devOsmMapFixtureMapUrl, overpassDeUrl, overpassVkUrl, unionIntoCoverage };
@@ -0,0 +1,5 @@
1
+ export declare enum OsmDataSource {
2
+ OverpassDe = 0,
3
+ OsmOrg = 1,
4
+ OverpassVk = 2
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { MapBounds } from '@osm-editor-kit/osm-data';
2
+ export declare function boundsToOverpassBbox(bounds: MapBounds): string;
3
+ export declare function buildOverpassInterpreterUrl(server: string, query: string): string;
@@ -0,0 +1,2 @@
1
+ export declare const overpassDeUrl = "https://overpass-api.de/api/interpreter?data=";
2
+ export declare const overpassVkUrl = "https://maps.mail.ru/osm/tools/overpass/api/interpreter?data=";
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@osm-editor-kit/osm-coverage",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Incremental OSM coverage fetching for map viewports with TanStack Query session state.",
5
+ "license": "MIT",
6
+ "author": "Tobias Jordans",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/osmberlin/street-space-editor.git",
10
+ "directory": "packages/osm-coverage"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "tag": "alpha"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup && rm -f .tsbuildinfo && tsc -p tsconfig.build.json",
30
+ "type-check": "tsc --noEmit",
31
+ "test-run": "bun test src/test",
32
+ "prepublishOnly": "bun run build && bun ../../scripts/package-exports-for-publish.ts",
33
+ "postpublish": "bun ../../scripts/package-exports-restore.ts",
34
+ "prepack": "bun ../../scripts/package-exports-for-publish.ts",
35
+ "postpack": "bun ../../scripts/package-exports-restore.ts"
36
+ },
37
+ "dependencies": {
38
+ "@osm-editor-kit/osm-data": "workspace:*",
39
+ "@tanstack/react-query": "^5.85.5",
40
+ "@turf/area": "^7.3.5",
41
+ "@turf/bbox": "^7.3.5",
42
+ "@turf/bbox-polygon": "^7.3.5",
43
+ "@turf/difference": "^7.3.5",
44
+ "@turf/flatten": "^7.3.5",
45
+ "@turf/helpers": "^7.3.5",
46
+ "@turf/intersect": "^7.3.5",
47
+ "@turf/union": "^7.3.5"
48
+ },
49
+ "devDependencies": {
50
+ "@types/geojson": "^7946.0.16",
51
+ "@types/react": "^19.1.10",
52
+ "bun-types": "^1.2.20",
53
+ "react": "^19.1.1",
54
+ "tsup": "^8.5.1",
55
+ "typescript": "^7.0.0"
56
+ },
57
+ "peerDependencies": {
58
+ "react": "^19.0.0"
59
+ },
60
+ "publishExports": {
61
+ ".": {
62
+ "types": "./dist/index.d.ts",
63
+ "import": "./dist/index.js"
64
+ },
65
+ "./package.json": "./package.json"
66
+ }
67
+ }