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/dist/index.d.cts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
interface TownshipClientOptions {
|
|
2
|
+
/** Township America API key. */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Override the default API base URL. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Request timeout in milliseconds (default: 30000). */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
interface GeoJSONPoint {
|
|
10
|
+
type: "Point";
|
|
11
|
+
coordinates: [longitude: number, latitude: number];
|
|
12
|
+
}
|
|
13
|
+
interface GeoJSONPolygon {
|
|
14
|
+
type: "Polygon";
|
|
15
|
+
coordinates: number[][][];
|
|
16
|
+
}
|
|
17
|
+
type Unit = "Township" | "First Division" | "Second Division";
|
|
18
|
+
interface FeatureProperties {
|
|
19
|
+
shape: "grid" | "centroid";
|
|
20
|
+
search_term: string | number[];
|
|
21
|
+
legal_location: string;
|
|
22
|
+
alternate_legal_location?: string;
|
|
23
|
+
unit?: Unit;
|
|
24
|
+
survey_system?: string;
|
|
25
|
+
county?: string;
|
|
26
|
+
state?: string;
|
|
27
|
+
}
|
|
28
|
+
interface GeoJSONFeature<G = GeoJSONPoint | GeoJSONPolygon> {
|
|
29
|
+
type: "Feature";
|
|
30
|
+
geometry: G;
|
|
31
|
+
properties: FeatureProperties;
|
|
32
|
+
}
|
|
33
|
+
interface GeoJSONFeatureCollection {
|
|
34
|
+
type: "FeatureCollection";
|
|
35
|
+
features: GeoJSONFeature[];
|
|
36
|
+
}
|
|
37
|
+
interface SearchResult {
|
|
38
|
+
/** The normalized legal land description. */
|
|
39
|
+
legalLocation: string;
|
|
40
|
+
/** Latitude of the centroid. */
|
|
41
|
+
latitude: number;
|
|
42
|
+
/** Longitude of the centroid. */
|
|
43
|
+
longitude: number;
|
|
44
|
+
/** US state where the land unit is located. */
|
|
45
|
+
state: string | undefined;
|
|
46
|
+
/** County where the land unit is located. */
|
|
47
|
+
county: string | undefined;
|
|
48
|
+
/** The land unit type (Township, First Division, Second Division). */
|
|
49
|
+
unit: string | undefined;
|
|
50
|
+
/** The survey system (PLSS). */
|
|
51
|
+
surveySystem: string | undefined;
|
|
52
|
+
/** Alternate legal land description if available. */
|
|
53
|
+
alternateLegalLocation: string | undefined;
|
|
54
|
+
/** Grid boundary polygon, or null if no boundary returned. */
|
|
55
|
+
boundary: GeoJSONPolygon | null;
|
|
56
|
+
/** The raw GeoJSON FeatureCollection from the API. */
|
|
57
|
+
raw: GeoJSONFeatureCollection;
|
|
58
|
+
}
|
|
59
|
+
interface ReverseOptions {
|
|
60
|
+
/** Precision level: 'Township', 'First Division', 'Second Division', or 'all'. */
|
|
61
|
+
unit?: Unit | "all";
|
|
62
|
+
}
|
|
63
|
+
interface AutocompleteOptions {
|
|
64
|
+
/** Maximum number of suggestions (1–10, default 3). */
|
|
65
|
+
limit?: number;
|
|
66
|
+
/** [longitude, latitude] to bias results toward. */
|
|
67
|
+
proximity?: [number, number];
|
|
68
|
+
}
|
|
69
|
+
interface BatchSearchOptions {
|
|
70
|
+
/** Max items per HTTP request (default 100, max 100). */
|
|
71
|
+
chunkSize?: number;
|
|
72
|
+
}
|
|
73
|
+
interface BatchReverseOptions {
|
|
74
|
+
/** Precision level: 'Township', 'First Division', 'Second Division', or 'all'. */
|
|
75
|
+
unit?: Unit | "all";
|
|
76
|
+
/** Max items per HTTP request (default 100, max 100). */
|
|
77
|
+
chunkSize?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare class TownshipClient {
|
|
81
|
+
private readonly baseUrl;
|
|
82
|
+
private readonly apiKey;
|
|
83
|
+
private readonly timeout;
|
|
84
|
+
constructor(options: TownshipClientOptions);
|
|
85
|
+
private request;
|
|
86
|
+
private handleError;
|
|
87
|
+
/**
|
|
88
|
+
* Convert a PLSS legal land description to GPS coordinates.
|
|
89
|
+
*
|
|
90
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
91
|
+
* @returns A SearchResult with coordinates, boundary, and metadata.
|
|
92
|
+
* @throws {ValidationError} If the location string is invalid.
|
|
93
|
+
* @throws {NotFoundError} If no match is found.
|
|
94
|
+
*/
|
|
95
|
+
search(legalLocation: string): Promise<SearchResult>;
|
|
96
|
+
/**
|
|
97
|
+
* Find the PLSS legal land description at the given GPS coordinates.
|
|
98
|
+
*
|
|
99
|
+
* @param longitude - Longitude (x) coordinate.
|
|
100
|
+
* @param latitude - Latitude (y) coordinate.
|
|
101
|
+
* @param options - Optional unit filter.
|
|
102
|
+
* @returns A SearchResult or array of SearchResults (when unit is 'all').
|
|
103
|
+
*/
|
|
104
|
+
reverse(longitude: number, latitude: number, options?: ReverseOptions): Promise<SearchResult | SearchResult[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Get autocomplete suggestions for a partial PLSS description.
|
|
107
|
+
*
|
|
108
|
+
* @param query - Partial search query (minimum 2 characters).
|
|
109
|
+
* @param options - Optional limit (1–10, default 3) and proximity.
|
|
110
|
+
* @returns Raw GeoJSON FeatureCollection with suggestion features.
|
|
111
|
+
*/
|
|
112
|
+
autocomplete(query: string, options?: AutocompleteOptions): Promise<GeoJSONFeatureCollection>;
|
|
113
|
+
/**
|
|
114
|
+
* Convert multiple PLSS descriptions to GPS coordinates in one request.
|
|
115
|
+
* Auto-chunks if more than 100 items.
|
|
116
|
+
*
|
|
117
|
+
* @param locations - Array of PLSS legal land descriptions.
|
|
118
|
+
* @param options - Optional chunk size.
|
|
119
|
+
* @returns Array of SearchResult or null for each input.
|
|
120
|
+
*/
|
|
121
|
+
batchSearch(locations: string[], options?: BatchSearchOptions): Promise<(SearchResult | null)[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Find PLSS descriptions for multiple coordinate pairs in one request.
|
|
124
|
+
* Auto-chunks if more than 100 items.
|
|
125
|
+
*
|
|
126
|
+
* @param coordinates - Array of [longitude, latitude] pairs.
|
|
127
|
+
* @param options - Optional unit filter and chunk size.
|
|
128
|
+
* @returns Array of SearchResult or null for each input.
|
|
129
|
+
*/
|
|
130
|
+
batchReverse(coordinates: [number, number][], options?: BatchReverseOptions): Promise<(SearchResult | null)[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Get the boundary polygon for a legal land description.
|
|
133
|
+
*
|
|
134
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
135
|
+
* @returns The boundary GeoJSON Polygon, or null if not found.
|
|
136
|
+
*/
|
|
137
|
+
boundary(legalLocation: string): Promise<GeoJSONPolygon | null>;
|
|
138
|
+
/**
|
|
139
|
+
* Get the raw GeoJSON FeatureCollection for a legal land description.
|
|
140
|
+
*
|
|
141
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
142
|
+
* @returns The raw GeoJSON FeatureCollection from the API.
|
|
143
|
+
*/
|
|
144
|
+
raw(legalLocation: string): Promise<GeoJSONFeatureCollection>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Base error class for all Township SDK errors.
|
|
149
|
+
*/
|
|
150
|
+
declare class TownshipError extends Error {
|
|
151
|
+
readonly statusCode: number | null;
|
|
152
|
+
constructor(message: string, statusCode?: number | null);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Thrown when the API key is missing or invalid (HTTP 401).
|
|
156
|
+
*/
|
|
157
|
+
declare class AuthenticationError extends TownshipError {
|
|
158
|
+
constructor(message?: string);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Thrown when no result is found for the given input.
|
|
162
|
+
*/
|
|
163
|
+
declare class NotFoundError extends TownshipError {
|
|
164
|
+
constructor(message?: string);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Thrown when the API rate limit is exceeded (HTTP 429).
|
|
168
|
+
*/
|
|
169
|
+
declare class RateLimitError extends TownshipError {
|
|
170
|
+
constructor(message?: string);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Thrown when the request is malformed (HTTP 400).
|
|
174
|
+
*/
|
|
175
|
+
declare class ValidationError extends TownshipError {
|
|
176
|
+
constructor(message?: string);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Thrown when the batch payload exceeds the maximum size (HTTP 413).
|
|
180
|
+
*/
|
|
181
|
+
declare class PayloadTooLargeError extends TownshipError {
|
|
182
|
+
constructor(message?: string);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { AuthenticationError, type AutocompleteOptions, type BatchReverseOptions, type BatchSearchOptions, type FeatureProperties, type GeoJSONFeature, type GeoJSONFeatureCollection, type GeoJSONPoint, type GeoJSONPolygon, NotFoundError, PayloadTooLargeError, RateLimitError, type ReverseOptions, type SearchResult, TownshipClient, type TownshipClientOptions, TownshipError, type Unit, ValidationError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
interface TownshipClientOptions {
|
|
2
|
+
/** Township America API key. */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Override the default API base URL. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Request timeout in milliseconds (default: 30000). */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
interface GeoJSONPoint {
|
|
10
|
+
type: "Point";
|
|
11
|
+
coordinates: [longitude: number, latitude: number];
|
|
12
|
+
}
|
|
13
|
+
interface GeoJSONPolygon {
|
|
14
|
+
type: "Polygon";
|
|
15
|
+
coordinates: number[][][];
|
|
16
|
+
}
|
|
17
|
+
type Unit = "Township" | "First Division" | "Second Division";
|
|
18
|
+
interface FeatureProperties {
|
|
19
|
+
shape: "grid" | "centroid";
|
|
20
|
+
search_term: string | number[];
|
|
21
|
+
legal_location: string;
|
|
22
|
+
alternate_legal_location?: string;
|
|
23
|
+
unit?: Unit;
|
|
24
|
+
survey_system?: string;
|
|
25
|
+
county?: string;
|
|
26
|
+
state?: string;
|
|
27
|
+
}
|
|
28
|
+
interface GeoJSONFeature<G = GeoJSONPoint | GeoJSONPolygon> {
|
|
29
|
+
type: "Feature";
|
|
30
|
+
geometry: G;
|
|
31
|
+
properties: FeatureProperties;
|
|
32
|
+
}
|
|
33
|
+
interface GeoJSONFeatureCollection {
|
|
34
|
+
type: "FeatureCollection";
|
|
35
|
+
features: GeoJSONFeature[];
|
|
36
|
+
}
|
|
37
|
+
interface SearchResult {
|
|
38
|
+
/** The normalized legal land description. */
|
|
39
|
+
legalLocation: string;
|
|
40
|
+
/** Latitude of the centroid. */
|
|
41
|
+
latitude: number;
|
|
42
|
+
/** Longitude of the centroid. */
|
|
43
|
+
longitude: number;
|
|
44
|
+
/** US state where the land unit is located. */
|
|
45
|
+
state: string | undefined;
|
|
46
|
+
/** County where the land unit is located. */
|
|
47
|
+
county: string | undefined;
|
|
48
|
+
/** The land unit type (Township, First Division, Second Division). */
|
|
49
|
+
unit: string | undefined;
|
|
50
|
+
/** The survey system (PLSS). */
|
|
51
|
+
surveySystem: string | undefined;
|
|
52
|
+
/** Alternate legal land description if available. */
|
|
53
|
+
alternateLegalLocation: string | undefined;
|
|
54
|
+
/** Grid boundary polygon, or null if no boundary returned. */
|
|
55
|
+
boundary: GeoJSONPolygon | null;
|
|
56
|
+
/** The raw GeoJSON FeatureCollection from the API. */
|
|
57
|
+
raw: GeoJSONFeatureCollection;
|
|
58
|
+
}
|
|
59
|
+
interface ReverseOptions {
|
|
60
|
+
/** Precision level: 'Township', 'First Division', 'Second Division', or 'all'. */
|
|
61
|
+
unit?: Unit | "all";
|
|
62
|
+
}
|
|
63
|
+
interface AutocompleteOptions {
|
|
64
|
+
/** Maximum number of suggestions (1–10, default 3). */
|
|
65
|
+
limit?: number;
|
|
66
|
+
/** [longitude, latitude] to bias results toward. */
|
|
67
|
+
proximity?: [number, number];
|
|
68
|
+
}
|
|
69
|
+
interface BatchSearchOptions {
|
|
70
|
+
/** Max items per HTTP request (default 100, max 100). */
|
|
71
|
+
chunkSize?: number;
|
|
72
|
+
}
|
|
73
|
+
interface BatchReverseOptions {
|
|
74
|
+
/** Precision level: 'Township', 'First Division', 'Second Division', or 'all'. */
|
|
75
|
+
unit?: Unit | "all";
|
|
76
|
+
/** Max items per HTTP request (default 100, max 100). */
|
|
77
|
+
chunkSize?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare class TownshipClient {
|
|
81
|
+
private readonly baseUrl;
|
|
82
|
+
private readonly apiKey;
|
|
83
|
+
private readonly timeout;
|
|
84
|
+
constructor(options: TownshipClientOptions);
|
|
85
|
+
private request;
|
|
86
|
+
private handleError;
|
|
87
|
+
/**
|
|
88
|
+
* Convert a PLSS legal land description to GPS coordinates.
|
|
89
|
+
*
|
|
90
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
91
|
+
* @returns A SearchResult with coordinates, boundary, and metadata.
|
|
92
|
+
* @throws {ValidationError} If the location string is invalid.
|
|
93
|
+
* @throws {NotFoundError} If no match is found.
|
|
94
|
+
*/
|
|
95
|
+
search(legalLocation: string): Promise<SearchResult>;
|
|
96
|
+
/**
|
|
97
|
+
* Find the PLSS legal land description at the given GPS coordinates.
|
|
98
|
+
*
|
|
99
|
+
* @param longitude - Longitude (x) coordinate.
|
|
100
|
+
* @param latitude - Latitude (y) coordinate.
|
|
101
|
+
* @param options - Optional unit filter.
|
|
102
|
+
* @returns A SearchResult or array of SearchResults (when unit is 'all').
|
|
103
|
+
*/
|
|
104
|
+
reverse(longitude: number, latitude: number, options?: ReverseOptions): Promise<SearchResult | SearchResult[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Get autocomplete suggestions for a partial PLSS description.
|
|
107
|
+
*
|
|
108
|
+
* @param query - Partial search query (minimum 2 characters).
|
|
109
|
+
* @param options - Optional limit (1–10, default 3) and proximity.
|
|
110
|
+
* @returns Raw GeoJSON FeatureCollection with suggestion features.
|
|
111
|
+
*/
|
|
112
|
+
autocomplete(query: string, options?: AutocompleteOptions): Promise<GeoJSONFeatureCollection>;
|
|
113
|
+
/**
|
|
114
|
+
* Convert multiple PLSS descriptions to GPS coordinates in one request.
|
|
115
|
+
* Auto-chunks if more than 100 items.
|
|
116
|
+
*
|
|
117
|
+
* @param locations - Array of PLSS legal land descriptions.
|
|
118
|
+
* @param options - Optional chunk size.
|
|
119
|
+
* @returns Array of SearchResult or null for each input.
|
|
120
|
+
*/
|
|
121
|
+
batchSearch(locations: string[], options?: BatchSearchOptions): Promise<(SearchResult | null)[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Find PLSS descriptions for multiple coordinate pairs in one request.
|
|
124
|
+
* Auto-chunks if more than 100 items.
|
|
125
|
+
*
|
|
126
|
+
* @param coordinates - Array of [longitude, latitude] pairs.
|
|
127
|
+
* @param options - Optional unit filter and chunk size.
|
|
128
|
+
* @returns Array of SearchResult or null for each input.
|
|
129
|
+
*/
|
|
130
|
+
batchReverse(coordinates: [number, number][], options?: BatchReverseOptions): Promise<(SearchResult | null)[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Get the boundary polygon for a legal land description.
|
|
133
|
+
*
|
|
134
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
135
|
+
* @returns The boundary GeoJSON Polygon, or null if not found.
|
|
136
|
+
*/
|
|
137
|
+
boundary(legalLocation: string): Promise<GeoJSONPolygon | null>;
|
|
138
|
+
/**
|
|
139
|
+
* Get the raw GeoJSON FeatureCollection for a legal land description.
|
|
140
|
+
*
|
|
141
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
142
|
+
* @returns The raw GeoJSON FeatureCollection from the API.
|
|
143
|
+
*/
|
|
144
|
+
raw(legalLocation: string): Promise<GeoJSONFeatureCollection>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Base error class for all Township SDK errors.
|
|
149
|
+
*/
|
|
150
|
+
declare class TownshipError extends Error {
|
|
151
|
+
readonly statusCode: number | null;
|
|
152
|
+
constructor(message: string, statusCode?: number | null);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Thrown when the API key is missing or invalid (HTTP 401).
|
|
156
|
+
*/
|
|
157
|
+
declare class AuthenticationError extends TownshipError {
|
|
158
|
+
constructor(message?: string);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Thrown when no result is found for the given input.
|
|
162
|
+
*/
|
|
163
|
+
declare class NotFoundError extends TownshipError {
|
|
164
|
+
constructor(message?: string);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Thrown when the API rate limit is exceeded (HTTP 429).
|
|
168
|
+
*/
|
|
169
|
+
declare class RateLimitError extends TownshipError {
|
|
170
|
+
constructor(message?: string);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Thrown when the request is malformed (HTTP 400).
|
|
174
|
+
*/
|
|
175
|
+
declare class ValidationError extends TownshipError {
|
|
176
|
+
constructor(message?: string);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Thrown when the batch payload exceeds the maximum size (HTTP 413).
|
|
180
|
+
*/
|
|
181
|
+
declare class PayloadTooLargeError extends TownshipError {
|
|
182
|
+
constructor(message?: string);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { AuthenticationError, type AutocompleteOptions, type BatchReverseOptions, type BatchSearchOptions, type FeatureProperties, type GeoJSONFeature, type GeoJSONFeatureCollection, type GeoJSONPoint, type GeoJSONPolygon, NotFoundError, PayloadTooLargeError, RateLimitError, type ReverseOptions, type SearchResult, TownshipClient, type TownshipClientOptions, TownshipError, type Unit, ValidationError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TownshipError = class extends Error {
|
|
3
|
+
constructor(message, statusCode = null) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.statusCode = statusCode;
|
|
6
|
+
this.name = "TownshipError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var AuthenticationError = class extends TownshipError {
|
|
10
|
+
constructor(message = "Invalid or missing API key") {
|
|
11
|
+
super(message, 401);
|
|
12
|
+
this.name = "AuthenticationError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var NotFoundError = class extends TownshipError {
|
|
16
|
+
constructor(message = "Location not found") {
|
|
17
|
+
super(message, 404);
|
|
18
|
+
this.name = "NotFoundError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var RateLimitError = class extends TownshipError {
|
|
22
|
+
constructor(message = "Rate limit exceeded. Please slow down your requests.") {
|
|
23
|
+
super(message, 429);
|
|
24
|
+
this.name = "RateLimitError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var ValidationError = class extends TownshipError {
|
|
28
|
+
constructor(message = "Invalid request") {
|
|
29
|
+
super(message, 400);
|
|
30
|
+
this.name = "ValidationError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var PayloadTooLargeError = class extends TownshipError {
|
|
34
|
+
constructor(message = "Batch payload exceeds the maximum of 100 records per request") {
|
|
35
|
+
super(message, 413);
|
|
36
|
+
this.name = "PayloadTooLargeError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/client.ts
|
|
41
|
+
var DEFAULT_BASE_URL = "https://developer.townshipamerica.com";
|
|
42
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
43
|
+
var MAX_BATCH_SIZE = 100;
|
|
44
|
+
function toSearchResult(fc) {
|
|
45
|
+
const centroid = fc.features.find((f) => f.properties.shape === "centroid");
|
|
46
|
+
const grid = fc.features.find((f) => f.properties.shape === "grid");
|
|
47
|
+
const props = centroid?.properties ?? grid?.properties;
|
|
48
|
+
if (!props) {
|
|
49
|
+
throw new TownshipError("Unexpected API response: no features returned");
|
|
50
|
+
}
|
|
51
|
+
const [lng, lat] = centroid ? centroid.geometry.coordinates : [0, 0];
|
|
52
|
+
return {
|
|
53
|
+
legalLocation: props.legal_location,
|
|
54
|
+
latitude: lat,
|
|
55
|
+
longitude: lng,
|
|
56
|
+
state: props.state,
|
|
57
|
+
county: props.county,
|
|
58
|
+
unit: props.unit,
|
|
59
|
+
surveySystem: props.survey_system,
|
|
60
|
+
alternateLegalLocation: props.alternate_legal_location,
|
|
61
|
+
boundary: grid ? grid.geometry : null,
|
|
62
|
+
raw: fc
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
var TownshipClient = class {
|
|
66
|
+
baseUrl;
|
|
67
|
+
apiKey;
|
|
68
|
+
timeout;
|
|
69
|
+
constructor(options) {
|
|
70
|
+
this.apiKey = options.apiKey;
|
|
71
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
72
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
73
|
+
if (!this.apiKey) {
|
|
74
|
+
throw new TownshipError("apiKey is required");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// --- Internal ---
|
|
78
|
+
async request(path, init) {
|
|
79
|
+
const url = `${this.baseUrl}${path}`;
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
...init,
|
|
85
|
+
headers: {
|
|
86
|
+
"X-API-Key": this.apiKey,
|
|
87
|
+
"User-Agent": "townshipamerica-js/1.0.0",
|
|
88
|
+
...init?.headers ?? {}
|
|
89
|
+
},
|
|
90
|
+
signal: controller.signal
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
await this.handleError(response);
|
|
94
|
+
}
|
|
95
|
+
return await response.json();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error instanceof TownshipError) throw error;
|
|
98
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
99
|
+
throw new TownshipError("Request timed out");
|
|
100
|
+
}
|
|
101
|
+
throw new TownshipError(error instanceof Error ? error.message : "Unknown error");
|
|
102
|
+
} finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async handleError(response) {
|
|
107
|
+
let message;
|
|
108
|
+
try {
|
|
109
|
+
const body = await response.json();
|
|
110
|
+
message = body.error ?? body.message ?? response.statusText;
|
|
111
|
+
} catch {
|
|
112
|
+
message = response.statusText;
|
|
113
|
+
}
|
|
114
|
+
const status = response.status;
|
|
115
|
+
if (status === 400) throw new ValidationError(message);
|
|
116
|
+
if (status === 401) throw new AuthenticationError(message);
|
|
117
|
+
if (status === 404) throw new NotFoundError(message);
|
|
118
|
+
if (status === 413) throw new PayloadTooLargeError(message);
|
|
119
|
+
if (status === 429) throw new RateLimitError(message);
|
|
120
|
+
throw new TownshipError(message, status);
|
|
121
|
+
}
|
|
122
|
+
// --- Search ---
|
|
123
|
+
/**
|
|
124
|
+
* Convert a PLSS legal land description to GPS coordinates.
|
|
125
|
+
*
|
|
126
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
127
|
+
* @returns A SearchResult with coordinates, boundary, and metadata.
|
|
128
|
+
* @throws {ValidationError} If the location string is invalid.
|
|
129
|
+
* @throws {NotFoundError} If no match is found.
|
|
130
|
+
*/
|
|
131
|
+
async search(legalLocation) {
|
|
132
|
+
const params = new URLSearchParams({ location: legalLocation });
|
|
133
|
+
const data = await this.request(
|
|
134
|
+
`/search/legal-location?${params}`
|
|
135
|
+
);
|
|
136
|
+
if (!("features" in data) || !data.features?.length) {
|
|
137
|
+
throw new NotFoundError(`No results found for "${legalLocation}"`);
|
|
138
|
+
}
|
|
139
|
+
return toSearchResult(data);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Find the PLSS legal land description at the given GPS coordinates.
|
|
143
|
+
*
|
|
144
|
+
* @param longitude - Longitude (x) coordinate.
|
|
145
|
+
* @param latitude - Latitude (y) coordinate.
|
|
146
|
+
* @param options - Optional unit filter.
|
|
147
|
+
* @returns A SearchResult or array of SearchResults (when unit is 'all').
|
|
148
|
+
*/
|
|
149
|
+
async reverse(longitude, latitude, options) {
|
|
150
|
+
const params = new URLSearchParams({
|
|
151
|
+
location: `${longitude},${latitude}`
|
|
152
|
+
});
|
|
153
|
+
if (options?.unit) {
|
|
154
|
+
params.set("unit", options.unit);
|
|
155
|
+
}
|
|
156
|
+
const data = await this.request(
|
|
157
|
+
`/search/coordinates?${params}`
|
|
158
|
+
);
|
|
159
|
+
if (!("features" in data) || !data.features?.length) {
|
|
160
|
+
throw new NotFoundError(`No results found for coordinates [${longitude}, ${latitude}]`);
|
|
161
|
+
}
|
|
162
|
+
const fc = data;
|
|
163
|
+
if (options?.unit === "all") {
|
|
164
|
+
const groups = /* @__PURE__ */ new Map();
|
|
165
|
+
for (const feature of fc.features) {
|
|
166
|
+
const key = feature.properties.legal_location;
|
|
167
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
168
|
+
groups.get(key).push(feature);
|
|
169
|
+
}
|
|
170
|
+
return Array.from(groups.values()).map((features) => {
|
|
171
|
+
return toSearchResult({ type: "FeatureCollection", features });
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return toSearchResult(fc);
|
|
175
|
+
}
|
|
176
|
+
// --- Autocomplete ---
|
|
177
|
+
/**
|
|
178
|
+
* Get autocomplete suggestions for a partial PLSS description.
|
|
179
|
+
*
|
|
180
|
+
* @param query - Partial search query (minimum 2 characters).
|
|
181
|
+
* @param options - Optional limit (1–10, default 3) and proximity.
|
|
182
|
+
* @returns Raw GeoJSON FeatureCollection with suggestion features.
|
|
183
|
+
*/
|
|
184
|
+
async autocomplete(query, options) {
|
|
185
|
+
const params = new URLSearchParams({ location: query });
|
|
186
|
+
if (options?.limit != null) {
|
|
187
|
+
params.set("limit", String(options.limit));
|
|
188
|
+
}
|
|
189
|
+
if (options?.proximity) {
|
|
190
|
+
params.set("proximity", `${options.proximity[0]},${options.proximity[1]}`);
|
|
191
|
+
}
|
|
192
|
+
return this.request(`/autocomplete/legal-location?${params}`);
|
|
193
|
+
}
|
|
194
|
+
// --- Batch ---
|
|
195
|
+
/**
|
|
196
|
+
* Convert multiple PLSS descriptions to GPS coordinates in one request.
|
|
197
|
+
* Auto-chunks if more than 100 items.
|
|
198
|
+
*
|
|
199
|
+
* @param locations - Array of PLSS legal land descriptions.
|
|
200
|
+
* @param options - Optional chunk size.
|
|
201
|
+
* @returns Array of SearchResult or null for each input.
|
|
202
|
+
*/
|
|
203
|
+
async batchSearch(locations, options) {
|
|
204
|
+
const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
|
|
205
|
+
const results = [];
|
|
206
|
+
for (let i = 0; i < locations.length; i += chunkSize) {
|
|
207
|
+
const chunk = locations.slice(i, i + chunkSize);
|
|
208
|
+
const data = await this.request(
|
|
209
|
+
"/batch/legal-location",
|
|
210
|
+
{
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify(chunk)
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
for (const fc of data) {
|
|
217
|
+
if (!fc || !fc.features?.length) {
|
|
218
|
+
results.push(null);
|
|
219
|
+
} else {
|
|
220
|
+
results.push(toSearchResult(fc));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Find PLSS descriptions for multiple coordinate pairs in one request.
|
|
228
|
+
* Auto-chunks if more than 100 items.
|
|
229
|
+
*
|
|
230
|
+
* @param coordinates - Array of [longitude, latitude] pairs.
|
|
231
|
+
* @param options - Optional unit filter and chunk size.
|
|
232
|
+
* @returns Array of SearchResult or null for each input.
|
|
233
|
+
*/
|
|
234
|
+
async batchReverse(coordinates, options) {
|
|
235
|
+
const chunkSize = Math.min(options?.chunkSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
|
|
236
|
+
const results = [];
|
|
237
|
+
for (let i = 0; i < coordinates.length; i += chunkSize) {
|
|
238
|
+
const chunk = coordinates.slice(i, i + chunkSize);
|
|
239
|
+
const body = { coordinates: chunk };
|
|
240
|
+
if (options?.unit) {
|
|
241
|
+
body.unit = options.unit;
|
|
242
|
+
}
|
|
243
|
+
const data = await this.request("/batch/coordinates", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: { "Content-Type": "application/json" },
|
|
246
|
+
body: JSON.stringify(body)
|
|
247
|
+
});
|
|
248
|
+
if (Array.isArray(data)) {
|
|
249
|
+
for (const fc of data) {
|
|
250
|
+
if (!fc || !fc.features?.length) {
|
|
251
|
+
results.push(null);
|
|
252
|
+
} else {
|
|
253
|
+
results.push(toSearchResult(fc));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} else if (data && "features" in data) {
|
|
257
|
+
const groups = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const feature of data.features) {
|
|
259
|
+
const key = JSON.stringify(feature.properties.search_term);
|
|
260
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
261
|
+
groups.get(key).push(feature);
|
|
262
|
+
}
|
|
263
|
+
for (const features of groups.values()) {
|
|
264
|
+
results.push(toSearchResult({ type: "FeatureCollection", features }));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
// --- Convenience ---
|
|
271
|
+
/**
|
|
272
|
+
* Get the boundary polygon for a legal land description.
|
|
273
|
+
*
|
|
274
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
275
|
+
* @returns The boundary GeoJSON Polygon, or null if not found.
|
|
276
|
+
*/
|
|
277
|
+
async boundary(legalLocation) {
|
|
278
|
+
const result = await this.search(legalLocation);
|
|
279
|
+
return result.boundary;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get the raw GeoJSON FeatureCollection for a legal land description.
|
|
283
|
+
*
|
|
284
|
+
* @param legalLocation - e.g. "NW 25 24N 1E 6th Meridian"
|
|
285
|
+
* @returns The raw GeoJSON FeatureCollection from the API.
|
|
286
|
+
*/
|
|
287
|
+
async raw(legalLocation) {
|
|
288
|
+
const params = new URLSearchParams({ location: legalLocation });
|
|
289
|
+
const data = await this.request(
|
|
290
|
+
`/search/legal-location?${params}`
|
|
291
|
+
);
|
|
292
|
+
if (!("features" in data)) {
|
|
293
|
+
return { type: "FeatureCollection", features: [] };
|
|
294
|
+
}
|
|
295
|
+
return data;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
export {
|
|
299
|
+
AuthenticationError,
|
|
300
|
+
NotFoundError,
|
|
301
|
+
PayloadTooLargeError,
|
|
302
|
+
RateLimitError,
|
|
303
|
+
TownshipClient,
|
|
304
|
+
TownshipError,
|
|
305
|
+
ValidationError
|
|
306
|
+
};
|
|
307
|
+
//# sourceMappingURL=index.js.map
|