@happyvertical/geo 0.80.0 → 0.80.2
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/dist/chunks/google-CZUa1ho0.js +244 -0
- package/dist/chunks/google-CZUa1ho0.js.map +1 -0
- package/dist/chunks/openstreetmap-St4Kw-vS.js +314 -0
- package/dist/chunks/openstreetmap-St4Kw-vS.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +377 -261
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunks/google-Ci3_ec7t.js +0 -342
- package/dist/chunks/google-Ci3_ec7t.js.map +0 -1
- package/dist/chunks/openstreetmap-DEPHzMUV.js +0 -419
- package/dist/chunks/openstreetmap-DEPHzMUV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,290 +1,406 @@
|
|
|
1
1
|
import { loadEnvConfig } from "@happyvertical/utils";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
2
|
+
//#region src/shared/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Base error class for geo operations.
|
|
5
|
+
* All geo-specific errors extend this class and include a machine-readable `code`
|
|
6
|
+
* and the `provider` name that produced the error.
|
|
7
|
+
*/
|
|
8
|
+
var GeoError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
provider;
|
|
11
|
+
/**
|
|
12
|
+
* @param message - Human-readable error description
|
|
13
|
+
* @param code - Machine-readable error code (e.g. 'RATE_LIMIT', 'AUTH_ERROR')
|
|
14
|
+
* @param provider - Provider that raised the error ('google' | 'openstreetmap')
|
|
15
|
+
*/
|
|
16
|
+
constructor(message, code, provider) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.provider = provider;
|
|
20
|
+
this.name = "GeoError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when the provider's rate limit has been exceeded.
|
|
25
|
+
*/
|
|
26
|
+
var RateLimitError = class extends GeoError {
|
|
27
|
+
/**
|
|
28
|
+
* @param provider - Provider that raised the error
|
|
29
|
+
* @param retryAfter - Seconds to wait before retrying (if known)
|
|
30
|
+
*/
|
|
31
|
+
constructor(provider, retryAfter) {
|
|
32
|
+
super(`Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ""}`, "RATE_LIMIT", provider);
|
|
33
|
+
this.name = "RateLimitError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when the geocoding query is empty or malformed.
|
|
38
|
+
*/
|
|
39
|
+
var InvalidQueryError = class extends GeoError {
|
|
40
|
+
/**
|
|
41
|
+
* @param query - The invalid query string
|
|
42
|
+
* @param provider - Provider that raised the error
|
|
43
|
+
*/
|
|
44
|
+
constructor(query, provider) {
|
|
45
|
+
super(`Invalid query: ${query}`, "INVALID_QUERY", provider);
|
|
46
|
+
this.name = "InvalidQueryError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when the provider rejects the API key or credentials.
|
|
51
|
+
*/
|
|
52
|
+
var AuthenticationError = class extends GeoError {
|
|
53
|
+
/**
|
|
54
|
+
* @param provider - Provider that raised the error
|
|
55
|
+
*/
|
|
56
|
+
constructor(provider) {
|
|
57
|
+
super("Authentication failed", "AUTH_ERROR", provider);
|
|
58
|
+
this.name = "AuthenticationError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when a geocoding query returns zero results.
|
|
63
|
+
*/
|
|
64
|
+
var NoResultsError = class extends GeoError {
|
|
65
|
+
/**
|
|
66
|
+
* @param query - The query that produced no results
|
|
67
|
+
* @param provider - Provider that raised the error
|
|
68
|
+
*/
|
|
69
|
+
constructor(query, provider) {
|
|
70
|
+
super(`No results found for query: ${query}`, "NO_RESULTS", provider);
|
|
71
|
+
this.name = "NoResultsError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/shared/utils.ts
|
|
76
|
+
/**
|
|
77
|
+
* Map Google Maps place types to standardized location types.
|
|
78
|
+
* Checks types in priority order: address, city, region, country, point_of_interest.
|
|
79
|
+
*
|
|
80
|
+
* @param types - Array of Google Maps place type strings (e.g. 'street_address', 'locality')
|
|
81
|
+
* @returns The standardized location type
|
|
82
|
+
*/
|
|
58
83
|
function mapGooglePlaceType(types) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (types.includes("administrative_area_level_1") || types.includes("administrative_area_level_2")) {
|
|
67
|
-
return "region";
|
|
68
|
-
}
|
|
69
|
-
if (types.includes("country")) {
|
|
70
|
-
return "country";
|
|
71
|
-
}
|
|
72
|
-
if (types.includes("point_of_interest") || types.includes("establishment")) {
|
|
73
|
-
return "point_of_interest";
|
|
74
|
-
}
|
|
75
|
-
return "unknown";
|
|
84
|
+
if (!types || types.length === 0) return "unknown";
|
|
85
|
+
if (types.includes("street_address") || types.includes("premise")) return "address";
|
|
86
|
+
if (types.includes("locality") || types.includes("postal_town")) return "city";
|
|
87
|
+
if (types.includes("administrative_area_level_1") || types.includes("administrative_area_level_2")) return "region";
|
|
88
|
+
if (types.includes("country")) return "country";
|
|
89
|
+
if (types.includes("point_of_interest") || types.includes("establishment")) return "point_of_interest";
|
|
90
|
+
return "unknown";
|
|
76
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Map OpenStreetMap place types to standardized location types.
|
|
94
|
+
* Uses both the Nominatim `type` and `addresstype` fields for classification.
|
|
95
|
+
*
|
|
96
|
+
* @param type - Nominatim result type (e.g. 'house', 'city', 'state')
|
|
97
|
+
* @param addressType - Nominatim addresstype field (optional secondary classifier)
|
|
98
|
+
* @returns The standardized location type
|
|
99
|
+
*/
|
|
77
100
|
function mapOSMPlaceType(type, addressType) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (checkType === "state" || checkType === "province" || checkType === "region" || checkAddressType === "state") {
|
|
87
|
-
return "region";
|
|
88
|
-
}
|
|
89
|
-
if (checkType === "country" || checkAddressType === "country") {
|
|
90
|
-
return "country";
|
|
91
|
-
}
|
|
92
|
-
if (checkType === "attraction" || checkType === "tourism" || checkType === "amenity") {
|
|
93
|
-
return "point_of_interest";
|
|
94
|
-
}
|
|
95
|
-
return "unknown";
|
|
101
|
+
const checkType = (type || "").toLowerCase();
|
|
102
|
+
const checkAddressType = (addressType || "").toLowerCase();
|
|
103
|
+
if (checkType === "house" || checkType === "building" || checkAddressType === "house") return "address";
|
|
104
|
+
if (checkType === "city" || checkType === "town" || checkType === "village" || checkType === "hamlet" || checkAddressType === "city" || checkAddressType === "town" || checkAddressType === "village") return "city";
|
|
105
|
+
if (checkType === "state" || checkType === "province" || checkType === "region" || checkAddressType === "state") return "region";
|
|
106
|
+
if (checkType === "country" || checkAddressType === "country") return "country";
|
|
107
|
+
if (checkType === "attraction" || checkType === "tourism" || checkType === "amenity") return "point_of_interest";
|
|
108
|
+
return "unknown";
|
|
96
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Normalize a country code to uppercase ISO 3166-1 format.
|
|
112
|
+
* Returns 'XX' for undefined, empty, or unrecognized inputs.
|
|
113
|
+
*
|
|
114
|
+
* @param code - Country code string (alpha-2 or alpha-3)
|
|
115
|
+
* @returns Uppercase country code, or 'XX' if unknown
|
|
116
|
+
*/
|
|
97
117
|
function normalizeCountryCode(code) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return normalized.length === 3 ? normalized : "XX";
|
|
118
|
+
if (!code) return "XX";
|
|
119
|
+
const normalized = code.toUpperCase().trim();
|
|
120
|
+
if (normalized.length === 2) return normalized;
|
|
121
|
+
return normalized.length === 3 ? normalized : "XX";
|
|
104
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate that a latitude value is within the valid range (-90 to 90).
|
|
125
|
+
*
|
|
126
|
+
* @param lat - Latitude value to validate
|
|
127
|
+
* @returns `true` if the value is a number between -90 and 90 inclusive
|
|
128
|
+
*/
|
|
105
129
|
function isValidLatitude(lat) {
|
|
106
|
-
|
|
130
|
+
return typeof lat === "number" && lat >= -90 && lat <= 90;
|
|
107
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Validate that a longitude value is within the valid range (-180 to 180).
|
|
134
|
+
*
|
|
135
|
+
* @param lng - Longitude value to validate
|
|
136
|
+
* @returns `true` if the value is a number between -180 and 180 inclusive
|
|
137
|
+
*/
|
|
108
138
|
function isValidLongitude(lng) {
|
|
109
|
-
|
|
139
|
+
return typeof lng === "number" && lng >= -180 && lng <= 180;
|
|
110
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate that both latitude and longitude are within their valid ranges.
|
|
143
|
+
*
|
|
144
|
+
* @param latitude - Latitude value (-90 to 90)
|
|
145
|
+
* @param longitude - Longitude value (-180 to 180)
|
|
146
|
+
* @returns Object with `valid: true` if both are valid, or `valid: false` with an `error` message
|
|
147
|
+
*/
|
|
111
148
|
function validateCoordinates(latitude, longitude) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
error: `Invalid longitude: ${longitude}. Must be between -180 and 180.`
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return { valid: true };
|
|
149
|
+
if (!isValidLatitude(latitude)) return {
|
|
150
|
+
valid: false,
|
|
151
|
+
error: `Invalid latitude: ${latitude}. Must be between -90 and 90.`
|
|
152
|
+
};
|
|
153
|
+
if (!isValidLongitude(longitude)) return {
|
|
154
|
+
valid: false,
|
|
155
|
+
error: `Invalid longitude: ${longitude}. Must be between -180 and 180.`
|
|
156
|
+
};
|
|
157
|
+
return { valid: true };
|
|
125
158
|
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/static-maps.ts
|
|
161
|
+
/**
|
|
162
|
+
* Generate Mapbox static map URL
|
|
163
|
+
*/
|
|
126
164
|
function getMapboxUrl(latitude, longitude, options) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
overlay = `pin-l+${color}(${longitude},${latitude})/`;
|
|
143
|
-
}
|
|
144
|
-
if (options.markers?.length) {
|
|
145
|
-
const markerOverlays = options.markers.map((m) => {
|
|
146
|
-
const color = (m.color ?? "e74c3c").replace("#", "");
|
|
147
|
-
const size = m.size === "small" ? "s" : m.size === "large" ? "l" : "m";
|
|
148
|
-
const label = m.label ? `-${m.label}` : "";
|
|
149
|
-
return `pin-${size}${label}+${color}(${m.longitude},${m.latitude})`;
|
|
150
|
-
});
|
|
151
|
-
overlay = markerOverlays.join(",") + "/";
|
|
152
|
-
}
|
|
153
|
-
const retinaStr = scale === 2 ? "@2x" : "";
|
|
154
|
-
return `https://api.mapbox.com/styles/v1/mapbox/${style}/static/${overlay}${longitude},${latitude},${zoom}/${width}x${height}${retinaStr}?access_token=${token}`;
|
|
165
|
+
const token = options.mapboxToken ?? process.env.MAPBOX_ACCESS_TOKEN ?? process.env.MAPBOX_TOKEN;
|
|
166
|
+
if (!token) throw new Error("Mapbox access token required. Provide via options.mapboxToken or MAPBOX_ACCESS_TOKEN env var.");
|
|
167
|
+
const width = options.width ?? 1200;
|
|
168
|
+
const height = options.height ?? 630;
|
|
169
|
+
const zoom = options.zoom ?? 14;
|
|
170
|
+
const style = options.mapboxStyle ?? "streets-v12";
|
|
171
|
+
const scale = options.scale ?? 1;
|
|
172
|
+
const showMarker = options.showMarker ?? true;
|
|
173
|
+
let overlay = "";
|
|
174
|
+
if (showMarker) overlay = `pin-l+${(options.markerColor ?? "e74c3c").replace("#", "")}(${longitude},${latitude})/`;
|
|
175
|
+
if (options.markers?.length) overlay = options.markers.map((m) => {
|
|
176
|
+
const color = (m.color ?? "e74c3c").replace("#", "");
|
|
177
|
+
return `pin-${m.size === "small" ? "s" : m.size === "large" ? "l" : "m"}${m.label ? `-${m.label}` : ""}+${color}(${m.longitude},${m.latitude})`;
|
|
178
|
+
}).join(",") + "/";
|
|
179
|
+
return `https://api.mapbox.com/styles/v1/mapbox/${style}/static/${overlay}${longitude},${latitude},${zoom}/${width}x${height}${scale === 2 ? "@2x" : ""}?access_token=${token}`;
|
|
155
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Generate Google Maps static map URL
|
|
183
|
+
*/
|
|
156
184
|
function getGoogleUrl(latitude, longitude, options) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const size = m.size ?? "medium";
|
|
185
|
-
const label = m.label ? `|label:${m.label.charAt(0).toUpperCase()}` : "";
|
|
186
|
-
params.append(
|
|
187
|
-
"markers",
|
|
188
|
-
`color:${color}|size:${size}${label}|${m.latitude},${m.longitude}`
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return `https://maps.googleapis.com/maps/api/staticmap?${params.toString()}`;
|
|
185
|
+
const apiKey = options.googleApiKey ?? process.env.GOOGLE_MAPS_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
186
|
+
if (!apiKey) throw new Error("Google Maps API key required. Provide via options.googleApiKey or GOOGLE_MAPS_API_KEY env var.");
|
|
187
|
+
const width = options.width ?? 1200;
|
|
188
|
+
const height = options.height ?? 630;
|
|
189
|
+
const zoom = options.zoom ?? 14;
|
|
190
|
+
const mapType = options.googleMapType ?? "roadmap";
|
|
191
|
+
const scale = options.scale ?? 1;
|
|
192
|
+
const showMarker = options.showMarker ?? true;
|
|
193
|
+
const params = new URLSearchParams({
|
|
194
|
+
center: `${latitude},${longitude}`,
|
|
195
|
+
zoom: zoom.toString(),
|
|
196
|
+
size: `${width}x${height}`,
|
|
197
|
+
maptype: mapType,
|
|
198
|
+
scale: scale.toString(),
|
|
199
|
+
key: apiKey
|
|
200
|
+
});
|
|
201
|
+
if (showMarker) {
|
|
202
|
+
const color = (options.markerColor ?? "red").replace("#", "0x");
|
|
203
|
+
params.append("markers", `color:${color}|${latitude},${longitude}`);
|
|
204
|
+
}
|
|
205
|
+
if (options.markers?.length) for (const m of options.markers) {
|
|
206
|
+
const color = (m.color ?? "red").replace("#", "0x");
|
|
207
|
+
const size = m.size ?? "medium";
|
|
208
|
+
const label = m.label ? `|label:${m.label.charAt(0).toUpperCase()}` : "";
|
|
209
|
+
params.append("markers", `color:${color}|size:${size}${label}|${m.latitude},${m.longitude}`);
|
|
210
|
+
}
|
|
211
|
+
return `https://maps.googleapis.com/maps/api/staticmap?${params.toString()}`;
|
|
193
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Generate a static map URL
|
|
215
|
+
*
|
|
216
|
+
* Returns a URL that can be used directly in <img> tags or fetched for processing.
|
|
217
|
+
*
|
|
218
|
+
* @param latitude - Center latitude
|
|
219
|
+
* @param longitude - Center longitude
|
|
220
|
+
* @param options - Map options
|
|
221
|
+
* @returns URL string for the static map
|
|
222
|
+
*
|
|
223
|
+
* @example Mapbox (default)
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const url = getStaticMapUrl(53.5461, -113.4938, {
|
|
226
|
+
* provider: 'mapbox',
|
|
227
|
+
* zoom: 14,
|
|
228
|
+
* mapboxStyle: 'streets-v12'
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* @example Google Maps
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const url = getStaticMapUrl(53.5461, -113.4938, {
|
|
235
|
+
* provider: 'google',
|
|
236
|
+
* zoom: 14,
|
|
237
|
+
* googleMapType: 'roadmap'
|
|
238
|
+
* });
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* @example With custom markers
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const url = getStaticMapUrl(53.5461, -113.4938, {
|
|
244
|
+
* showMarker: false, // Hide center marker
|
|
245
|
+
* markers: [
|
|
246
|
+
* { latitude: 53.5461, longitude: -113.4938, color: 'blue', label: 'A' },
|
|
247
|
+
* { latitude: 53.5500, longitude: -113.4900, color: 'green', label: 'B' }
|
|
248
|
+
* ]
|
|
249
|
+
* });
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
194
252
|
function getStaticMapUrl(latitude, longitude, options = {}) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
case "mapbox":
|
|
200
|
-
default:
|
|
201
|
-
return getMapboxUrl(latitude, longitude, options);
|
|
202
|
-
}
|
|
253
|
+
switch (options.provider ?? "mapbox") {
|
|
254
|
+
case "google": return getGoogleUrl(latitude, longitude, options);
|
|
255
|
+
default: return getMapboxUrl(latitude, longitude, options);
|
|
256
|
+
}
|
|
203
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Fetch a static map image
|
|
260
|
+
*
|
|
261
|
+
* Downloads the map image and returns it as a Buffer.
|
|
262
|
+
*
|
|
263
|
+
* @param latitude - Center latitude
|
|
264
|
+
* @param longitude - Center longitude
|
|
265
|
+
* @param options - Map options
|
|
266
|
+
* @returns Promise resolving to the map image result
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* const result = await fetchStaticMap(53.5461, -113.4938, {
|
|
271
|
+
* provider: 'mapbox',
|
|
272
|
+
* width: 1200,
|
|
273
|
+
* height: 630
|
|
274
|
+
* });
|
|
275
|
+
*
|
|
276
|
+
* await fs.writeFile('map.png', result.buffer);
|
|
277
|
+
* console.log(`Fetched ${result.width}x${result.height} map`);
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
204
280
|
async function fetchStaticMap(latitude, longitude, options = {}) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
mimeType: "image/png",
|
|
222
|
-
url
|
|
223
|
-
};
|
|
281
|
+
const width = options.width ?? 1200;
|
|
282
|
+
const height = options.height ?? 630;
|
|
283
|
+
const url = getStaticMapUrl(latitude, longitude, options);
|
|
284
|
+
const response = await fetch(url);
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
const errorText = await response.text();
|
|
287
|
+
throw new Error(`Failed to fetch static map: ${response.status} ${response.statusText} - ${errorText}`);
|
|
288
|
+
}
|
|
289
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
290
|
+
return {
|
|
291
|
+
buffer: Buffer.from(arrayBuffer),
|
|
292
|
+
width,
|
|
293
|
+
height,
|
|
294
|
+
mimeType: "image/png",
|
|
295
|
+
url
|
|
296
|
+
};
|
|
224
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Generate OG-sized static map URL (1200x630)
|
|
300
|
+
*
|
|
301
|
+
* Convenience function for generating Open Graph / social media sized maps.
|
|
302
|
+
*
|
|
303
|
+
* @param latitude - Center latitude
|
|
304
|
+
* @param longitude - Center longitude
|
|
305
|
+
* @param options - Map options (width/height ignored, set to 1200x630)
|
|
306
|
+
* @returns URL string for the static map
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const url = getOGMapUrl(53.5461, -113.4938);
|
|
311
|
+
* // Returns 1200x630 map URL
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
225
314
|
function getOGMapUrl(latitude, longitude, options = {}) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
315
|
+
return getStaticMapUrl(latitude, longitude, {
|
|
316
|
+
...options,
|
|
317
|
+
width: 1200,
|
|
318
|
+
height: 630
|
|
319
|
+
});
|
|
231
320
|
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/index.ts
|
|
323
|
+
/**
|
|
324
|
+
* Geo package entry point
|
|
325
|
+
* Provides standardized geographical information interface
|
|
326
|
+
*/
|
|
327
|
+
/**
|
|
328
|
+
* Type guard for Google Maps options
|
|
329
|
+
*/
|
|
232
330
|
function isGoogleMapsOptions(options) {
|
|
233
|
-
|
|
331
|
+
return options.provider === "google";
|
|
234
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Type guard for OpenStreetMap options
|
|
335
|
+
*/
|
|
235
336
|
function isOpenStreetMapOptions(options) {
|
|
236
|
-
|
|
337
|
+
return options.provider === "openstreetmap";
|
|
237
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Factory function to create a geo adapter instance
|
|
341
|
+
*
|
|
342
|
+
* Supports configuration via environment variables using the pattern:
|
|
343
|
+
* - HAVE_GEO_PROVIDER → provider ('google' | 'openstreetmap')
|
|
344
|
+
* - GOOGLE_MAPS_API_KEY → apiKey (for Google Maps provider)
|
|
345
|
+
*
|
|
346
|
+
* User-provided options always take precedence over environment variables.
|
|
347
|
+
*
|
|
348
|
+
* @param options - Configuration options for the geo provider (optional)
|
|
349
|
+
* @returns Promise resolving to a geo adapter that implements GeoAdapter
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // Create Google Maps adapter with explicit options
|
|
354
|
+
* const googleGeo = await getGeoAdapter({
|
|
355
|
+
* provider: 'google',
|
|
356
|
+
* apiKey: process.env.GOOGLE_MAPS_API_KEY!
|
|
357
|
+
* });
|
|
358
|
+
*
|
|
359
|
+
* // Create adapter using environment variables
|
|
360
|
+
* // HAVE_GEO_PROVIDER=google
|
|
361
|
+
* // GOOGLE_MAPS_API_KEY=your-api-key
|
|
362
|
+
* const geoFromEnv = await getGeoAdapter();
|
|
363
|
+
*
|
|
364
|
+
* // Create OpenStreetMap adapter
|
|
365
|
+
* const osmGeo = await getGeoAdapter({
|
|
366
|
+
* provider: 'openstreetmap'
|
|
367
|
+
* });
|
|
368
|
+
*
|
|
369
|
+
* // Use the adapter
|
|
370
|
+
* const locations = await googleGeo.lookup('Eiffel Tower');
|
|
371
|
+
* const coords = await osmGeo.reverseGeocode(48.8584, 2.2945);
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
238
374
|
async function getGeoAdapter(options = {}) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
if (isOpenStreetMapOptions(fullOptions)) {
|
|
266
|
-
const { OpenStreetMapProvider } = await import("./chunks/openstreetmap-DEPHzMUV.js");
|
|
267
|
-
return new OpenStreetMapProvider(fullOptions);
|
|
268
|
-
}
|
|
269
|
-
throw new Error(`Unsupported provider: ${fullOptions.provider}`);
|
|
375
|
+
const config = loadEnvConfig(options, {
|
|
376
|
+
packageName: "geo",
|
|
377
|
+
schema: {
|
|
378
|
+
provider: "string",
|
|
379
|
+
timeout: "number",
|
|
380
|
+
maxResults: "number",
|
|
381
|
+
rateLimitDelay: "number",
|
|
382
|
+
userAgent: "string"
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
if (config.provider === "google" && !config.apiKey) {
|
|
386
|
+
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
|
|
387
|
+
if (apiKey) config.apiKey = apiKey;
|
|
388
|
+
}
|
|
389
|
+
if (!config.provider) throw new Error("Provider is required. Set via options.provider or HAVE_GEO_PROVIDER environment variable.");
|
|
390
|
+
const fullOptions = config;
|
|
391
|
+
if (isGoogleMapsOptions(fullOptions)) {
|
|
392
|
+
const { GoogleMapsProvider } = await import("./chunks/google-CZUa1ho0.js");
|
|
393
|
+
return new GoogleMapsProvider(fullOptions);
|
|
394
|
+
}
|
|
395
|
+
if (isOpenStreetMapOptions(fullOptions)) {
|
|
396
|
+
const { OpenStreetMapProvider } = await import("./chunks/openstreetmap-St4Kw-vS.js");
|
|
397
|
+
return new OpenStreetMapProvider(fullOptions);
|
|
398
|
+
}
|
|
399
|
+
throw new Error(`Unsupported provider: ${fullOptions.provider}`);
|
|
270
400
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
PACKAGE_VERSION_INITIALIZED,
|
|
278
|
-
RateLimitError,
|
|
279
|
-
fetchStaticMap,
|
|
280
|
-
getGeoAdapter,
|
|
281
|
-
getOGMapUrl,
|
|
282
|
-
getStaticMapUrl,
|
|
283
|
-
isValidLatitude,
|
|
284
|
-
isValidLongitude,
|
|
285
|
-
mapGooglePlaceType,
|
|
286
|
-
mapOSMPlaceType,
|
|
287
|
-
normalizeCountryCode,
|
|
288
|
-
validateCoordinates
|
|
289
|
-
};
|
|
290
|
-
//# sourceMappingURL=index.js.map
|
|
401
|
+
/** @internal */
|
|
402
|
+
var PACKAGE_VERSION_INITIALIZED = true;
|
|
403
|
+
//#endregion
|
|
404
|
+
export { AuthenticationError, GeoError, InvalidQueryError, NoResultsError, PACKAGE_VERSION_INITIALIZED, RateLimitError, fetchStaticMap, getGeoAdapter, getOGMapUrl, getStaticMapUrl, isValidLatitude, isValidLongitude, mapGooglePlaceType, mapOSMPlaceType, normalizeCountryCode, validateCoordinates };
|
|
405
|
+
|
|
406
|
+
//# sourceMappingURL=index.js.map
|