@effect-dynamodb/geo 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 +31 -0
- package/dist/GeoIndex.d.ts +128 -0
- package/dist/GeoIndex.d.ts.map +1 -0
- package/dist/GeoIndex.js +101 -0
- package/dist/GeoIndex.js.map +1 -0
- package/dist/GeoSearch.d.ts +42 -0
- package/dist/GeoSearch.d.ts.map +1 -0
- package/dist/GeoSearch.js +119 -0
- package/dist/GeoSearch.js.map +1 -0
- package/dist/H3.d.ts +70 -0
- package/dist/H3.d.ts.map +1 -0
- package/dist/H3.js +189 -0
- package/dist/H3.js.map +1 -0
- package/dist/Spherical.d.ts +37 -0
- package/dist/Spherical.d.ts.map +1 -0
- package/dist/Spherical.js +89 -0
- package/dist/Spherical.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Justin Menga
|
|
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,31 @@
|
|
|
1
|
+
# @effect-dynamodb/geo
|
|
2
|
+
|
|
3
|
+
Geospatial index and proximity search for [`effect-dynamodb`](https://www.npmjs.com/package/effect-dynamodb), built on Uber's [H3](https://h3geo.org) hexagonal grid.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@effect-dynamodb/geo)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
**Documentation:** https://jmenga.github.io/effect-dynamodb/guides/geospatial
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @effect-dynamodb/geo effect-dynamodb effect
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`effect` and `effect-dynamodb` are peer dependencies.
|
|
17
|
+
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
Adds two operations to any entity with `lat`/`lng` coordinates:
|
|
21
|
+
|
|
22
|
+
- **Proximity search** — "find items within N kilometers of a point"
|
|
23
|
+
- **Bounding-box search** — "find items inside a rectangle"
|
|
24
|
+
|
|
25
|
+
Implemented as a GSI overlay using H3 cell IDs as the partition key, so reads stay O(visited cells) instead of scanning the table.
|
|
26
|
+
|
|
27
|
+
See the [Geospatial guide](https://jmenga.github.io/effect-dynamodb/guides/geospatial) for setup, indexing strategy, and tradeoffs.
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoIndex — Geospatial index for effect-dynamodb entities.
|
|
3
|
+
*
|
|
4
|
+
* Binds geospatial configuration to an Entity and provides:
|
|
5
|
+
* - `put` — write with automatic geo field enrichment
|
|
6
|
+
* - `nearby` — radius-based proximity search
|
|
7
|
+
* - `enrich` — lower-level geo field computation (for transactions/batch writes)
|
|
8
|
+
*/
|
|
9
|
+
import { type Context, Effect, type Schema } from "effect";
|
|
10
|
+
import type { DynamoClient, DynamoClientError, DynamoSchema, KeyComposer, Table, UniqueConstraintViolation, ValidationError } from "effect-dynamodb";
|
|
11
|
+
import * as H3 from "./H3.js";
|
|
12
|
+
import type { LatLng } from "./Spherical.js";
|
|
13
|
+
export type { LatLng } from "./Spherical.js";
|
|
14
|
+
export type TimeBucket = "hourly";
|
|
15
|
+
export type SortOrder = "ASC" | "DESC";
|
|
16
|
+
export interface GeoFields {
|
|
17
|
+
readonly cell: {
|
|
18
|
+
readonly field: string;
|
|
19
|
+
readonly resolution: number;
|
|
20
|
+
};
|
|
21
|
+
readonly parentCell: {
|
|
22
|
+
readonly field: string;
|
|
23
|
+
readonly resolution: number;
|
|
24
|
+
};
|
|
25
|
+
readonly timePartition: {
|
|
26
|
+
readonly field: string;
|
|
27
|
+
readonly source: string;
|
|
28
|
+
readonly bucket: TimeBucket;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface Coordinates {
|
|
32
|
+
readonly latitude: number;
|
|
33
|
+
readonly longitude: number;
|
|
34
|
+
}
|
|
35
|
+
export interface NearbyOptions {
|
|
36
|
+
readonly center: LatLng;
|
|
37
|
+
readonly radius: number;
|
|
38
|
+
readonly unit?: H3.GeoUnit | undefined;
|
|
39
|
+
readonly timeWindow?: {
|
|
40
|
+
readonly start: number;
|
|
41
|
+
readonly end: number;
|
|
42
|
+
} | undefined;
|
|
43
|
+
readonly sort?: SortOrder | undefined;
|
|
44
|
+
readonly pkFilter?: Record<string, unknown> | undefined;
|
|
45
|
+
}
|
|
46
|
+
export interface NearbyResult<A> {
|
|
47
|
+
readonly item: A;
|
|
48
|
+
readonly distance: number;
|
|
49
|
+
}
|
|
50
|
+
/** Structural type for the entity properties GeoIndex needs */
|
|
51
|
+
export interface GeoEntity<A, P> {
|
|
52
|
+
readonly _schema: DynamoSchema.DynamoSchema;
|
|
53
|
+
readonly _tableTag: Context.Service<Table.TableConfig, Table.TableConfig>;
|
|
54
|
+
readonly entityType: string;
|
|
55
|
+
readonly indexes: Record<string, KeyComposer.IndexDefinition>;
|
|
56
|
+
readonly schemas: {
|
|
57
|
+
readonly recordSchema: Schema.Codec<any>;
|
|
58
|
+
};
|
|
59
|
+
readonly put: (input: A) => P;
|
|
60
|
+
}
|
|
61
|
+
export interface GeoIndex<A, P> {
|
|
62
|
+
/** Write an item with automatic geo field enrichment. */
|
|
63
|
+
readonly put: (input: A) => P;
|
|
64
|
+
/** Search for items near a geographic point within a given radius. */
|
|
65
|
+
readonly nearby: (options: NearbyOptions) => Effect.Effect<Array<NearbyResult<A>>, DynamoClientError | ValidationError, DynamoClient | Table.TableConfig>;
|
|
66
|
+
/** Compute geo fields for an input (for use with transactions/batch writes). */
|
|
67
|
+
readonly enrich: (input: A) => A;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a GeoIndex that binds geospatial configuration to an entity's GSI.
|
|
71
|
+
*
|
|
72
|
+
* Returns an object with `put`, `nearby`, and `enrich` methods.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const VehicleGeo = GeoIndex.make({
|
|
77
|
+
* entity: Vehicles,
|
|
78
|
+
* index: "byCell",
|
|
79
|
+
* coordinates: (item) => item.latitude !== undefined && item.longitude !== undefined
|
|
80
|
+
* ? { latitude: item.latitude, longitude: item.longitude }
|
|
81
|
+
* : undefined,
|
|
82
|
+
* fields: {
|
|
83
|
+
* cell: { field: "cell", resolution: 15 },
|
|
84
|
+
* parentCell: { field: "parentCell", resolution: 3 },
|
|
85
|
+
* timePartition: { field: "timePartition", source: "timestamp", bucket: "hourly" },
|
|
86
|
+
* },
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* // Write — geo fields computed automatically
|
|
90
|
+
* yield* VehicleGeo.put({ vehicleId: "v-1", latitude: 37.77, longitude: -122.42, timestamp: now })
|
|
91
|
+
*
|
|
92
|
+
* // Search
|
|
93
|
+
* const results = yield* VehicleGeo.nearby({ center, radius: 2000, unit: "m" })
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare const make: <A, P>(config: {
|
|
97
|
+
readonly entity: GeoEntity<A, P>;
|
|
98
|
+
readonly index: string;
|
|
99
|
+
readonly coordinates: (item: A) => Coordinates | undefined;
|
|
100
|
+
readonly fields: GeoFields;
|
|
101
|
+
}) => GeoIndex<A, P>;
|
|
102
|
+
/**
|
|
103
|
+
* A GeoIndex with `DynamoClient | Table.TableConfig` dependencies resolved.
|
|
104
|
+
* All operations return `Effect<..., ..., never>`.
|
|
105
|
+
*/
|
|
106
|
+
export interface BoundGeoIndex<A> {
|
|
107
|
+
/** Write an item with automatic geo field enrichment. R = never. */
|
|
108
|
+
readonly put: (input: A) => Effect.Effect<A, DynamoClientError | ValidationError | UniqueConstraintViolation, never>;
|
|
109
|
+
/** Search for items near a geographic point. R = never. */
|
|
110
|
+
readonly nearby: (options: NearbyOptions) => Effect.Effect<Array<NearbyResult<A>>, DynamoClientError | ValidationError, never>;
|
|
111
|
+
/** Compute geo fields for an input (pure — same as GeoIndex.enrich). */
|
|
112
|
+
readonly enrich: (input: A) => A;
|
|
113
|
+
/** Provide escape hatch for complex pipe chains. */
|
|
114
|
+
readonly provide: <B, E>(effect: Effect.Effect<B, E, DynamoClient | Table.TableConfig>) => Effect.Effect<B, E, never>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve `DynamoClient | Table.TableConfig` dependencies and return a
|
|
118
|
+
* `BoundGeoIndex` whose operations all have `R = never`.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const geo = yield* GeoIndex.bind(VehicleGeo)
|
|
123
|
+
* yield* geo.put({ vehicleId: "v-1", latitude: 37.77, longitude: -122.42, timestamp: Date.now() })
|
|
124
|
+
* const results = yield* geo.nearby({ center, radius: 2000, unit: "m" })
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export declare const bind: <A, P>(geoIndex: GeoIndex<A, P>) => Effect.Effect<BoundGeoIndex<A>, never, DynamoClient | Table.TableConfig>;
|
|
128
|
+
//# sourceMappingURL=GeoIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoIndex.d.ts","sourceRoot":"","sources":["../src/GeoIndex.ts"],"names":[],"mappings":"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"}
|
package/dist/GeoIndex.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoIndex — Geospatial index for effect-dynamodb entities.
|
|
3
|
+
*
|
|
4
|
+
* Binds geospatial configuration to an Entity and provides:
|
|
5
|
+
* - `put` — write with automatic geo field enrichment
|
|
6
|
+
* - `nearby` — radius-based proximity search
|
|
7
|
+
* - `enrich` — lower-level geo field computation (for transactions/batch writes)
|
|
8
|
+
*/
|
|
9
|
+
import { Effect } from "effect";
|
|
10
|
+
import * as _GeoSearch from "./GeoSearch.js";
|
|
11
|
+
import * as H3 from "./H3.js";
|
|
12
|
+
const BUCKET_MS = {
|
|
13
|
+
hourly: H3.HOURLY_BUCKET_MS,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a GeoIndex that binds geospatial configuration to an entity's GSI.
|
|
17
|
+
*
|
|
18
|
+
* Returns an object with `put`, `nearby`, and `enrich` methods.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const VehicleGeo = GeoIndex.make({
|
|
23
|
+
* entity: Vehicles,
|
|
24
|
+
* index: "byCell",
|
|
25
|
+
* coordinates: (item) => item.latitude !== undefined && item.longitude !== undefined
|
|
26
|
+
* ? { latitude: item.latitude, longitude: item.longitude }
|
|
27
|
+
* : undefined,
|
|
28
|
+
* fields: {
|
|
29
|
+
* cell: { field: "cell", resolution: 15 },
|
|
30
|
+
* parentCell: { field: "parentCell", resolution: 3 },
|
|
31
|
+
* timePartition: { field: "timePartition", source: "timestamp", bucket: "hourly" },
|
|
32
|
+
* },
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* // Write — geo fields computed automatically
|
|
36
|
+
* yield* VehicleGeo.put({ vehicleId: "v-1", latitude: 37.77, longitude: -122.42, timestamp: now })
|
|
37
|
+
*
|
|
38
|
+
* // Search
|
|
39
|
+
* const results = yield* VehicleGeo.nearby({ center, radius: 2000, unit: "m" })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const make = (config) => {
|
|
43
|
+
const indexDef = config.entity.indexes[config.index];
|
|
44
|
+
if (!indexDef) {
|
|
45
|
+
throw new Error(`GeoIndex: index "${config.index}" not found on entity`);
|
|
46
|
+
}
|
|
47
|
+
const bucketMs = BUCKET_MS[config.fields.timePartition.bucket];
|
|
48
|
+
const enrich = (input) => {
|
|
49
|
+
const coords = config.coordinates(input);
|
|
50
|
+
if (!coords)
|
|
51
|
+
return input;
|
|
52
|
+
const { fields } = config;
|
|
53
|
+
const cell = H3.computeCell(coords.latitude, coords.longitude, fields.cell.resolution);
|
|
54
|
+
const parentCell = H3.computeParentCell(cell, fields.parentCell.resolution);
|
|
55
|
+
const record = input;
|
|
56
|
+
const timestampValue = record[fields.timePartition.source];
|
|
57
|
+
if (typeof timestampValue !== "number")
|
|
58
|
+
return input;
|
|
59
|
+
const timePartition = H3.computeTimePartition(timestampValue, bucketMs);
|
|
60
|
+
return {
|
|
61
|
+
...input,
|
|
62
|
+
[fields.cell.field]: cell,
|
|
63
|
+
[fields.parentCell.field]: parentCell,
|
|
64
|
+
[fields.timePartition.field]: timePartition,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const searchConfig = {
|
|
68
|
+
entity: config.entity,
|
|
69
|
+
indexDef,
|
|
70
|
+
coordinates: config.coordinates,
|
|
71
|
+
fields: config.fields,
|
|
72
|
+
bucketMs,
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
put: (input) => config.entity.put(enrich(input)),
|
|
76
|
+
nearby: (options) => _GeoSearch.nearby(searchConfig, options),
|
|
77
|
+
enrich,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Resolve `DynamoClient | Table.TableConfig` dependencies and return a
|
|
82
|
+
* `BoundGeoIndex` whose operations all have `R = never`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const geo = yield* GeoIndex.bind(VehicleGeo)
|
|
87
|
+
* yield* geo.put({ vehicleId: "v-1", latitude: 37.77, longitude: -122.42, timestamp: Date.now() })
|
|
88
|
+
* const results = yield* geo.nearby({ center, radius: 2000, unit: "m" })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export const bind = (geoIndex) => Effect.gen(function* () {
|
|
92
|
+
const ctx = yield* Effect.context();
|
|
93
|
+
const provide = (effect) => Effect.provide(effect, ctx);
|
|
94
|
+
return {
|
|
95
|
+
put: (input) => provide(geoIndex.put(input).asEffect()),
|
|
96
|
+
nearby: (options) => provide(geoIndex.nearby(options)),
|
|
97
|
+
enrich: geoIndex.enrich,
|
|
98
|
+
provide,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=GeoIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoIndex.js","sourceRoot":"","sources":["../src/GeoIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoSearch — Internal geospatial search orchestration for DynamoDB.
|
|
3
|
+
*
|
|
4
|
+
* Composes multiple Query objects from effect-dynamodb core and executes them
|
|
5
|
+
* in parallel to perform radius-based proximity searches using H3 hexagonal grid.
|
|
6
|
+
*
|
|
7
|
+
* @internal Used by GeoIndex — consumers should use `GeoIndex.nearby()`.
|
|
8
|
+
*/
|
|
9
|
+
import { type Context, Effect, Schema } from "effect";
|
|
10
|
+
import type { DynamoClientError, DynamoSchema, Table } from "effect-dynamodb";
|
|
11
|
+
import { type DynamoClient, KeyComposer, ValidationError } from "effect-dynamodb";
|
|
12
|
+
import type { Coordinates, GeoFields, NearbyOptions, NearbyResult } from "./GeoIndex.js";
|
|
13
|
+
/** Internal config passed from GeoIndex.make to the search implementation. */
|
|
14
|
+
export interface SearchConfig<A> {
|
|
15
|
+
readonly entity: {
|
|
16
|
+
readonly _schema: DynamoSchema.DynamoSchema;
|
|
17
|
+
readonly _tableTag: Context.Service<Table.TableConfig, Table.TableConfig>;
|
|
18
|
+
readonly entityType: string;
|
|
19
|
+
readonly indexes: Record<string, KeyComposer.IndexDefinition>;
|
|
20
|
+
readonly schemas: {
|
|
21
|
+
readonly recordSchema: Schema.Codec<any>;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
readonly indexDef: KeyComposer.IndexDefinition;
|
|
25
|
+
readonly coordinates: (item: A) => Coordinates | undefined;
|
|
26
|
+
readonly fields: GeoFields;
|
|
27
|
+
readonly bucketMs: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Search for items near a geographic point within a given radius.
|
|
31
|
+
*
|
|
32
|
+
* Orchestrates:
|
|
33
|
+
* 1. Compute optimal H3 resolution and ring size for the radius
|
|
34
|
+
* 2. Generate and prune H3 cells within radius
|
|
35
|
+
* 3. Group contiguous cells into BETWEEN range query chunks
|
|
36
|
+
* 4. Generate time partition keys for the time window
|
|
37
|
+
* 5. Build N queries (one per timePartition x chunk combination)
|
|
38
|
+
* 6. Execute all queries in parallel
|
|
39
|
+
* 7. Post-process: compute great-circle distance, filter by radius, sort
|
|
40
|
+
*/
|
|
41
|
+
export declare const nearby: <A>(config: SearchConfig<A>, options: NearbyOptions) => Effect.Effect<Array<NearbyResult<A>>, DynamoClientError | ValidationError, DynamoClient | Table.TableConfig>;
|
|
42
|
+
//# sourceMappingURL=GeoSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoSearch.d.ts","sourceRoot":"","sources":["../src/GeoSearch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAC7E,OAAO,EAAE,KAAK,YAAY,EAAE,WAAW,EAAS,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAExF,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAIxF,8EAA8E;AAC9E,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,YAAY,CAAA;QAC3C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;QACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;QAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,CAAA;QAC7D,QAAQ,CAAC,OAAO,EAAE;YAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;SAAE,CAAA;KAC/D,CAAA;IACD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAA;IAC9C,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,WAAW,GAAG,SAAS,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EACtB,QAAQ,YAAY,CAAC,CAAC,CAAC,EACvB,SAAS,aAAa,KACrB,MAAM,CAAC,MAAM,CACd,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EACtB,iBAAiB,GAAG,eAAe,EACnC,YAAY,GAAG,KAAK,CAAC,WAAW,CAmI9B,CAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoSearch — Internal geospatial search orchestration for DynamoDB.
|
|
3
|
+
*
|
|
4
|
+
* Composes multiple Query objects from effect-dynamodb core and executes them
|
|
5
|
+
* in parallel to perform radius-based proximity searches using H3 hexagonal grid.
|
|
6
|
+
*
|
|
7
|
+
* @internal Used by GeoIndex — consumers should use `GeoIndex.nearby()`.
|
|
8
|
+
*/
|
|
9
|
+
import { Effect, Schema } from "effect";
|
|
10
|
+
import { KeyComposer, Query, ValidationError } from "effect-dynamodb";
|
|
11
|
+
import * as h3 from "h3-js";
|
|
12
|
+
import * as H3 from "./H3.js";
|
|
13
|
+
import * as Spherical from "./Spherical.js";
|
|
14
|
+
const H3_RESOLUTION_PRECISE = 15;
|
|
15
|
+
/**
|
|
16
|
+
* Search for items near a geographic point within a given radius.
|
|
17
|
+
*
|
|
18
|
+
* Orchestrates:
|
|
19
|
+
* 1. Compute optimal H3 resolution and ring size for the radius
|
|
20
|
+
* 2. Generate and prune H3 cells within radius
|
|
21
|
+
* 3. Group contiguous cells into BETWEEN range query chunks
|
|
22
|
+
* 4. Generate time partition keys for the time window
|
|
23
|
+
* 5. Build N queries (one per timePartition x chunk combination)
|
|
24
|
+
* 6. Execute all queries in parallel
|
|
25
|
+
* 7. Post-process: compute great-circle distance, filter by radius, sort
|
|
26
|
+
*/
|
|
27
|
+
export const nearby = (config, options) => Effect.gen(function* () {
|
|
28
|
+
const { center, radius, unit = "m", sort = "ASC", pkFilter } = options;
|
|
29
|
+
const { latitude, longitude } = center;
|
|
30
|
+
// Step 1: Optimal H3 resolution for the search radius
|
|
31
|
+
const searchResolution = H3.optimalResolution(latitude, longitude, radius);
|
|
32
|
+
const cell = H3.computeCell(latitude, longitude, searchResolution);
|
|
33
|
+
// Step 2: Number of rings needed
|
|
34
|
+
const k = H3.optimalK(cell, radius, unit);
|
|
35
|
+
// Step 3: Generate grid disk (concentric rings)
|
|
36
|
+
const ringCells = h3.gridDiskDistances(cell, k);
|
|
37
|
+
// Step 4: Prune cells outside radius
|
|
38
|
+
const prunedCells = H3.pruneCells(latitude, longitude, radius, ringCells);
|
|
39
|
+
// Step 5: Group into contiguous chunks for BETWEEN queries
|
|
40
|
+
const sortedChunks = H3.sequentialChunk(prunedCells.flat());
|
|
41
|
+
// Step 6: Time partitions
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const startTime = options.timeWindow?.start ?? now - 15 * 60 * 1000;
|
|
44
|
+
const endTime = options.timeWindow?.end ?? now;
|
|
45
|
+
const timePartitions = H3.getTimePartitions(startTime, endTime, config.bucketMs);
|
|
46
|
+
// Step 7: Build queries — one per (timePartition, chunk)
|
|
47
|
+
const { entity, indexDef, fields } = config;
|
|
48
|
+
const dynamoIndexName = indexDef.index;
|
|
49
|
+
const pkField = indexDef.pk.field;
|
|
50
|
+
const skField = indexDef.sk.field;
|
|
51
|
+
const resolveTableName = entity._tableTag.useSync((tc) => tc.name);
|
|
52
|
+
const decoder = (raw) => Schema.decodeUnknownEffect(entity.schemas.recordSchema)(raw).pipe(Effect.mapError((cause) => new ValidationError({
|
|
53
|
+
entityType: entity.entityType,
|
|
54
|
+
operation: "GeoSearch.decode",
|
|
55
|
+
cause,
|
|
56
|
+
})));
|
|
57
|
+
// Group search cells by parent cell — when search resolution is low,
|
|
58
|
+
// multiple search cells may map to the same parent cell at parentResolution
|
|
59
|
+
const parentResolution = fields.parentCell.resolution;
|
|
60
|
+
const queryConfigs = timePartitions.flatMap((partition) => sortedChunks
|
|
61
|
+
.map((chunk) => {
|
|
62
|
+
const firstCell = chunk[0];
|
|
63
|
+
const lastCell = chunk[chunk.length - 1];
|
|
64
|
+
if (!firstCell || !lastCell)
|
|
65
|
+
return undefined;
|
|
66
|
+
// Compute parent cell: if search resolution > parent resolution, use cellToParent.
|
|
67
|
+
// Otherwise, the search cell IS at or below parent resolution — use it directly.
|
|
68
|
+
const parentCell = searchResolution > parentResolution
|
|
69
|
+
? H3.computeParentCell(firstCell, parentResolution)
|
|
70
|
+
: firstCell;
|
|
71
|
+
const lowerCell = H3.cellToCenterChild(firstCell, H3_RESOLUTION_PRECISE);
|
|
72
|
+
const upperCell = H3.cellToUpperBound(lastCell, H3_RESOLUTION_PRECISE);
|
|
73
|
+
// Build PK composites: geo fields + any extra filter composites
|
|
74
|
+
const pkComposites = {
|
|
75
|
+
...pkFilter,
|
|
76
|
+
[fields.parentCell.field]: parentCell,
|
|
77
|
+
[fields.timePartition.field]: partition,
|
|
78
|
+
};
|
|
79
|
+
const pkValue = KeyComposer.composePk(entity._schema, entity.entityType, indexDef, pkComposites);
|
|
80
|
+
// Compose full SK values with schema prefix (must match stored SK format)
|
|
81
|
+
const lower = KeyComposer.composeSk(entity._schema, entity.entityType, 1, indexDef, {
|
|
82
|
+
[fields.cell.field]: lowerCell,
|
|
83
|
+
});
|
|
84
|
+
const upper = KeyComposer.composeSk(entity._schema, entity.entityType, 1, indexDef, {
|
|
85
|
+
[fields.cell.field]: upperCell,
|
|
86
|
+
});
|
|
87
|
+
return { pkValue, lower, upper };
|
|
88
|
+
})
|
|
89
|
+
.filter((q) => q !== undefined));
|
|
90
|
+
// Step 8: Build and execute all queries in parallel
|
|
91
|
+
const allItems = yield* Effect.forEach(queryConfigs, (qc) => {
|
|
92
|
+
const query = Query.make({
|
|
93
|
+
tableName: "",
|
|
94
|
+
indexName: dynamoIndexName,
|
|
95
|
+
pkField,
|
|
96
|
+
pkValue: qc.pkValue,
|
|
97
|
+
skField,
|
|
98
|
+
entityTypes: [entity.entityType],
|
|
99
|
+
decoder: decoder,
|
|
100
|
+
resolveTableName,
|
|
101
|
+
}).pipe(Query.where({ between: [qc.lower, qc.upper] }));
|
|
102
|
+
return Query.collect(query);
|
|
103
|
+
}, { concurrency: "unbounded" });
|
|
104
|
+
const flatItems = allItems.flat();
|
|
105
|
+
// Step 9: Compute distance, filter by radius, sort
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const item of flatItems) {
|
|
108
|
+
const coords = config.coordinates(item);
|
|
109
|
+
if (!coords)
|
|
110
|
+
continue;
|
|
111
|
+
const distance = Spherical.greatCircleDistance(center, coords);
|
|
112
|
+
if (unit === "km" ? distance / 1000 <= radius : distance <= radius) {
|
|
113
|
+
results.push({ item, distance: unit === "km" ? distance / 1000 : distance });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
results.sort((a, b) => a.distance - b.distance);
|
|
117
|
+
return sort === "ASC" ? results : results.reverse();
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=GeoSearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoSearch.js","sourceRoot":"","sources":["../src/GeoSearch.ts"],"names":[],"mappings":"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"}
|
package/dist/H3.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H3 — Pure H3 hexagonal grid algorithms for geospatial indexing.
|
|
3
|
+
*
|
|
4
|
+
* Ported from midgard's TelemetryRepository. All functions are pure
|
|
5
|
+
* (no Effect dependencies) and operate on H3 cells and coordinates.
|
|
6
|
+
*/
|
|
7
|
+
export type GeoUnit = "m" | "km";
|
|
8
|
+
/**
|
|
9
|
+
* Find the H3 resolution where hexagon edge length best matches the search radius.
|
|
10
|
+
*
|
|
11
|
+
* Starts with an estimate from average edge lengths, then refines using
|
|
12
|
+
* actual edge lengths at the given location (hexagons vary by latitude).
|
|
13
|
+
*/
|
|
14
|
+
export declare const optimalResolution: (latitude: number, longitude: number, radius: number) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Calculate the number of hexagonal rings (k) needed to cover a given radius
|
|
17
|
+
* from a center cell.
|
|
18
|
+
*/
|
|
19
|
+
export declare const optimalK: (cell: string, radius: number, unit: GeoUnit) => number;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the distance from a point to the edge between two neighboring cells.
|
|
22
|
+
*
|
|
23
|
+
* Uses spherical geometry to find the intersection of the bearing from
|
|
24
|
+
* the target cell center toward the search point with the shared edge boundary.
|
|
25
|
+
*/
|
|
26
|
+
export declare const edgeDistance: (latitude: number, longitude: number, sourceCell: string, targetCell: string) => number;
|
|
27
|
+
/**
|
|
28
|
+
* Remove cells from outer rings that are beyond the search radius.
|
|
29
|
+
*
|
|
30
|
+
* Walks outward ring by ring, checking edge distance from the search center
|
|
31
|
+
* to each cell's shared boundary with its inner-ring neighbor.
|
|
32
|
+
*/
|
|
33
|
+
export declare const pruneCells: (latitude: number, longitude: number, radius: number, cells: string[][]) => string[][];
|
|
34
|
+
/**
|
|
35
|
+
* Group sorted H3 cells into chunks of contiguous cells.
|
|
36
|
+
*
|
|
37
|
+
* Contiguous cells (adjacent in the H3 index space) can be queried
|
|
38
|
+
* with a single BETWEEN range query on the sort key.
|
|
39
|
+
*/
|
|
40
|
+
export declare const sequentialChunk: (cells: string[]) => string[][];
|
|
41
|
+
/**
|
|
42
|
+
* Compute H3 cell at the given resolution for coordinates.
|
|
43
|
+
*/
|
|
44
|
+
export declare const computeCell: (latitude: number, longitude: number, resolution: number) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Compute parent cell at the given resolution.
|
|
47
|
+
*/
|
|
48
|
+
export declare const computeParentCell: (cell: string, resolution: number) => string;
|
|
49
|
+
/**
|
|
50
|
+
* Compute the hourly time partition string for a timestamp.
|
|
51
|
+
*/
|
|
52
|
+
export declare const computeTimePartition: (timestampMs: number, bucketMs: number) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Generate all time partition strings covering a time window.
|
|
55
|
+
*/
|
|
56
|
+
export declare const getTimePartitions: (startMs: number, endMs: number, bucketMs: number) => string[];
|
|
57
|
+
/**
|
|
58
|
+
* Compute the precise child cell at resolution 15 for range query lower bounds.
|
|
59
|
+
*/
|
|
60
|
+
export declare const cellToCenterChild: (cell: string, resolution: number) => string;
|
|
61
|
+
/**
|
|
62
|
+
* Compute upper bound cell string for BETWEEN queries.
|
|
63
|
+
* Adjusts the resolution hex digit to the target resolution.
|
|
64
|
+
*/
|
|
65
|
+
export declare const cellToUpperBound: (cell: string, targetResolution: number) => string;
|
|
66
|
+
/**
|
|
67
|
+
* The bucket size in milliseconds for hourly partitions.
|
|
68
|
+
*/
|
|
69
|
+
export declare const HOURLY_BUCKET_MS: number;
|
|
70
|
+
//# sourceMappingURL=H3.d.ts.map
|
package/dist/H3.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"H3.d.ts","sourceRoot":"","sources":["../src/H3.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAA;AAShC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,QAAQ,MAAM,KAAG,MA0BvF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,MAAM,OAAO,KAAG,MAWtE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,YAAY,MAAM,KACjB,MAmCF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,QAAQ,MAAM,EACd,OAAO,MAAM,EAAE,EAAE,KAChB,MAAM,EAAE,EA0BV,CAAA;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EAAE,EAoBzD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,YAAY,MAAM,KAAG,MACpC,CAAA;AAElD;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,YAAY,MAAM,KAAG,MAClC,CAAA;AAEnC;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,aAAa,MAAM,EAAE,UAAU,MAAM,KAAG,MACjC,CAAA;AAE5C;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,OAAO,MAAM,EAAE,UAAU,MAAM,KAAG,MAAM,EAS1F,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,EAAE,YAAY,MAAM,KAAG,MAC7B,CAAA;AAExC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,kBAAkB,MAAM,KAAG,MACR,CAAA;AAElE;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAU,CAAA"}
|
package/dist/H3.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H3 — Pure H3 hexagonal grid algorithms for geospatial indexing.
|
|
3
|
+
*
|
|
4
|
+
* Ported from midgard's TelemetryRepository. All functions are pure
|
|
5
|
+
* (no Effect dependencies) and operate on H3 cells and coordinates.
|
|
6
|
+
*/
|
|
7
|
+
import * as h3 from "h3-js";
|
|
8
|
+
import * as Spherical from "./Spherical.js";
|
|
9
|
+
// Pre-compute average edge lengths for each H3 resolution (0-15)
|
|
10
|
+
const AVERAGE_EDGE_LENGTHS = Object.fromEntries(Array.from({ length: 16 }, (_, i) => [i, h3.getHexagonEdgeLengthAvg(i, h3.UNITS.m)]));
|
|
11
|
+
const HOUR_MS = 1000 * 60 * 60;
|
|
12
|
+
/**
|
|
13
|
+
* Find the H3 resolution where hexagon edge length best matches the search radius.
|
|
14
|
+
*
|
|
15
|
+
* Starts with an estimate from average edge lengths, then refines using
|
|
16
|
+
* actual edge lengths at the given location (hexagons vary by latitude).
|
|
17
|
+
*/
|
|
18
|
+
export const optimalResolution = (latitude, longitude, radius) => {
|
|
19
|
+
let startResolution = 0;
|
|
20
|
+
for (let i = 0; i < 15; i++) {
|
|
21
|
+
const current = AVERAGE_EDGE_LENGTHS[i];
|
|
22
|
+
const next = AVERAGE_EDGE_LENGTHS[i + 1];
|
|
23
|
+
if (current !== undefined && next !== undefined && current > radius && radius >= next) {
|
|
24
|
+
startResolution = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (let i = startResolution; i < 15; i++) {
|
|
29
|
+
const cell1 = h3.latLngToCell(latitude, longitude, i);
|
|
30
|
+
const edges1 = h3.originToDirectedEdges(cell1).map((edge) => h3.edgeLength(edge, h3.UNITS.m));
|
|
31
|
+
const minEdge1 = Math.min(...edges1);
|
|
32
|
+
const cell2 = h3.latLngToCell(latitude, longitude, i + 1);
|
|
33
|
+
const edges2 = h3.originToDirectedEdges(cell2).map((edge) => h3.edgeLength(edge, h3.UNITS.m));
|
|
34
|
+
const minEdge2 = Math.min(...edges2);
|
|
35
|
+
if (minEdge1 > radius && radius >= minEdge2) {
|
|
36
|
+
return i;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return startResolution;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Calculate the number of hexagonal rings (k) needed to cover a given radius
|
|
43
|
+
* from a center cell.
|
|
44
|
+
*/
|
|
45
|
+
export const optimalK = (cell, radius, unit) => {
|
|
46
|
+
const edges = h3
|
|
47
|
+
.originToDirectedEdges(cell)
|
|
48
|
+
.map((edge) => h3.edgeLength(edge, unit === "km" ? h3.UNITS.km : h3.UNITS.m));
|
|
49
|
+
const minEdgeLength = Math.min(...edges);
|
|
50
|
+
const ratio = radius / minEdgeLength;
|
|
51
|
+
const hasLinearRatio = Math.ceil(ratio) % 2 !== 0 || ratio < 1;
|
|
52
|
+
return hasLinearRatio
|
|
53
|
+
? Math.ceil((2 / 3) * ratio + 1 / 3)
|
|
54
|
+
: Math.ceil(1 / 3 + (1 / 3) * Math.sqrt(4 * ratio ** 2 - 3));
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Compute the distance from a point to the edge between two neighboring cells.
|
|
58
|
+
*
|
|
59
|
+
* Uses spherical geometry to find the intersection of the bearing from
|
|
60
|
+
* the target cell center toward the search point with the shared edge boundary.
|
|
61
|
+
*/
|
|
62
|
+
export const edgeDistance = (latitude, longitude, sourceCell, targetCell) => {
|
|
63
|
+
const targetCenter = h3.cellToLatLng(targetCell);
|
|
64
|
+
const targetCenterPoint = {
|
|
65
|
+
latitude: targetCenter[0],
|
|
66
|
+
longitude: targetCenter[1],
|
|
67
|
+
};
|
|
68
|
+
const locationPoint = { latitude, longitude };
|
|
69
|
+
const targetCenterBearing = Spherical.initialBearing(targetCenterPoint, locationPoint);
|
|
70
|
+
const edge = h3.cellsToDirectedEdge(sourceCell, targetCell);
|
|
71
|
+
const edgeLine = h3.directedEdgeToBoundary(edge);
|
|
72
|
+
const edgePointA = edgeLine[0];
|
|
73
|
+
const edgePointB = edgeLine[1];
|
|
74
|
+
if (!edgePointA || !edgePointB)
|
|
75
|
+
return Infinity;
|
|
76
|
+
const edgeA = { latitude: edgePointA[0], longitude: edgePointA[1] };
|
|
77
|
+
const edgeB = { latitude: edgePointB[0], longitude: edgePointB[1] };
|
|
78
|
+
const edgeBearing = Spherical.initialBearing(edgeA, edgeB);
|
|
79
|
+
const intersectionPoint = Spherical.intersection(targetCenterPoint, targetCenterBearing, edgeA, edgeBearing);
|
|
80
|
+
if (intersectionPoint === undefined)
|
|
81
|
+
return Infinity;
|
|
82
|
+
return h3.greatCircleDistance([latitude, longitude], [intersectionPoint.latitude, intersectionPoint.longitude], h3.UNITS.m);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Remove cells from outer rings that are beyond the search radius.
|
|
86
|
+
*
|
|
87
|
+
* Walks outward ring by ring, checking edge distance from the search center
|
|
88
|
+
* to each cell's shared boundary with its inner-ring neighbor.
|
|
89
|
+
*/
|
|
90
|
+
export const pruneCells = (latitude, longitude, radius, cells) => {
|
|
91
|
+
const firstRing = cells[0];
|
|
92
|
+
if (!firstRing)
|
|
93
|
+
return [];
|
|
94
|
+
const prunedCells = [firstRing];
|
|
95
|
+
for (let i = 0; i < cells.length - 1; i++) {
|
|
96
|
+
const sourceCells = cells[i];
|
|
97
|
+
const targetCells = cells[i + 1];
|
|
98
|
+
if (!sourceCells || !targetCells)
|
|
99
|
+
continue;
|
|
100
|
+
const prunedSet = new Set();
|
|
101
|
+
for (const sourceCell of sourceCells) {
|
|
102
|
+
for (const targetCell of targetCells) {
|
|
103
|
+
if (!prunedSet.has(targetCell) && h3.areNeighborCells(sourceCell, targetCell)) {
|
|
104
|
+
const distance = edgeDistance(latitude, longitude, sourceCell, targetCell);
|
|
105
|
+
if (radius > distance) {
|
|
106
|
+
prunedSet.add(targetCell);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
prunedCells.push(Array.from(prunedSet).sort());
|
|
112
|
+
}
|
|
113
|
+
return prunedCells;
|
|
114
|
+
};
|
|
115
|
+
// Compute the difference between two H3 cell indexes, normalized by resolution
|
|
116
|
+
const cellDifference = (a, b) => {
|
|
117
|
+
return (BigInt(`0x${b}`) - BigInt(`0x${a}`)) >> ((BigInt(15) - BigInt(`0x${b[1]}`)) * BigInt(3));
|
|
118
|
+
};
|
|
119
|
+
// Extended cell difference accounting for parent cell boundaries
|
|
120
|
+
const extendedCellDifference = (a, b) => {
|
|
121
|
+
return (cellDifference(a, b) -
|
|
122
|
+
cellDifference(h3.cellToParent(a, h3.getResolution(a) - 1), h3.cellToParent(b, h3.getResolution(b) - 1)));
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Group sorted H3 cells into chunks of contiguous cells.
|
|
126
|
+
*
|
|
127
|
+
* Contiguous cells (adjacent in the H3 index space) can be queried
|
|
128
|
+
* with a single BETWEEN range query on the sort key.
|
|
129
|
+
*/
|
|
130
|
+
export const sequentialChunk = (cells) => {
|
|
131
|
+
const sorted = [...cells].sort();
|
|
132
|
+
const first = sorted[0];
|
|
133
|
+
if (!first)
|
|
134
|
+
return [];
|
|
135
|
+
const chunks = [[first]];
|
|
136
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
137
|
+
const cell = sorted[i];
|
|
138
|
+
if (!cell)
|
|
139
|
+
continue;
|
|
140
|
+
const lastChunk = chunks[chunks.length - 1];
|
|
141
|
+
const lastCell = lastChunk?.[lastChunk.length - 1];
|
|
142
|
+
if (lastCell && extendedCellDifference(lastCell, cell) === BigInt(1)) {
|
|
143
|
+
lastChunk.push(cell);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
chunks.push([cell]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return chunks;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Compute H3 cell at the given resolution for coordinates.
|
|
153
|
+
*/
|
|
154
|
+
export const computeCell = (latitude, longitude, resolution) => h3.latLngToCell(latitude, longitude, resolution);
|
|
155
|
+
/**
|
|
156
|
+
* Compute parent cell at the given resolution.
|
|
157
|
+
*/
|
|
158
|
+
export const computeParentCell = (cell, resolution) => h3.cellToParent(cell, resolution);
|
|
159
|
+
/**
|
|
160
|
+
* Compute the hourly time partition string for a timestamp.
|
|
161
|
+
*/
|
|
162
|
+
export const computeTimePartition = (timestampMs, bucketMs) => String(Math.floor(timestampMs / bucketMs));
|
|
163
|
+
/**
|
|
164
|
+
* Generate all time partition strings covering a time window.
|
|
165
|
+
*/
|
|
166
|
+
export const getTimePartitions = (startMs, endMs, bucketMs) => {
|
|
167
|
+
const partitions = new Set();
|
|
168
|
+
let time = startMs;
|
|
169
|
+
while (time <= endMs) {
|
|
170
|
+
partitions.add(computeTimePartition(time, bucketMs));
|
|
171
|
+
time += bucketMs;
|
|
172
|
+
}
|
|
173
|
+
partitions.add(computeTimePartition(endMs, bucketMs));
|
|
174
|
+
return Array.from(partitions);
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Compute the precise child cell at resolution 15 for range query lower bounds.
|
|
178
|
+
*/
|
|
179
|
+
export const cellToCenterChild = (cell, resolution) => h3.cellToCenterChild(cell, resolution);
|
|
180
|
+
/**
|
|
181
|
+
* Compute upper bound cell string for BETWEEN queries.
|
|
182
|
+
* Adjusts the resolution hex digit to the target resolution.
|
|
183
|
+
*/
|
|
184
|
+
export const cellToUpperBound = (cell, targetResolution) => cell.slice(0, 1) + targetResolution.toString(16) + cell.slice(2);
|
|
185
|
+
/**
|
|
186
|
+
* The bucket size in milliseconds for hourly partitions.
|
|
187
|
+
*/
|
|
188
|
+
export const HOURLY_BUCKET_MS = HOUR_MS;
|
|
189
|
+
//# sourceMappingURL=H3.js.map
|
package/dist/H3.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"H3.js","sourceRoot":"","sources":["../src/H3.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spherical — Pure spherical geometry functions.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `initialBearingTo` — great-circle initial bearing (forward azimuth)
|
|
6
|
+
* - `intersection` — intersection of two great-circle paths
|
|
7
|
+
*
|
|
8
|
+
* All angles are in degrees externally, radians internally.
|
|
9
|
+
*/
|
|
10
|
+
export interface LatLng {
|
|
11
|
+
readonly latitude: number;
|
|
12
|
+
readonly longitude: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Compute the initial bearing (forward azimuth) from `from` to `to`
|
|
16
|
+
* along a great-circle path.
|
|
17
|
+
*
|
|
18
|
+
* Formula:
|
|
19
|
+
* θ = atan2(sin(Δλ)·cos(φ₂), cos(φ₁)·sin(φ₂) − sin(φ₁)·cos(φ₂)·cos(Δλ))
|
|
20
|
+
*
|
|
21
|
+
* Returns bearing in degrees [0, 360).
|
|
22
|
+
*/
|
|
23
|
+
export declare const initialBearing: (from: LatLng, to: LatLng) => number;
|
|
24
|
+
/**
|
|
25
|
+
* Find the intersection point of two great-circle paths defined by
|
|
26
|
+
* a start point and initial bearing each.
|
|
27
|
+
*
|
|
28
|
+
* Uses the Vincenty formula to compute the intersection.
|
|
29
|
+
* Returns `undefined` when paths are parallel, antipodal, or ambiguous.
|
|
30
|
+
*/
|
|
31
|
+
export declare const intersection: (p1: LatLng, brng1: number, p2: LatLng, brng2: number) => LatLng | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Compute the great-circle distance between two points in meters.
|
|
34
|
+
* Uses the Haversine formula.
|
|
35
|
+
*/
|
|
36
|
+
export declare const greatCircleDistance: (from: LatLng, to: LatLng) => number;
|
|
37
|
+
//# sourceMappingURL=Spherical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spherical.d.ts","sourceRoot":"","sources":["../src/Spherical.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAKD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,IAAI,MAAM,KAAG,MAUzD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,IAAI,MAAM,EACV,OAAO,MAAM,EACb,IAAI,MAAM,EACV,OAAO,MAAM,KACZ,MAAM,GAAG,SA2DX,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,IAAI,MAAM,KAAG,MAW9D,CAAA"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spherical — Pure spherical geometry functions.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `initialBearingTo` — great-circle initial bearing (forward azimuth)
|
|
6
|
+
* - `intersection` — intersection of two great-circle paths
|
|
7
|
+
*
|
|
8
|
+
* All angles are in degrees externally, radians internally.
|
|
9
|
+
*/
|
|
10
|
+
const toRad = (deg) => (deg * Math.PI) / 180;
|
|
11
|
+
const toDeg = (rad) => (rad * 180) / Math.PI;
|
|
12
|
+
/**
|
|
13
|
+
* Compute the initial bearing (forward azimuth) from `from` to `to`
|
|
14
|
+
* along a great-circle path.
|
|
15
|
+
*
|
|
16
|
+
* Formula:
|
|
17
|
+
* θ = atan2(sin(Δλ)·cos(φ₂), cos(φ₁)·sin(φ₂) − sin(φ₁)·cos(φ₂)·cos(Δλ))
|
|
18
|
+
*
|
|
19
|
+
* Returns bearing in degrees [0, 360).
|
|
20
|
+
*/
|
|
21
|
+
export const initialBearing = (from, to) => {
|
|
22
|
+
const φ1 = toRad(from.latitude);
|
|
23
|
+
const φ2 = toRad(to.latitude);
|
|
24
|
+
const Δλ = toRad(to.longitude - from.longitude);
|
|
25
|
+
const x = Math.sin(Δλ) * Math.cos(φ2);
|
|
26
|
+
const y = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
|
|
27
|
+
const θ = Math.atan2(x, y);
|
|
28
|
+
return (toDeg(θ) + 360) % 360;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Find the intersection point of two great-circle paths defined by
|
|
32
|
+
* a start point and initial bearing each.
|
|
33
|
+
*
|
|
34
|
+
* Uses the Vincenty formula to compute the intersection.
|
|
35
|
+
* Returns `undefined` when paths are parallel, antipodal, or ambiguous.
|
|
36
|
+
*/
|
|
37
|
+
export const intersection = (p1, brng1, p2, brng2) => {
|
|
38
|
+
const φ1 = toRad(p1.latitude);
|
|
39
|
+
const λ1 = toRad(p1.longitude);
|
|
40
|
+
const φ2 = toRad(p2.latitude);
|
|
41
|
+
const λ2 = toRad(p2.longitude);
|
|
42
|
+
const θ13 = toRad(brng1);
|
|
43
|
+
const θ23 = toRad(brng2);
|
|
44
|
+
const Δφ = φ2 - φ1;
|
|
45
|
+
const Δλ = λ2 - λ1;
|
|
46
|
+
// Angular distance p1-p2
|
|
47
|
+
const δ12 = 2 *
|
|
48
|
+
Math.asin(Math.sqrt(Math.sin(Δφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2));
|
|
49
|
+
if (Math.abs(δ12) < Number.EPSILON)
|
|
50
|
+
return undefined;
|
|
51
|
+
// Initial/final bearings between p1-p2
|
|
52
|
+
const cosθa = (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ1));
|
|
53
|
+
const cosθb = (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ2));
|
|
54
|
+
const θa = Math.acos(Math.min(1, Math.max(-1, cosθa)));
|
|
55
|
+
const θb = Math.acos(Math.min(1, Math.max(-1, cosθb)));
|
|
56
|
+
const θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * Math.PI - θa;
|
|
57
|
+
const θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * Math.PI - θb : θb;
|
|
58
|
+
const α1 = θ13 - θ12;
|
|
59
|
+
const α2 = θ21 - θ23;
|
|
60
|
+
// Check for parallel/antipodal paths
|
|
61
|
+
if (Math.sin(α1) === 0 && Math.sin(α2) === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (Math.sin(α1) * Math.sin(α2) < 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
const cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
|
|
66
|
+
const δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2), Math.cos(α2) + Math.cos(α1) * cosα3);
|
|
67
|
+
const φ3 = Math.asin(Math.min(1, Math.max(-1, Math.sin(φ1) * Math.cos(δ13) + Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13))));
|
|
68
|
+
const Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1), Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
|
|
69
|
+
const λ3 = λ1 + Δλ13;
|
|
70
|
+
return {
|
|
71
|
+
latitude: toDeg(φ3),
|
|
72
|
+
longitude: ((toDeg(λ3) + 540) % 360) - 180,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Compute the great-circle distance between two points in meters.
|
|
77
|
+
* Uses the Haversine formula.
|
|
78
|
+
*/
|
|
79
|
+
export const greatCircleDistance = (from, to) => {
|
|
80
|
+
const R = 6371008.8; // Earth mean radius in meters
|
|
81
|
+
const φ1 = toRad(from.latitude);
|
|
82
|
+
const φ2 = toRad(to.latitude);
|
|
83
|
+
const Δφ = toRad(to.latitude - from.latitude);
|
|
84
|
+
const Δλ = toRad(to.longitude - from.longitude);
|
|
85
|
+
const a = Math.sin(Δφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
|
|
86
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
87
|
+
return R * c;
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=Spherical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spherical.js","sourceRoot":"","sources":["../src/Spherical.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { BoundGeoIndex, Coordinates, GeoEntity, GeoFields, GeoIndex as GeoIndexType, NearbyOptions, NearbyResult, SortOrder, TimeBucket, } from "./GeoIndex.js";
|
|
2
|
+
export * as GeoIndex from "./GeoIndex.js";
|
|
3
|
+
export * as H3 from "./H3.js";
|
|
4
|
+
export type { LatLng } from "./Spherical.js";
|
|
5
|
+
export * as Spherical from "./Spherical.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,QAAQ,IAAI,YAAY,EACxB,aAAa,EACb,YAAY,EACZ,SAAS,EACT,UAAU,GACX,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAE7B,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effect-dynamodb/geo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Geospatial index and search for effect-dynamodb using H3 hexagonal grid",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Justin Menga",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/jmenga/effect-dynamodb",
|
|
11
|
+
"directory": "packages/effect-dynamodb-geo"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://jmenga.github.io/effect-dynamodb/guides/geospatial",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/jmenga/effect-dynamodb/issues"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"effect",
|
|
22
|
+
"effect-ts",
|
|
23
|
+
"dynamodb",
|
|
24
|
+
"geospatial",
|
|
25
|
+
"h3",
|
|
26
|
+
"hexagonal-grid",
|
|
27
|
+
"geo-search",
|
|
28
|
+
"location",
|
|
29
|
+
"proximity"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"main": "dist/index.js",
|
|
33
|
+
"types": "dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:connected": "vitest run --config vitest.connected.ts",
|
|
50
|
+
"check": "tsc --noEmit && tsc --noEmit -p tsconfig.examples.json",
|
|
51
|
+
"lint": "biome check .",
|
|
52
|
+
"lint:fix": "biome check --write ."
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"h3-js": "^4.2.1"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"effect": "^4.0.0-beta.48",
|
|
59
|
+
"effect-dynamodb": "workspace:^"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@aws-sdk/client-dynamodb": "^3.700.0",
|
|
63
|
+
"@effect/vitest": "4.0.0-beta.48",
|
|
64
|
+
"@types/node": "^25.0.0",
|
|
65
|
+
"effect": "4.0.0-beta.48",
|
|
66
|
+
"effect-dynamodb": "workspace:*",
|
|
67
|
+
"typescript": "^5.7.0",
|
|
68
|
+
"vitest": "^4.1.0"
|
|
69
|
+
}
|
|
70
|
+
}
|