Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,35 @@ Both functions take the following parameters:

- `maxResults`: Maximum number of results to return (default: `10`).

##### Bounding box search

You can find all stations within a geographic bounding box using the `bbox` function:

```typescript
import { bbox } from "@neaps/tide-database";

// Find stations in the Boston area
const stations = bbox(-71.5, 42, -70.5, 42.8);
console.log("Stations in bounds:", stations.length);

// With a filter
const referenceOnly = bbox(
-72,
41,
-70,
43,
(station) => station.type === "reference",
);
```

Parameters:

- `minLon`: Minimum longitude (west edge of the bounding box).
- `minLat`: Minimum latitude (south edge of the bounding box).
- `maxLon`: Maximum longitude (east edge of the bounding box).
- `maxLat`: Maximum latitude (north edge of the bounding box).
- `filter`: Optional function that takes a station and returns `true` to include it in results, or `false` to exclude it.

##### Full-text search

You can search for stations by name, region, country, or continent using the `search` function. It supports fuzzy matching and prefix search:
Expand Down
21 changes: 19 additions & 2 deletions src/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ export type Position = Latitude & Longitude;
type Latitude = { latitude: number } | { lat: number };
type Longitude = { longitude: number } | { lon: number } | { lng: number };

export type Filter = (station: Station) => boolean;

export type NearestOptions = Position & {
maxDistance?: number;
filter?: (station: Station) => boolean;
filter?: Filter;
};

export type NearOptions = NearestOptions & {
maxResults?: number;
};

export type TextSearchOptions = {
filter?: (station: Station) => boolean;
filter?: Filter;
maxResults?: number;
};

Expand Down Expand Up @@ -66,6 +68,21 @@ export function nearest(options: NearestOptions): StationWithDistance | null {
return results[0] ?? null;
}

/**
* Find stations within a bounding box.
*/
export function bbox(
minLon: number,
minLat: number,
maxLon: number,
maxLat: number,
filter?: Filter,
): Station[] {
const ids: number[] = geoIndex.range(minLon, minLat, maxLon, maxLat);
const results = ids.map((id) => stations[id]!);
return filter ? results.filter(filter) : results;
}

export function positionToPoint(options: Position): [number, number] {
const longitude =
"longitude" in options
Expand Down
36 changes: 35 additions & 1 deletion test/search.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, test, expect } from "vitest";
import { near, nearest, search } from "../src/index.js";
import { near, nearest, search, bbox } from "../src/index.js";

describe("near", () => {
[
Expand Down Expand Up @@ -68,6 +68,40 @@ describe("nearest", () => {
});
});

describe("bbox", () => {
test("returns stations within bounding box", () => {
// Boston area: roughly -71.5 to -70.5 lon, 42 to 42.8 lat
const results = bbox(-71.5, 42, -70.5, 42.8);
expect(results.length).toBeGreaterThan(0);
results.forEach((station) => {
expect(station.longitude).toBeGreaterThanOrEqual(-71.5);
expect(station.longitude).toBeLessThanOrEqual(-70.5);
expect(station.latitude).toBeGreaterThanOrEqual(42);
expect(station.latitude).toBeLessThanOrEqual(42.8);
});
});

test("returns empty array for bbox with no stations", () => {
// Middle of the Pacific
const results = bbox(-170, -50, -169, -49);
expect(results).toEqual([]);
});

test("can filter results", () => {
const results = bbox(
-72,
41,
-70,
43,
(station) => station.type === "reference",
);
expect(results.length).toBeGreaterThan(0);
results.forEach((station) => {
expect(station.type).toBe("reference");
});
});
});

describe("search", () => {
test("searches by name", () => {
const results = search("Boston");
Expand Down