townshipamerica 1.0.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 +227 -0
- package/dist/index.cjs +340 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +185 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +307 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maps & Apps Inc.
|
|
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,227 @@
|
|
|
1
|
+
# townshipamerica
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the [Township America API](https://townshipamerica.com/api) — convert US PLSS (Public Land Survey System) legal land descriptions to GPS coordinates and back.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install townshipamerica
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { TownshipClient } from "townshipamerica";
|
|
15
|
+
|
|
16
|
+
const client = new TownshipClient({ apiKey: "YOUR_API_KEY" });
|
|
17
|
+
|
|
18
|
+
// PLSS to GPS
|
|
19
|
+
const result = await client.search("NW 25 24N 1E 6th Meridian");
|
|
20
|
+
console.log(result.latitude, result.longitude); // 41.077932 -104.01924
|
|
21
|
+
console.log(result.state); // "Colorado"
|
|
22
|
+
console.log(result.county); // "Weld"
|
|
23
|
+
|
|
24
|
+
// GPS to PLSS
|
|
25
|
+
const reverse = await client.reverse(-104.01924, 41.077932);
|
|
26
|
+
console.log(reverse.legalLocation); // "NW 25 24N 1E 6th Meridian"
|
|
27
|
+
|
|
28
|
+
// Autocomplete
|
|
29
|
+
const suggestions = await client.autocomplete("NW 25", { limit: 5 });
|
|
30
|
+
console.log(suggestions.features[0].properties.legal_location);
|
|
31
|
+
|
|
32
|
+
// Batch (up to 100 per request, auto-chunks larger arrays)
|
|
33
|
+
const batch = await client.batchSearch([
|
|
34
|
+
"NW 25 24N 1E 6th Meridian",
|
|
35
|
+
"NE 12 4N 5E Indian Meridian"
|
|
36
|
+
]);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API Reference
|
|
40
|
+
|
|
41
|
+
### `new TownshipClient(options)`
|
|
42
|
+
|
|
43
|
+
| Option | Type | Default | Description |
|
|
44
|
+
| --------- | -------- | --------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
45
|
+
| `apiKey` | `string` | — | **Required.** Your API key from [townshipamerica.com](https://townshipamerica.com) |
|
|
46
|
+
| `baseUrl` | `string` | `https://developer.townshipamerica.com` | Override the API base URL |
|
|
47
|
+
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### `client.search(legalLocation)`
|
|
52
|
+
|
|
53
|
+
Convert a PLSS legal land description to GPS coordinates.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const result = await client.search("NE 12 4N 5E Indian Meridian");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Returns:** `SearchResult`
|
|
60
|
+
|
|
61
|
+
| Field | Type | Description |
|
|
62
|
+
| ------------------------ | -------------------------- | -------------------------------------------------- |
|
|
63
|
+
| `legalLocation` | `string` | Normalized legal description |
|
|
64
|
+
| `latitude` | `number` | Centroid latitude |
|
|
65
|
+
| `longitude` | `number` | Centroid longitude |
|
|
66
|
+
| `state` | `string` | US state |
|
|
67
|
+
| `county` | `string` | County |
|
|
68
|
+
| `unit` | `string` | `Township`, `First Division`, or `Second Division` |
|
|
69
|
+
| `surveySystem` | `string` | `PLSS` |
|
|
70
|
+
| `alternateLegalLocation` | `string` | Alternate description format |
|
|
71
|
+
| `boundary` | `GeoJSONPolygon \| null` | Grid boundary polygon |
|
|
72
|
+
| `raw` | `GeoJSONFeatureCollection` | Raw API response |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### `client.reverse(longitude, latitude, options?)`
|
|
77
|
+
|
|
78
|
+
Find the PLSS description at GPS coordinates.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await client.reverse(-104.01924, 41.077932, {
|
|
82
|
+
unit: "First Division"
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Options:**
|
|
87
|
+
|
|
88
|
+
| Option | Type | Description |
|
|
89
|
+
| ------ | -------------------------------------------------------------- | ---------------- |
|
|
90
|
+
| `unit` | `'Township' \| 'First Division' \| 'Second Division' \| 'all'` | Precision filter |
|
|
91
|
+
|
|
92
|
+
**Returns:** `SearchResult` (single unit) or `SearchResult[]` (when `unit: 'all'`)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `client.autocomplete(query, options?)`
|
|
97
|
+
|
|
98
|
+
Get autocomplete suggestions for a partial PLSS description.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const fc = await client.autocomplete("NW 25", { limit: 5 });
|
|
102
|
+
for (const feature of fc.features) {
|
|
103
|
+
console.log(feature.properties.legal_location);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Options:**
|
|
108
|
+
|
|
109
|
+
| Option | Type | Description |
|
|
110
|
+
| ----------- | ------------ | ------------------------------------- |
|
|
111
|
+
| `limit` | `number` | Max suggestions, 1–10 (default 3) |
|
|
112
|
+
| `proximity` | `[lng, lat]` | Bias results toward these coordinates |
|
|
113
|
+
|
|
114
|
+
**Returns:** `GeoJSONFeatureCollection`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `client.batchSearch(locations, options?)`
|
|
119
|
+
|
|
120
|
+
Convert multiple descriptions in one request. Automatically chunks arrays larger than 100.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const results = await client.batchSearch([
|
|
124
|
+
"NW 25 24N 1E 6th Meridian",
|
|
125
|
+
"NE 12 4N 5E Indian Meridian"
|
|
126
|
+
]);
|
|
127
|
+
// results[0] is SearchResult or null (if no match)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Returns:** `(SearchResult | null)[]`
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `client.batchReverse(coordinates, options?)`
|
|
135
|
+
|
|
136
|
+
Reverse geocode multiple coordinate pairs. Automatically chunks arrays larger than 100.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const results = await client.batchReverse(
|
|
140
|
+
[
|
|
141
|
+
[-104.01924, 41.077932],
|
|
142
|
+
[-104.648933, 41.454928]
|
|
143
|
+
],
|
|
144
|
+
{ unit: "Township" }
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Options:**
|
|
149
|
+
|
|
150
|
+
| Option | Type | Description |
|
|
151
|
+
| ----------- | -------------------------------------------------------------- | ----------------------------------- |
|
|
152
|
+
| `unit` | `'Township' \| 'First Division' \| 'Second Division' \| 'all'` | Precision filter |
|
|
153
|
+
| `chunkSize` | `number` | Items per request (default/max 100) |
|
|
154
|
+
|
|
155
|
+
**Returns:** `(SearchResult | null)[]`
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### `client.boundary(legalLocation)`
|
|
160
|
+
|
|
161
|
+
Get just the boundary polygon for a legal description.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const polygon = await client.boundary("NW 25 24N 1E 6th Meridian");
|
|
165
|
+
// polygon.type === 'Polygon'
|
|
166
|
+
// polygon.coordinates === [[[lng, lat], ...]]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Returns:** `GeoJSONPolygon | null`
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `client.raw(legalLocation)`
|
|
174
|
+
|
|
175
|
+
Get the raw GeoJSON FeatureCollection from the API (no transformation).
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const fc = await client.raw("NW 25 24N 1E 6th Meridian");
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Returns:** `GeoJSONFeatureCollection`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Error Handling
|
|
186
|
+
|
|
187
|
+
All errors extend `TownshipError`:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import {
|
|
191
|
+
TownshipClient,
|
|
192
|
+
TownshipError,
|
|
193
|
+
AuthenticationError,
|
|
194
|
+
ValidationError,
|
|
195
|
+
NotFoundError,
|
|
196
|
+
RateLimitError,
|
|
197
|
+
PayloadTooLargeError
|
|
198
|
+
} from "townshipamerica";
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
await client.search("NW 25 24N 1E 6th Meridian");
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof AuthenticationError) {
|
|
204
|
+
// Invalid API key (401)
|
|
205
|
+
} else if (error instanceof NotFoundError) {
|
|
206
|
+
// No results found
|
|
207
|
+
} else if (error instanceof RateLimitError) {
|
|
208
|
+
// Too many requests (429)
|
|
209
|
+
} else if (error instanceof ValidationError) {
|
|
210
|
+
// Bad request (400)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## PLSS Format Examples
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
NW 25 24N 1E 6th Meridian → Quarter Section (First Division)
|
|
219
|
+
25 24N 1E 6th Meridian → Section (Second Division)
|
|
220
|
+
24N 1E 6th Meridian → Township
|
|
221
|
+
NE 12 4N 5E Indian Meridian → Named meridian
|
|
222
|
+
7 2N 3E Black Hills Meridian → Section with named meridian
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT — [Maps & Apps Inc.](https://townshipamerica.com)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
NotFoundError: () => NotFoundError,
|
|
25
|
+
PayloadTooLargeError: () => PayloadTooLargeError,
|
|
26
|
+
RateLimitError: () => RateLimitError,
|
|
27
|
+
TownshipClient: () => TownshipClient,
|
|
28
|
+
TownshipError: () => TownshipError,
|
|
29
|
+
ValidationError: () => ValidationError
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var TownshipError = class extends Error {
|
|
35
|
+
constructor(message, statusCode = null) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.statusCode = statusCode;
|
|
38
|
+
this.name = "TownshipError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var AuthenticationError = class extends TownshipError {
|
|
42
|
+
constructor(message = "Invalid or missing API key") {
|
|
43
|
+
super(message, 401);
|
|
44
|
+
this.name = "AuthenticationError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var NotFoundError = class extends TownshipError {
|
|
48
|
+
constructor(message = "Location not found") {
|
|
49
|
+
super(message, 404);
|
|
50
|
+
this.name = "NotFoundError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var RateLimitError = class extends TownshipError {
|
|
54
|
+
constructor(message = "Rate limit exceeded. Please slow down your requests.") {
|
|
55
|
+
super(message, 429);
|
|
56
|
+
this.name = "RateLimitError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var ValidationError = class extends TownshipError {
|
|
60
|
+
constructor(message = "Invalid request") {
|
|
61
|
+
super(message, 400);
|
|
62
|
+
this.name = "ValidationError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var PayloadTooLargeError = class extends TownshipError {
|
|
66
|
+
constructor(message = "Batch payload exceeds the maximum of 100 records per request") {
|
|
67
|
+
super(message, 413);
|
|
68
|
+
this.name = "PayloadTooLargeError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/client.ts
|
|
73
|
+
var DEFAULT_BASE_URL = "https://developer.townshipamerica.com";
|
|
74
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
75
|
+
var MAX_BATCH_SIZE = 100;
|
|
76
|
+
function toSearchResult(fc) {
|
|
77
|
+
const centroid = fc.features.find((f) => f.properties.shape === "centroid");
|
|
78
|
+
const grid = fc.features.find((f) => f.properties.shape === "grid");
|
|
79
|
+
const props = centroid?.properties ?? grid?.properties;
|
|
80
|
+
if (!props) {
|
|
81
|
+
throw new TownshipError("Unexpected API response: no features returned");
|
|
82
|
+
}
|
|
83
|
+
const [lng, lat] = centroid ? centroid.geometry.coordinates : [0, 0];
|
|
84
|
+
return {
|
|
85
|
+
legalLocation: props.legal_location,
|
|
86
|
+
latitude: lat,
|
|
87
|
+
longitude: lng,
|
|
88
|
+
state: props.state,
|
|
89
|
+
county: props.county,
|
|
90
|
+
unit: props.unit,
|
|
91
|
+
surveySystem: props.survey_system,
|
|
92
|
+
alternateLegalLocation: props.alternate_legal_location,
|
|
93
|
+
boundary: grid ? grid.geometry : null,
|
|
94
|
+
raw: fc
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var TownshipClient = class {
|
|
98
|
+
baseUrl;
|
|
99
|
+
apiKey;
|
|
100
|
+
timeout;
|
|
101
|
+
constructor(options) {
|
|
102
|
+
this.apiKey = options.apiKey;
|
|
103
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
104
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
105
|
+
if (!this.apiKey) {
|
|
106
|
+
throw new TownshipError("apiKey is required");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// --- Internal ---
|
|
110
|
+
async request(path, init) {
|
|
111
|
+
const url = `${this.baseUrl}${path}`;
|
|
112
|
+
const controller = new AbortController();
|
|
113
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(url, {
|
|
116
|
+
...init,
|
|
117
|
+
headers: {
|
|
118
|
+
"X-API-Key": this.apiKey,
|
|
119
|
+
"User-Agent": "townshipamerica-js/1.0.0",
|
|
120
|
+
...init?.headers ?? {}
|
|
121
|
+
},
|
|
122
|
+
signal: controller.signal
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
await this.handleError(response);
|
|
126
|
+
}
|
|
127
|
+
return await response.json();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error instanceof TownshipError) throw error;
|
|
130
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
131
|
+
throw new TownshipError("Request timed out");
|
|
132
|
+
}
|
|
133
|
+
throw new TownshipError(error instanceof Error ? error.message : "Unknown error");
|
|
134
|
+
} finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async handleError(response) {
|
|
139
|
+
let message;
|
|
140
|
+
try {
|
|
141
|
+
const body = await response.json();
|
|
142
|
+
message = body.error ?? body.message ?? response.statusText;
|
|
143
|
+
} catch {
|
|
144
|
+
message = response.statusText;
|
|
145
|
+
}
|
|
146
|
+
const status = response.status;
|
|
147
|
+
if (status === 400) throw new ValidationError(message);
|
|
148
|
+
if (status === 401) throw new AuthenticationError(message);
|
|
149
|
+
if (status === 404) throw new NotFoundError(message);
|
|
150
|
+
if (status === 413) throw new PayloadTooLargeError(message);
|
|
151
|
+
if (status === 429) throw new RateLimitError(message);
|
|
152
|
+
throw new TownshipError(message, status);
|
|
153
|
+
}
|
|
154
|
+
// --- Search ---
|
|
155
|
+
/**
|
|
156
|
+
* Convert a PLSS legal land description to GPS coordinates.
|
|
157
|
+
*
|
|
158
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
159
|
+
* @returns A SearchResult with coordinates, boundary, and metadata.
|
|
160
|
+
* @throws {ValidationError} If the location string is invalid.
|
|
161
|
+
* @throws {NotFoundError} If no match is found.
|
|
162
|
+
*/
|
|
163
|
+
async search(legalLocation) {
|
|
164
|
+
const params = new URLSearchParams({ location: legalLocation });
|
|
165
|
+
const data = await this.request(
|
|
166
|
+
`/search/legal-location?${params}`
|
|
167
|
+
);
|
|
168
|
+
if (!("features" in data) || !data.features?.length) {
|
|
169
|
+
throw new NotFoundError(`No results found for "${legalLocation}"`);
|
|
170
|
+
}
|
|
171
|
+
return toSearchResult(data);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find the PLSS legal land description at the given GPS coordinates.
|
|
175
|
+
*
|
|
176
|
+
* @param longitude - Longitude (x) coordinate.
|
|
177
|
+
* @param latitude - Latitude (y) coordinate.
|
|
178
|
+
* @param options - Optional unit filter.
|
|
179
|
+
* @returns A SearchResult or array of SearchResults (when unit is 'all').
|
|
180
|
+
*/
|
|
181
|
+
async reverse(longitude, latitude, options) {
|
|
182
|
+
const params = new URLSearchParams({
|
|
183
|
+
location: `${longitude},${latitude}`
|
|
184
|
+
});
|
|
185
|
+
if (options?.unit) {
|
|
186
|
+
params.set("unit", options.unit);
|
|
187
|
+
}
|
|
188
|
+
const data = await this.request(
|
|
189
|
+
`/search/coordinates?${params}`
|
|
190
|
+
);
|
|
191
|
+
if (!("features" in data) || !data.features?.length) {
|
|
192
|
+
throw new NotFoundError(`No results found for coordinates [${longitude}, ${latitude}]`);
|
|
193
|
+
}
|
|
194
|
+
const fc = data;
|
|
195
|
+
if (options?.unit === "all") {
|
|
196
|
+
const groups = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const feature of fc.features) {
|
|
198
|
+
const key = feature.properties.legal_location;
|
|
199
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
200
|
+
groups.get(key).push(feature);
|
|
201
|
+
}
|
|
202
|
+
return Array.from(groups.values()).map((features) => {
|
|
203
|
+
return toSearchResult({ type: "FeatureCollection", features });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return toSearchResult(fc);
|
|
207
|
+
}
|
|
208
|
+
// --- Autocomplete ---
|
|
209
|
+
/**
|
|
210
|
+
* Get autocomplete suggestions for a partial PLSS description.
|
|
211
|
+
*
|
|
212
|
+
* @param query - Partial search query (minimum 2 characters).
|
|
213
|
+
* @param options - Optional limit (1–10, default 3) and proximity.
|
|
214
|
+
* @returns Raw GeoJSON FeatureCollection with suggestion features.
|
|
215
|
+
*/
|
|
216
|
+
async autocomplete(query, options) {
|
|
217
|
+
const params = new URLSearchParams({ location: query });
|
|
218
|
+
if (options?.limit != null) {
|
|
219
|
+
params.set("limit", String(options.limit));
|
|
220
|
+
}
|
|
221
|
+
if (options?.proximity) {
|
|
222
|
+
params.set("proximity", `${options.proximity[0]},${options.proximity[1]}`);
|
|
223
|
+
}
|
|
224
|
+
return this.request(`/autocomplete/legal-location?${params}`);
|
|
225
|
+
}
|
|
226
|
+
// --- Batch ---
|
|
227
|
+
/**
|
|
228
|
+
* Convert multiple PLSS descriptions to GPS coordinates in one request.
|
|
229
|
+
* Auto-chunks if more than 100 items.
|
|
230
|
+
*
|
|
231
|
+
* @param locations - Array of PLSS legal land descriptions.
|
|
232
|
+
* @param options - Optional chunk size.
|
|
233
|
+
* @returns Array of SearchResult or null for each input.
|
|
234
|
+
*/
|
|
235
|
+
async batchSearch(locations, options) {
|
|
236
|
+
const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
|
|
237
|
+
const results = [];
|
|
238
|
+
for (let i = 0; i < locations.length; i += chunkSize) {
|
|
239
|
+
const chunk = locations.slice(i, i + chunkSize);
|
|
240
|
+
const data = await this.request(
|
|
241
|
+
"/batch/legal-location",
|
|
242
|
+
{
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: { "Content-Type": "application/json" },
|
|
245
|
+
body: JSON.stringify(chunk)
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
for (const fc of data) {
|
|
249
|
+
if (!fc || !fc.features?.length) {
|
|
250
|
+
results.push(null);
|
|
251
|
+
} else {
|
|
252
|
+
results.push(toSearchResult(fc));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return results;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Find PLSS descriptions for multiple coordinate pairs in one request.
|
|
260
|
+
* Auto-chunks if more than 100 items.
|
|
261
|
+
*
|
|
262
|
+
* @param coordinates - Array of [longitude, latitude] pairs.
|
|
263
|
+
* @param options - Optional unit filter and chunk size.
|
|
264
|
+
* @returns Array of SearchResult or null for each input.
|
|
265
|
+
*/
|
|
266
|
+
async batchReverse(coordinates, options) {
|
|
267
|
+
const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
|
|
268
|
+
const results = [];
|
|
269
|
+
for (let i = 0; i < coordinates.length; i += chunkSize) {
|
|
270
|
+
const chunk = coordinates.slice(i, i + chunkSize);
|
|
271
|
+
const body = { coordinates: chunk };
|
|
272
|
+
if (options?.unit) {
|
|
273
|
+
body.unit = options.unit;
|
|
274
|
+
}
|
|
275
|
+
const data = await this.request("/batch/coordinates", {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/json" },
|
|
278
|
+
body: JSON.stringify(body)
|
|
279
|
+
});
|
|
280
|
+
if (Array.isArray(data)) {
|
|
281
|
+
for (const fc of data) {
|
|
282
|
+
if (!fc || !fc.features?.length) {
|
|
283
|
+
results.push(null);
|
|
284
|
+
} else {
|
|
285
|
+
results.push(toSearchResult(fc));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else if (data && "features" in data) {
|
|
289
|
+
const groups = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const feature of data.features) {
|
|
291
|
+
const key = JSON.stringify(feature.properties.search_term);
|
|
292
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
293
|
+
groups.get(key).push(feature);
|
|
294
|
+
}
|
|
295
|
+
for (const features of groups.values()) {
|
|
296
|
+
results.push(toSearchResult({ type: "FeatureCollection", features }));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
// --- Convenience ---
|
|
303
|
+
/**
|
|
304
|
+
* Get the boundary polygon for a legal land description.
|
|
305
|
+
*
|
|
306
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
307
|
+
* @returns The boundary GeoJSON Polygon, or null if not found.
|
|
308
|
+
*/
|
|
309
|
+
async boundary(legalLocation) {
|
|
310
|
+
const result = await this.search(legalLocation);
|
|
311
|
+
return result.boundary;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get the raw GeoJSON FeatureCollection for a legal land description.
|
|
315
|
+
*
|
|
316
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
317
|
+
* @returns The raw GeoJSON FeatureCollection from the API.
|
|
318
|
+
*/
|
|
319
|
+
async raw(legalLocation) {
|
|
320
|
+
const params = new URLSearchParams({ location: legalLocation });
|
|
321
|
+
const data = await this.request(
|
|
322
|
+
`/search/legal-location?${params}`
|
|
323
|
+
);
|
|
324
|
+
if (!("features" in data)) {
|
|
325
|
+
return { type: "FeatureCollection", features: [] };
|
|
326
|
+
}
|
|
327
|
+
return data;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
331
|
+
0 && (module.exports = {
|
|
332
|
+
AuthenticationError,
|
|
333
|
+
NotFoundError,
|
|
334
|
+
PayloadTooLargeError,
|
|
335
|
+
RateLimitError,
|
|
336
|
+
TownshipClient,
|
|
337
|
+
TownshipError,
|
|
338
|
+
ValidationError
|
|
339
|
+
});
|
|
340
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { TownshipClient } from \"./client.js\";\nexport {\n TownshipError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n PayloadTooLargeError\n} from \"./errors.js\";\nexport type {\n TownshipClientOptions,\n GeoJSONPoint,\n GeoJSONPolygon,\n GeoJSONFeature,\n GeoJSONFeatureCollection,\n FeatureProperties,\n Unit,\n SearchResult,\n ReverseOptions,\n AutocompleteOptions,\n BatchSearchOptions,\n BatchReverseOptions\n} from \"./types.js\";\n","/**\n * Base error class for all Township SDK errors.\n */\nexport class TownshipError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number | null = null\n ) {\n super(message);\n this.name = \"TownshipError\";\n }\n}\n\n/**\n * Thrown when the API key is missing or invalid (HTTP 401).\n */\nexport class AuthenticationError extends TownshipError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, 401);\n this.name = \"AuthenticationError\";\n }\n}\n\n/**\n * Thrown when no result is found for the given input.\n */\nexport class NotFoundError extends TownshipError {\n constructor(message = \"Location not found\") {\n super(message, 404);\n this.name = \"NotFoundError\";\n }\n}\n\n/**\n * Thrown when the API rate limit is exceeded (HTTP 429).\n */\nexport class RateLimitError extends TownshipError {\n constructor(message = \"Rate limit exceeded. Please slow down your requests.\") {\n super(message, 429);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Thrown when the request is malformed (HTTP 400).\n */\nexport class ValidationError extends TownshipError {\n constructor(message = \"Invalid request\") {\n super(message, 400);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Thrown when the batch payload exceeds the maximum size (HTTP 413).\n */\nexport class PayloadTooLargeError extends TownshipError {\n constructor(message = \"Batch payload exceeds the maximum of 100 records per request\") {\n super(message, 413);\n this.name = \"PayloadTooLargeError\";\n }\n}\n","import {\n AuthenticationError,\n NotFoundError,\n PayloadTooLargeError,\n RateLimitError,\n TownshipError,\n ValidationError\n} from \"./errors.js\";\nimport type {\n AutocompleteOptions,\n BatchReverseOptions,\n BatchSearchOptions,\n GeoJSONFeatureCollection,\n GeoJSONPolygon,\n ReverseOptions,\n SearchResult,\n TownshipClientOptions\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://developer.townshipamerica.com\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst MAX_BATCH_SIZE = 100;\n\nfunction toSearchResult(fc: GeoJSONFeatureCollection): SearchResult {\n const centroid = fc.features.find((f) => f.properties.shape === \"centroid\");\n const grid = fc.features.find((f) => f.properties.shape === \"grid\");\n\n const props = centroid?.properties ?? grid?.properties;\n if (!props) {\n throw new TownshipError(\"Unexpected API response: no features returned\");\n }\n\n const [lng, lat] = centroid ? (centroid.geometry.coordinates as [number, number]) : [0, 0];\n\n return {\n legalLocation: props.legal_location,\n latitude: lat,\n longitude: lng,\n state: props.state,\n county: props.county,\n unit: props.unit,\n surveySystem: props.survey_system,\n alternateLegalLocation: props.alternate_legal_location,\n boundary: grid ? (grid.geometry as GeoJSONPolygon) : null,\n raw: fc\n };\n}\n\nexport class TownshipClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeout: number;\n\n constructor(options: TownshipClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n if (!this.apiKey) {\n throw new TownshipError(\"apiKey is required\");\n }\n }\n\n // --- Internal ---\n\n private async request<T>(path: string, init?: RequestInit): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...init,\n headers: {\n \"X-API-Key\": this.apiKey,\n \"User-Agent\": \"townshipamerica-js/1.0.0\",\n ...(init?.headers ?? {})\n },\n signal: controller.signal\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n return (await response.json()) as T;\n } catch (error) {\n if (error instanceof TownshipError) throw error;\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new TownshipError(\"Request timed out\");\n }\n throw new TownshipError(error instanceof Error ? error.message : \"Unknown error\");\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async handleError(response: Response): Promise<never> {\n let message: string;\n try {\n const body = (await response.json()) as Record<string, unknown>;\n message = (body.error ?? body.message ?? response.statusText) as string;\n } catch {\n message = response.statusText;\n }\n\n const status = response.status;\n if (status === 400) throw new ValidationError(message);\n if (status === 401) throw new AuthenticationError(message);\n if (status === 404) throw new NotFoundError(message);\n if (status === 413) throw new PayloadTooLargeError(message);\n if (status === 429) throw new RateLimitError(message);\n throw new TownshipError(message, status);\n }\n\n // --- Search ---\n\n /**\n * Convert a PLSS legal land description to GPS coordinates.\n *\n * @param legalLocation - e.g. \"NW 25 24N 1E 6th Meridian\"\n * @returns A SearchResult with coordinates, boundary, and metadata.\n * @throws {ValidationError} If the location string is invalid.\n * @throws {NotFoundError} If no match is found.\n */\n async search(legalLocation: string): Promise<SearchResult> {\n const params = new URLSearchParams({ location: legalLocation });\n const data = await this.request<GeoJSONFeatureCollection | Record<string, never>>(\n `/search/legal-location?${params}`\n );\n\n if (!(\"features\" in data) || !data.features?.length) {\n throw new NotFoundError(`No results found for \"${legalLocation}\"`);\n }\n\n return toSearchResult(data as GeoJSONFeatureCollection);\n }\n\n /**\n * Find the PLSS legal land description at the given GPS coordinates.\n *\n * @param longitude - Longitude (x) coordinate.\n * @param latitude - Latitude (y) coordinate.\n * @param options - Optional unit filter.\n * @returns A SearchResult or array of SearchResults (when unit is 'all').\n */\n async reverse(\n longitude: number,\n latitude: number,\n options?: ReverseOptions\n ): Promise<SearchResult | SearchResult[]> {\n const params = new URLSearchParams({\n location: `${longitude},${latitude}`\n });\n if (options?.unit) {\n params.set(\"unit\", options.unit);\n }\n\n const data = await this.request<GeoJSONFeatureCollection | Record<string, never>>(\n `/search/coordinates?${params}`\n );\n\n if (!(\"features\" in data) || !data.features?.length) {\n throw new NotFoundError(`No results found for coordinates [${longitude}, ${latitude}]`);\n }\n\n const fc = data as GeoJSONFeatureCollection;\n\n if (options?.unit === \"all\") {\n const groups = new Map<string, typeof fc.features>();\n for (const feature of fc.features) {\n const key = feature.properties.legal_location;\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(feature);\n }\n\n return Array.from(groups.values()).map((features) => {\n return toSearchResult({ type: \"FeatureCollection\", features });\n });\n }\n\n return toSearchResult(fc);\n }\n\n // --- Autocomplete ---\n\n /**\n * Get autocomplete suggestions for a partial PLSS description.\n *\n * @param query - Partial search query (minimum 2 characters).\n * @param options - Optional limit (1–10, default 3) and proximity.\n * @returns Raw GeoJSON FeatureCollection with suggestion features.\n */\n async autocomplete(\n query: string,\n options?: AutocompleteOptions\n ): Promise<GeoJSONFeatureCollection> {\n const params = new URLSearchParams({ location: query });\n if (options?.limit != null) {\n params.set(\"limit\", String(options.limit));\n }\n if (options?.proximity) {\n params.set(\"proximity\", `${options.proximity[0]},${options.proximity[1]}`);\n }\n\n return this.request<GeoJSONFeatureCollection>(`/autocomplete/legal-location?${params}`);\n }\n\n // --- Batch ---\n\n /**\n * Convert multiple PLSS descriptions to GPS coordinates in one request.\n * Auto-chunks if more than 100 items.\n *\n * @param locations - Array of PLSS legal land descriptions.\n * @param options - Optional chunk size.\n * @returns Array of SearchResult or null for each input.\n */\n async batchSearch(\n locations: string[],\n options?: BatchSearchOptions\n ): Promise<(SearchResult | null)[]> {\n const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);\n const results: (SearchResult | null)[] = [];\n\n for (let i = 0; i < locations.length; i += chunkSize) {\n const chunk = locations.slice(i, i + chunkSize);\n const data = await this.request<(GeoJSONFeatureCollection | null)[]>(\n \"/batch/legal-location\",\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(chunk)\n }\n );\n\n for (const fc of data) {\n if (!fc || !fc.features?.length) {\n results.push(null);\n } else {\n results.push(toSearchResult(fc));\n }\n }\n }\n\n return results;\n }\n\n /**\n * Find PLSS descriptions for multiple coordinate pairs in one request.\n * Auto-chunks if more than 100 items.\n *\n * @param coordinates - Array of [longitude, latitude] pairs.\n * @param options - Optional unit filter and chunk size.\n * @returns Array of SearchResult or null for each input.\n */\n async batchReverse(\n coordinates: [number, number][],\n options?: BatchReverseOptions\n ): Promise<(SearchResult | null)[]> {\n const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);\n const results: (SearchResult | null)[] = [];\n\n for (let i = 0; i < coordinates.length; i += chunkSize) {\n const chunk = coordinates.slice(i, i + chunkSize);\n const body: Record<string, unknown> = { coordinates: chunk };\n if (options?.unit) {\n body.unit = options.unit;\n }\n\n const data = await this.request<\n (GeoJSONFeatureCollection | null)[] | GeoJSONFeatureCollection\n >(\"/batch/coordinates\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body)\n });\n\n if (Array.isArray(data)) {\n for (const fc of data) {\n if (!fc || !fc.features?.length) {\n results.push(null);\n } else {\n results.push(toSearchResult(fc));\n }\n }\n } else if (data && \"features\" in data) {\n // unit=all returns a single FeatureCollection — split by legal_location\n const groups = new Map<string, typeof data.features>();\n for (const feature of data.features) {\n const key = JSON.stringify(feature.properties.search_term);\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(feature);\n }\n for (const features of groups.values()) {\n results.push(toSearchResult({ type: \"FeatureCollection\", features }));\n }\n }\n }\n\n return results;\n }\n\n // --- Convenience ---\n\n /**\n * Get the boundary polygon for a legal land description.\n *\n * @param legalLocation - e.g. \"NW 25 24N 1E 6th Meridian\"\n * @returns The boundary GeoJSON Polygon, or null if not found.\n */\n async boundary(legalLocation: string): Promise<GeoJSONPolygon | null> {\n const result = await this.search(legalLocation);\n return result.boundary;\n }\n\n /**\n * Get the raw GeoJSON FeatureCollection for a legal land description.\n *\n * @param legalLocation - e.g. \"NW 25 24N 1E 6th Meridian\"\n * @returns The raw GeoJSON FeatureCollection from the API.\n */\n async raw(legalLocation: string): Promise<GeoJSONFeatureCollection> {\n const params = new URLSearchParams({ location: legalLocation });\n const data = await this.request<GeoJSONFeatureCollection | Record<string, never>>(\n `/search/legal-location?${params}`\n );\n\n if (!(\"features\" in data)) {\n return { type: \"FeatureCollection\", features: [] };\n }\n\n return data as GeoJSONFeatureCollection;\n }\n}\n"],"mappings":"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","names":[]}
|