@happyvertical/geo 0.74.8

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/index.js ADDED
@@ -0,0 +1,290 @@
1
+ import { loadEnvConfig } from "@happyvertical/utils";
2
+ class GeoError extends Error {
3
+ /**
4
+ * @param message - Human-readable error description
5
+ * @param code - Machine-readable error code (e.g. 'RATE_LIMIT', 'AUTH_ERROR')
6
+ * @param provider - Provider that raised the error ('google' | 'openstreetmap')
7
+ */
8
+ constructor(message, code, provider) {
9
+ super(message);
10
+ this.code = code;
11
+ this.provider = provider;
12
+ this.name = "GeoError";
13
+ }
14
+ }
15
+ class RateLimitError extends GeoError {
16
+ /**
17
+ * @param provider - Provider that raised the error
18
+ * @param retryAfter - Seconds to wait before retrying (if known)
19
+ */
20
+ constructor(provider, retryAfter) {
21
+ super(
22
+ `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ""}`,
23
+ "RATE_LIMIT",
24
+ provider
25
+ );
26
+ this.name = "RateLimitError";
27
+ }
28
+ }
29
+ class InvalidQueryError extends GeoError {
30
+ /**
31
+ * @param query - The invalid query string
32
+ * @param provider - Provider that raised the error
33
+ */
34
+ constructor(query, provider) {
35
+ super(`Invalid query: ${query}`, "INVALID_QUERY", provider);
36
+ this.name = "InvalidQueryError";
37
+ }
38
+ }
39
+ class AuthenticationError extends GeoError {
40
+ /**
41
+ * @param provider - Provider that raised the error
42
+ */
43
+ constructor(provider) {
44
+ super("Authentication failed", "AUTH_ERROR", provider);
45
+ this.name = "AuthenticationError";
46
+ }
47
+ }
48
+ class NoResultsError extends GeoError {
49
+ /**
50
+ * @param query - The query that produced no results
51
+ * @param provider - Provider that raised the error
52
+ */
53
+ constructor(query, provider) {
54
+ super(`No results found for query: ${query}`, "NO_RESULTS", provider);
55
+ this.name = "NoResultsError";
56
+ }
57
+ }
58
+ function mapGooglePlaceType(types) {
59
+ if (!types || types.length === 0) return "unknown";
60
+ if (types.includes("street_address") || types.includes("premise")) {
61
+ return "address";
62
+ }
63
+ if (types.includes("locality") || types.includes("postal_town")) {
64
+ return "city";
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";
76
+ }
77
+ function mapOSMPlaceType(type, addressType) {
78
+ const checkType = (type || "").toLowerCase();
79
+ const checkAddressType = (addressType || "").toLowerCase();
80
+ if (checkType === "house" || checkType === "building" || checkAddressType === "house") {
81
+ return "address";
82
+ }
83
+ if (checkType === "city" || checkType === "town" || checkType === "village" || checkType === "hamlet" || checkAddressType === "city" || checkAddressType === "town" || checkAddressType === "village") {
84
+ return "city";
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";
96
+ }
97
+ function normalizeCountryCode(code) {
98
+ if (!code) return "XX";
99
+ const normalized = code.toUpperCase().trim();
100
+ if (normalized.length === 2) {
101
+ return normalized;
102
+ }
103
+ return normalized.length === 3 ? normalized : "XX";
104
+ }
105
+ function isValidLatitude(lat) {
106
+ return typeof lat === "number" && lat >= -90 && lat <= 90;
107
+ }
108
+ function isValidLongitude(lng) {
109
+ return typeof lng === "number" && lng >= -180 && lng <= 180;
110
+ }
111
+ function validateCoordinates(latitude, longitude) {
112
+ if (!isValidLatitude(latitude)) {
113
+ return {
114
+ valid: false,
115
+ error: `Invalid latitude: ${latitude}. Must be between -90 and 90.`
116
+ };
117
+ }
118
+ if (!isValidLongitude(longitude)) {
119
+ return {
120
+ valid: false,
121
+ error: `Invalid longitude: ${longitude}. Must be between -180 and 180.`
122
+ };
123
+ }
124
+ return { valid: true };
125
+ }
126
+ function getMapboxUrl(latitude, longitude, options) {
127
+ const token = options.mapboxToken ?? process.env.MAPBOX_ACCESS_TOKEN ?? process.env.MAPBOX_TOKEN;
128
+ if (!token) {
129
+ throw new Error(
130
+ "Mapbox access token required. Provide via options.mapboxToken or MAPBOX_ACCESS_TOKEN env var."
131
+ );
132
+ }
133
+ const width = options.width ?? 1200;
134
+ const height = options.height ?? 630;
135
+ const zoom = options.zoom ?? 14;
136
+ const style = options.mapboxStyle ?? "streets-v12";
137
+ const scale = options.scale ?? 1;
138
+ const showMarker = options.showMarker ?? true;
139
+ let overlay = "";
140
+ if (showMarker) {
141
+ const color = (options.markerColor ?? "e74c3c").replace("#", "");
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}`;
155
+ }
156
+ function getGoogleUrl(latitude, longitude, options) {
157
+ const apiKey = options.googleApiKey ?? process.env.GOOGLE_MAPS_API_KEY ?? process.env.GOOGLE_API_KEY;
158
+ if (!apiKey) {
159
+ throw new Error(
160
+ "Google Maps API key required. Provide via options.googleApiKey or GOOGLE_MAPS_API_KEY env var."
161
+ );
162
+ }
163
+ const width = options.width ?? 1200;
164
+ const height = options.height ?? 630;
165
+ const zoom = options.zoom ?? 14;
166
+ const mapType = options.googleMapType ?? "roadmap";
167
+ const scale = options.scale ?? 1;
168
+ const showMarker = options.showMarker ?? true;
169
+ const params = new URLSearchParams({
170
+ center: `${latitude},${longitude}`,
171
+ zoom: zoom.toString(),
172
+ size: `${width}x${height}`,
173
+ maptype: mapType,
174
+ scale: scale.toString(),
175
+ key: apiKey
176
+ });
177
+ if (showMarker) {
178
+ const color = (options.markerColor ?? "red").replace("#", "0x");
179
+ params.append("markers", `color:${color}|${latitude},${longitude}`);
180
+ }
181
+ if (options.markers?.length) {
182
+ for (const m of options.markers) {
183
+ const color = (m.color ?? "red").replace("#", "0x");
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()}`;
193
+ }
194
+ function getStaticMapUrl(latitude, longitude, options = {}) {
195
+ const provider = options.provider ?? "mapbox";
196
+ switch (provider) {
197
+ case "google":
198
+ return getGoogleUrl(latitude, longitude, options);
199
+ case "mapbox":
200
+ default:
201
+ return getMapboxUrl(latitude, longitude, options);
202
+ }
203
+ }
204
+ async function fetchStaticMap(latitude, longitude, options = {}) {
205
+ const width = options.width ?? 1200;
206
+ const height = options.height ?? 630;
207
+ const url = getStaticMapUrl(latitude, longitude, options);
208
+ const response = await fetch(url);
209
+ if (!response.ok) {
210
+ const errorText = await response.text();
211
+ throw new Error(
212
+ `Failed to fetch static map: ${response.status} ${response.statusText} - ${errorText}`
213
+ );
214
+ }
215
+ const arrayBuffer = await response.arrayBuffer();
216
+ const buffer = Buffer.from(arrayBuffer);
217
+ return {
218
+ buffer,
219
+ width,
220
+ height,
221
+ mimeType: "image/png",
222
+ url
223
+ };
224
+ }
225
+ function getOGMapUrl(latitude, longitude, options = {}) {
226
+ return getStaticMapUrl(latitude, longitude, {
227
+ ...options,
228
+ width: 1200,
229
+ height: 630
230
+ });
231
+ }
232
+ function isGoogleMapsOptions(options) {
233
+ return options.provider === "google";
234
+ }
235
+ function isOpenStreetMapOptions(options) {
236
+ return options.provider === "openstreetmap";
237
+ }
238
+ async function getGeoAdapter(options = {}) {
239
+ const config = loadEnvConfig(options, {
240
+ packageName: "geo",
241
+ schema: {
242
+ provider: "string",
243
+ timeout: "number",
244
+ maxResults: "number",
245
+ rateLimitDelay: "number",
246
+ userAgent: "string"
247
+ }
248
+ });
249
+ if (config.provider === "google" && !config.apiKey) {
250
+ const apiKey = process.env.GOOGLE_MAPS_API_KEY;
251
+ if (apiKey) {
252
+ config.apiKey = apiKey;
253
+ }
254
+ }
255
+ if (!config.provider) {
256
+ throw new Error(
257
+ "Provider is required. Set via options.provider or HAVE_GEO_PROVIDER environment variable."
258
+ );
259
+ }
260
+ const fullOptions = config;
261
+ if (isGoogleMapsOptions(fullOptions)) {
262
+ const { GoogleMapsProvider } = await import("./chunks/google-Ci3_ec7t.js");
263
+ return new GoogleMapsProvider(fullOptions);
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}`);
270
+ }
271
+ const PACKAGE_VERSION_INITIALIZED = true;
272
+ export {
273
+ AuthenticationError,
274
+ GeoError,
275
+ InvalidQueryError,
276
+ NoResultsError,
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/shared/types.ts","../src/shared/utils.ts","../src/static-maps.ts","../src/index.ts"],"sourcesContent":["/**\n * Core types and interfaces for the Geo library\n */\n\n/**\n * Standardized location data structure\n */\nexport interface Location {\n /**\n * Unique identifier for the location (from provider)\n */\n id: string;\n\n /**\n * Type of location\n */\n type:\n | 'country'\n | 'region'\n | 'city'\n | 'address'\n | 'point_of_interest'\n | 'unknown';\n\n /**\n * Full formatted name/address of the location\n */\n name: string;\n\n /**\n * Latitude coordinate\n */\n latitude: number;\n\n /**\n * Longitude coordinate\n */\n longitude: number;\n\n /**\n * Address components (all optional)\n */\n addressComponents: {\n streetNumber?: string;\n streetName?: string;\n city?: string;\n region?: string;\n country?: string;\n postalCode?: string;\n };\n\n /**\n * ISO 3166-1 alpha-2 country code\n */\n countryCode: string;\n\n /**\n * Timezone identifier (optional, populated when provider returns it)\n */\n timezone?: string;\n\n /**\n * Raw response from the provider (for debugging or provider-specific data)\n */\n raw: any;\n}\n\n/**\n * Options for POI (point-of-interest) searches.\n *\n * `types` and `keyword` are both forwarded to the backing provider but each\n * provider interprets them slightly differently:\n *\n * - **Google**: `types[0]` becomes the request's `type` filter (Places API\n * accepts a single type per request; additional entries are ignored).\n * `keyword` is a free-text match across name/type/address/reviews.\n * - **OpenStreetMap (Overpass)**: `types` are matched against\n * `amenity`, `shop`, and `tourism` tag values (e.g. `'cafe'`,\n * `'supermarket'`, `'museum'`). When omitted, the provider searches across\n * a broad set of POI-ish tag keys (`amenity`, `shop`, `tourism`,\n * `leisure`, `office`, `historic`). `keyword` is appended as a substring\n * filter on the `name` tag.\n */\nexport interface PoiSearchOptions {\n /** Filter results to POIs matching these category values. See notes above. */\n types?: string[];\n /** Free-text keyword to narrow the search. */\n keyword?: string;\n /** Max results to return. Default 20. */\n limit?: number;\n /** Preferred language for place names (Google only). */\n language?: string;\n}\n\n/**\n * Geo provider interface - all providers must implement lookup and\n * reverseGeocode. `findPoisNear` is optional — providers implement it when\n * they support POI discovery beyond reverse geocoding. Callers that need to\n * know whether a given instance supports POI search should feature-detect:\n *\n * ```ts\n * if (typeof adapter.findPoisNear === 'function') { ... }\n * ```\n */\nexport interface GeoProvider {\n /**\n * Look up locations based on a query string\n * @param query - Search string (address, city, country, POI, etc.)\n * @returns Promise resolving to array of matching Location objects\n */\n lookup(query: string): Promise<Location[]>;\n\n /**\n * Reverse geocode from coordinates to location\n * @param latitude - Latitude coordinate\n * @param longitude - Longitude coordinate\n * @returns Promise resolving to array of matching Location objects\n */\n reverseGeocode(latitude: number, longitude: number): Promise<Location[]>;\n\n /**\n * Find POIs (point-of-interest places — businesses, landmarks, amenities)\n * within a radius of a coordinate. Returns locations with\n * `type: 'point_of_interest'` whose coords lie inside the requested\n * radius, sorted by the provider's relevance ranking.\n *\n * @param latitude - Center latitude\n * @param longitude - Center longitude\n * @param radiusMeters - Search radius in meters\n * @param options - Optional filters\n * @returns Promise resolving to array of matching Location objects\n */\n findPoisNear?(\n latitude: number,\n longitude: number,\n radiusMeters: number,\n options?: PoiSearchOptions,\n ): Promise<Location[]>;\n}\n\n/**\n * Geo adapter interface (structurally identical to GeoProvider)\n */\nexport interface GeoAdapter {\n /**\n * Look up locations based on a query string\n * @param query - Search string (address, city, country, POI, etc.)\n * @returns Promise resolving to array of matching Location objects\n */\n lookup(query: string): Promise<Location[]>;\n\n /**\n * Reverse geocode from coordinates to location\n * @param latitude - Latitude coordinate\n * @param longitude - Longitude coordinate\n * @returns Promise resolving to array of matching Location objects\n */\n reverseGeocode(latitude: number, longitude: number): Promise<Location[]>;\n\n /**\n * Find POIs near a coordinate. Optional — see `GeoProvider.findPoisNear`.\n */\n findPoisNear?(\n latitude: number,\n longitude: number,\n radiusMeters: number,\n options?: PoiSearchOptions,\n ): Promise<Location[]>;\n}\n\n/**\n * Base configuration options for all providers\n */\nexport interface BaseGeoOptions {\n /**\n * Request timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Maximum number of results to return\n */\n maxResults?: number;\n}\n\n/**\n * Google Maps provider options\n */\nexport interface GoogleMapsOptions extends BaseGeoOptions {\n provider: 'google';\n /**\n * Google Maps API key\n */\n apiKey: string;\n}\n\n/**\n * OpenStreetMap provider options\n */\nexport interface OpenStreetMapOptions extends BaseGeoOptions {\n provider: 'openstreetmap';\n /**\n * Custom User-Agent for OSM requests (optional, defaults to package name)\n */\n userAgent?: string;\n /**\n * Rate limit delay in milliseconds (default: 1000ms for 1 req/sec)\n */\n rateLimitDelay?: number;\n}\n\n/**\n * Union type for all provider options\n */\nexport type GeoAdapterOptions = GoogleMapsOptions | OpenStreetMapOptions;\n\n/**\n * Base error class for geo operations.\n * All geo-specific errors extend this class and include a machine-readable `code`\n * and the `provider` name that produced the error.\n */\nexport class GeoError extends Error {\n /**\n * @param message - Human-readable error description\n * @param code - Machine-readable error code (e.g. 'RATE_LIMIT', 'AUTH_ERROR')\n * @param provider - Provider that raised the error ('google' | 'openstreetmap')\n */\n constructor(\n message: string,\n public code: string,\n public provider?: string,\n ) {\n super(message);\n this.name = 'GeoError';\n }\n}\n\n/**\n * Thrown when the provider's rate limit has been exceeded.\n */\nexport class RateLimitError extends GeoError {\n /**\n * @param provider - Provider that raised the error\n * @param retryAfter - Seconds to wait before retrying (if known)\n */\n constructor(provider?: string, retryAfter?: number) {\n super(\n `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ''}`,\n 'RATE_LIMIT',\n provider,\n );\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Thrown when the geocoding query is empty or malformed.\n */\nexport class InvalidQueryError extends GeoError {\n /**\n * @param query - The invalid query string\n * @param provider - Provider that raised the error\n */\n constructor(query: string, provider?: string) {\n super(`Invalid query: ${query}`, 'INVALID_QUERY', provider);\n this.name = 'InvalidQueryError';\n }\n}\n\n/**\n * Thrown when the provider rejects the API key or credentials.\n */\nexport class AuthenticationError extends GeoError {\n /**\n * @param provider - Provider that raised the error\n */\n constructor(provider?: string) {\n super('Authentication failed', 'AUTH_ERROR', provider);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when a geocoding query returns zero results.\n */\nexport class NoResultsError extends GeoError {\n /**\n * @param query - The query that produced no results\n * @param provider - Provider that raised the error\n */\n constructor(query: string, provider?: string) {\n super(`No results found for query: ${query}`, 'NO_RESULTS', provider);\n this.name = 'NoResultsError';\n }\n}\n","/**\n * Utility functions for geo operations\n */\n\nimport type { Location } from './types';\n\n/**\n * Map Google Maps place types to standardized location types.\n * Checks types in priority order: address, city, region, country, point_of_interest.\n *\n * @param types - Array of Google Maps place type strings (e.g. 'street_address', 'locality')\n * @returns The standardized location type\n */\nexport function mapGooglePlaceType(types: string[]): Location['type'] {\n if (!types || types.length === 0) return 'unknown';\n\n // Check in priority order\n if (types.includes('street_address') || types.includes('premise')) {\n return 'address';\n }\n if (types.includes('locality') || types.includes('postal_town')) {\n return 'city';\n }\n if (\n types.includes('administrative_area_level_1') ||\n types.includes('administrative_area_level_2')\n ) {\n return 'region';\n }\n if (types.includes('country')) {\n return 'country';\n }\n if (types.includes('point_of_interest') || types.includes('establishment')) {\n return 'point_of_interest';\n }\n\n return 'unknown';\n}\n\n/**\n * Map OpenStreetMap place types to standardized location types.\n * Uses both the Nominatim `type` and `addresstype` fields for classification.\n *\n * @param type - Nominatim result type (e.g. 'house', 'city', 'state')\n * @param addressType - Nominatim addresstype field (optional secondary classifier)\n * @returns The standardized location type\n */\nexport function mapOSMPlaceType(\n type: string,\n addressType?: string,\n): Location['type'] {\n // OSM uses different classification - check both type and addresstype\n const checkType = (type || '').toLowerCase();\n const checkAddressType = (addressType || '').toLowerCase();\n\n if (\n checkType === 'house' ||\n checkType === 'building' ||\n checkAddressType === 'house'\n ) {\n return 'address';\n }\n\n if (\n checkType === 'city' ||\n checkType === 'town' ||\n checkType === 'village' ||\n checkType === 'hamlet' ||\n checkAddressType === 'city' ||\n checkAddressType === 'town' ||\n checkAddressType === 'village'\n ) {\n return 'city';\n }\n\n if (\n checkType === 'state' ||\n checkType === 'province' ||\n checkType === 'region' ||\n checkAddressType === 'state'\n ) {\n return 'region';\n }\n\n if (checkType === 'country' || checkAddressType === 'country') {\n return 'country';\n }\n\n if (\n checkType === 'attraction' ||\n checkType === 'tourism' ||\n checkType === 'amenity'\n ) {\n return 'point_of_interest';\n }\n\n return 'unknown';\n}\n\n/**\n * Normalize a country code to uppercase ISO 3166-1 format.\n * Returns 'XX' for undefined, empty, or unrecognized inputs.\n *\n * @param code - Country code string (alpha-2 or alpha-3)\n * @returns Uppercase country code, or 'XX' if unknown\n */\nexport function normalizeCountryCode(code: string | undefined): string {\n if (!code) return 'XX'; // Unknown country code\n\n // Already uppercase 2-letter code\n const normalized = code.toUpperCase().trim();\n\n // If it's already 2 letters, return it\n if (normalized.length === 2) {\n return normalized;\n }\n\n // If it's 3 letters (alpha-3), we would need a mapping table\n // For now, return as-is or 'XX' for unknown\n return normalized.length === 3 ? normalized : 'XX';\n}\n\n/**\n * Validate that a latitude value is within the valid range (-90 to 90).\n *\n * @param lat - Latitude value to validate\n * @returns `true` if the value is a number between -90 and 90 inclusive\n */\nexport function isValidLatitude(lat: number): boolean {\n return typeof lat === 'number' && lat >= -90 && lat <= 90;\n}\n\n/**\n * Validate that a longitude value is within the valid range (-180 to 180).\n *\n * @param lng - Longitude value to validate\n * @returns `true` if the value is a number between -180 and 180 inclusive\n */\nexport function isValidLongitude(lng: number): boolean {\n return typeof lng === 'number' && lng >= -180 && lng <= 180;\n}\n\n/**\n * Validate that both latitude and longitude are within their valid ranges.\n *\n * @param latitude - Latitude value (-90 to 90)\n * @param longitude - Longitude value (-180 to 180)\n * @returns Object with `valid: true` if both are valid, or `valid: false` with an `error` message\n */\nexport function validateCoordinates(\n latitude: number,\n longitude: number,\n): { valid: boolean; error?: string } {\n if (!isValidLatitude(latitude)) {\n return {\n valid: false,\n error: `Invalid latitude: ${latitude}. Must be between -90 and 90.`,\n };\n }\n\n if (!isValidLongitude(longitude)) {\n return {\n valid: false,\n error: `Invalid longitude: ${longitude}. Must be between -180 and 180.`,\n };\n }\n\n return { valid: true };\n}\n","/**\n * Static Map Generation\n *\n * Generates static map URLs and fetches map images for location-based content.\n * Supports Mapbox and Google Maps providers.\n *\n * @example\n * ```typescript\n * import { getStaticMapUrl, fetchStaticMap } from '@happyvertical/geo';\n *\n * // Get URL for embedding\n * const url = getStaticMapUrl(53.5461, -113.4938, {\n * provider: 'mapbox',\n * zoom: 14,\n * width: 1200,\n * height: 630\n * });\n *\n * // Fetch the actual image\n * const buffer = await fetchStaticMap(53.5461, -113.4938, {\n * provider: 'mapbox'\n * });\n * await writeFile('map.png', buffer);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Static map provider\n */\nexport type StaticMapProvider = 'mapbox' | 'google';\n\n/**\n * Mapbox map styles\n * @see https://docs.mapbox.com/api/maps/styles/\n */\nexport type MapboxStyle =\n | 'streets-v12'\n | 'outdoors-v12'\n | 'light-v11'\n | 'dark-v11'\n | 'satellite-v9'\n | 'satellite-streets-v12';\n\n/**\n * Google Maps map types\n * @see https://developers.google.com/maps/documentation/maps-static/start\n */\nexport type GoogleMapType = 'roadmap' | 'satellite' | 'terrain' | 'hybrid';\n\n/**\n * Marker definition for the map\n */\nexport interface StaticMapMarker {\n /** Latitude */\n latitude: number;\n /** Longitude */\n longitude: number;\n /** Marker color (hex or named color) */\n color?: string;\n /** Marker label (single character for Google, any for Mapbox) */\n label?: string;\n /** Marker size ('small' | 'medium' | 'large') */\n size?: 'small' | 'medium' | 'large';\n}\n\n/**\n * Options for static map generation\n */\nexport interface StaticMapOptions {\n /**\n * Map provider\n * @default 'mapbox'\n */\n provider?: StaticMapProvider;\n\n /**\n * Image width in pixels\n * @default 1200\n */\n width?: number;\n\n /**\n * Image height in pixels\n * @default 630\n */\n height?: number;\n\n /**\n * Zoom level (1-20)\n * @default 14\n */\n zoom?: number;\n\n /**\n * Marker color (hex without # or named color)\n * @default 'e74c3c' (red)\n */\n markerColor?: string;\n\n /**\n * Show a marker at the center point\n * @default true\n */\n showMarker?: boolean;\n\n /**\n * Mapbox access token (required for Mapbox provider)\n * Falls back to MAPBOX_ACCESS_TOKEN env var\n */\n mapboxToken?: string;\n\n /**\n * Google Maps API key (required for Google provider)\n * Falls back to GOOGLE_MAPS_API_KEY env var\n */\n googleApiKey?: string;\n\n /**\n * Mapbox style\n * @default 'streets-v12'\n */\n mapboxStyle?: MapboxStyle;\n\n /**\n * Google map type\n * @default 'roadmap'\n */\n googleMapType?: GoogleMapType;\n\n /**\n * Pixel density (1 or 2 for retina)\n * @default 1\n */\n scale?: 1 | 2;\n\n /**\n * Additional markers to display\n */\n markers?: StaticMapMarker[];\n}\n\n/**\n * Result from fetching a static map\n */\nexport interface StaticMapResult {\n /** PNG image buffer */\n buffer: Buffer;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n /** MIME type */\n mimeType: 'image/png';\n /** The URL that was fetched */\n url: string;\n}\n\n// ============================================================================\n// URL Generators\n// ============================================================================\n\n/**\n * Generate Mapbox static map URL\n */\nfunction getMapboxUrl(\n latitude: number,\n longitude: number,\n options: StaticMapOptions,\n): string {\n const token =\n options.mapboxToken ??\n process.env.MAPBOX_ACCESS_TOKEN ??\n process.env.MAPBOX_TOKEN;\n if (!token) {\n throw new Error(\n 'Mapbox access token required. Provide via options.mapboxToken or MAPBOX_ACCESS_TOKEN env var.',\n );\n }\n\n const width = options.width ?? 1200;\n const height = options.height ?? 630;\n const zoom = options.zoom ?? 14;\n const style = options.mapboxStyle ?? 'streets-v12';\n const scale = options.scale ?? 1;\n const showMarker = options.showMarker ?? true;\n\n // Build overlay for marker\n let overlay = '';\n if (showMarker) {\n const color = (options.markerColor ?? 'e74c3c').replace('#', '');\n // Mapbox marker format: pin-{size}-{label}+{color}({lon},{lat})\n overlay = `pin-l+${color}(${longitude},${latitude})/`;\n }\n\n // Add additional markers\n if (options.markers?.length) {\n const markerOverlays = options.markers.map((m) => {\n const color = (m.color ?? 'e74c3c').replace('#', '');\n const size = m.size === 'small' ? 's' : m.size === 'large' ? 'l' : 'm';\n const label = m.label ? `-${m.label}` : '';\n return `pin-${size}${label}+${color}(${m.longitude},${m.latitude})`;\n });\n overlay = markerOverlays.join(',') + '/';\n }\n\n const retinaStr = scale === 2 ? '@2x' : '';\n\n return `https://api.mapbox.com/styles/v1/mapbox/${style}/static/${overlay}${longitude},${latitude},${zoom}/${width}x${height}${retinaStr}?access_token=${token}`;\n}\n\n/**\n * Generate Google Maps static map URL\n */\nfunction getGoogleUrl(\n latitude: number,\n longitude: number,\n options: StaticMapOptions,\n): string {\n const apiKey =\n options.googleApiKey ??\n process.env.GOOGLE_MAPS_API_KEY ??\n process.env.GOOGLE_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'Google Maps API key required. Provide via options.googleApiKey or GOOGLE_MAPS_API_KEY env var.',\n );\n }\n\n const width = options.width ?? 1200;\n const height = options.height ?? 630;\n const zoom = options.zoom ?? 14;\n const mapType = options.googleMapType ?? 'roadmap';\n const scale = options.scale ?? 1;\n const showMarker = options.showMarker ?? true;\n\n const params = new URLSearchParams({\n center: `${latitude},${longitude}`,\n zoom: zoom.toString(),\n size: `${width}x${height}`,\n maptype: mapType,\n scale: scale.toString(),\n key: apiKey,\n });\n\n // Add center marker\n if (showMarker) {\n const color = (options.markerColor ?? 'red').replace('#', '0x');\n params.append('markers', `color:${color}|${latitude},${longitude}`);\n }\n\n // Add additional markers\n if (options.markers?.length) {\n for (const m of options.markers) {\n const color = (m.color ?? 'red').replace('#', '0x');\n const size = m.size ?? 'medium';\n const label = m.label ? `|label:${m.label.charAt(0).toUpperCase()}` : '';\n params.append(\n 'markers',\n `color:${color}|size:${size}${label}|${m.latitude},${m.longitude}`,\n );\n }\n }\n\n return `https://maps.googleapis.com/maps/api/staticmap?${params.toString()}`;\n}\n\n// ============================================================================\n// Main Functions\n// ============================================================================\n\n/**\n * Generate a static map URL\n *\n * Returns a URL that can be used directly in <img> tags or fetched for processing.\n *\n * @param latitude - Center latitude\n * @param longitude - Center longitude\n * @param options - Map options\n * @returns URL string for the static map\n *\n * @example Mapbox (default)\n * ```typescript\n * const url = getStaticMapUrl(53.5461, -113.4938, {\n * provider: 'mapbox',\n * zoom: 14,\n * mapboxStyle: 'streets-v12'\n * });\n * ```\n *\n * @example Google Maps\n * ```typescript\n * const url = getStaticMapUrl(53.5461, -113.4938, {\n * provider: 'google',\n * zoom: 14,\n * googleMapType: 'roadmap'\n * });\n * ```\n *\n * @example With custom markers\n * ```typescript\n * const url = getStaticMapUrl(53.5461, -113.4938, {\n * showMarker: false, // Hide center marker\n * markers: [\n * { latitude: 53.5461, longitude: -113.4938, color: 'blue', label: 'A' },\n * { latitude: 53.5500, longitude: -113.4900, color: 'green', label: 'B' }\n * ]\n * });\n * ```\n */\nexport function getStaticMapUrl(\n latitude: number,\n longitude: number,\n options: StaticMapOptions = {},\n): string {\n const provider = options.provider ?? 'mapbox';\n\n switch (provider) {\n case 'google':\n return getGoogleUrl(latitude, longitude, options);\n case 'mapbox':\n default:\n return getMapboxUrl(latitude, longitude, options);\n }\n}\n\n/**\n * Fetch a static map image\n *\n * Downloads the map image and returns it as a Buffer.\n *\n * @param latitude - Center latitude\n * @param longitude - Center longitude\n * @param options - Map options\n * @returns Promise resolving to the map image result\n *\n * @example\n * ```typescript\n * const result = await fetchStaticMap(53.5461, -113.4938, {\n * provider: 'mapbox',\n * width: 1200,\n * height: 630\n * });\n *\n * await fs.writeFile('map.png', result.buffer);\n * console.log(`Fetched ${result.width}x${result.height} map`);\n * ```\n */\nexport async function fetchStaticMap(\n latitude: number,\n longitude: number,\n options: StaticMapOptions = {},\n): Promise<StaticMapResult> {\n const width = options.width ?? 1200;\n const height = options.height ?? 630;\n const url = getStaticMapUrl(latitude, longitude, options);\n\n const response = await fetch(url);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to fetch static map: ${response.status} ${response.statusText} - ${errorText}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n return {\n buffer,\n width,\n height,\n mimeType: 'image/png',\n url,\n };\n}\n\n/**\n * Generate OG-sized static map URL (1200x630)\n *\n * Convenience function for generating Open Graph / social media sized maps.\n *\n * @param latitude - Center latitude\n * @param longitude - Center longitude\n * @param options - Map options (width/height ignored, set to 1200x630)\n * @returns URL string for the static map\n *\n * @example\n * ```typescript\n * const url = getOGMapUrl(53.5461, -113.4938);\n * // Returns 1200x630 map URL\n * ```\n */\nexport function getOGMapUrl(\n latitude: number,\n longitude: number,\n options: Omit<StaticMapOptions, 'width' | 'height'> = {},\n): string {\n return getStaticMapUrl(latitude, longitude, {\n ...options,\n width: 1200,\n height: 630,\n });\n}\n","/**\n * Geo package entry point\n * Provides standardized geographical information interface\n */\n\nimport { loadEnvConfig } from '@happyvertical/utils';\nimport type {\n GeoAdapter,\n GeoAdapterOptions,\n GoogleMapsOptions,\n OpenStreetMapOptions,\n} from './shared/types';\n\n// Export all types\nexport * from './shared/types';\nexport * from './shared/utils';\n\n// Export static map generation\nexport {\n fetchStaticMap,\n type GoogleMapType,\n getOGMapUrl,\n getStaticMapUrl,\n type MapboxStyle,\n type StaticMapMarker,\n type StaticMapOptions,\n type StaticMapProvider,\n type StaticMapResult,\n} from './static-maps';\n\n/**\n * Type guard for Google Maps options\n */\nfunction isGoogleMapsOptions(\n options: GeoAdapterOptions,\n): options is GoogleMapsOptions {\n return options.provider === 'google';\n}\n\n/**\n * Type guard for OpenStreetMap options\n */\nfunction isOpenStreetMapOptions(\n options: GeoAdapterOptions,\n): options is OpenStreetMapOptions {\n return options.provider === 'openstreetmap';\n}\n\n/**\n * Factory function to create a geo adapter instance\n *\n * Supports configuration via environment variables using the pattern:\n * - HAVE_GEO_PROVIDER → provider ('google' | 'openstreetmap')\n * - GOOGLE_MAPS_API_KEY → apiKey (for Google Maps provider)\n *\n * User-provided options always take precedence over environment variables.\n *\n * @param options - Configuration options for the geo provider (optional)\n * @returns Promise resolving to a geo adapter that implements GeoAdapter\n *\n * @example\n * ```typescript\n * // Create Google Maps adapter with explicit options\n * const googleGeo = await getGeoAdapter({\n * provider: 'google',\n * apiKey: process.env.GOOGLE_MAPS_API_KEY!\n * });\n *\n * // Create adapter using environment variables\n * // HAVE_GEO_PROVIDER=google\n * // GOOGLE_MAPS_API_KEY=your-api-key\n * const geoFromEnv = await getGeoAdapter();\n *\n * // Create OpenStreetMap adapter\n * const osmGeo = await getGeoAdapter({\n * provider: 'openstreetmap'\n * });\n *\n * // Use the adapter\n * const locations = await googleGeo.lookup('Eiffel Tower');\n * const coords = await osmGeo.reverseGeocode(48.8584, 2.2945);\n * ```\n */\nexport async function getGeoAdapter(\n options: Partial<GeoAdapterOptions> = {},\n): Promise<GeoAdapter> {\n // Load configuration from environment variables\n // Cast to any to handle union type with loadEnvConfig\n const config = loadEnvConfig(options as any, {\n packageName: 'geo',\n schema: {\n provider: 'string',\n timeout: 'number',\n maxResults: 'number',\n rateLimitDelay: 'number',\n userAgent: 'string',\n },\n }) as Partial<GeoAdapterOptions>;\n\n // Handle Google Maps API key from environment (not using HAVE_GEO_ prefix)\n if (config.provider === 'google' && !config.apiKey) {\n const apiKey = process.env.GOOGLE_MAPS_API_KEY;\n if (apiKey) {\n config.apiKey = apiKey;\n }\n }\n\n // Validate that we have a provider\n if (!config.provider) {\n throw new Error(\n 'Provider is required. Set via options.provider or HAVE_GEO_PROVIDER environment variable.',\n );\n }\n\n // Cast to full options type for provider-specific handling\n const fullOptions = config as GeoAdapterOptions;\n\n if (isGoogleMapsOptions(fullOptions)) {\n const { GoogleMapsProvider } = await import('./providers/google.js');\n return new GoogleMapsProvider(fullOptions);\n }\n\n if (isOpenStreetMapOptions(fullOptions)) {\n const { OpenStreetMapProvider } = await import(\n './providers/openstreetmap.js'\n );\n return new OpenStreetMapProvider(fullOptions);\n }\n\n // This should never happen due to TypeScript's discriminated union\n throw new Error(`Unsupported provider: ${(fullOptions as any).provider}`);\n}\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { GeoProvider, GoogleMapsOptions, Location, PoiSearchOptions } from '../shared/types';
2
+ /**
3
+ * Google Maps provider implementation with in-memory caching
4
+ */
5
+ export declare class GoogleMapsProvider implements GeoProvider {
6
+ private client;
7
+ private apiKey;
8
+ private timeout;
9
+ private maxResults;
10
+ private cache;
11
+ constructor(options: GoogleMapsOptions);
12
+ /**
13
+ * Initializes the memory cache for geocoding results
14
+ */
15
+ private initCache;
16
+ /**
17
+ * Generates a cache key for geocoding requests
18
+ */
19
+ private getCacheKey;
20
+ /**
21
+ * Look up locations based on a query string
22
+ */
23
+ lookup(query: string): Promise<Location[]>;
24
+ /**
25
+ * Reverse geocode from coordinates to location
26
+ */
27
+ reverseGeocode(latitude: number, longitude: number): Promise<Location[]>;
28
+ /**
29
+ * Find POIs near a coordinate using the Places API Nearby Search endpoint.
30
+ *
31
+ * Maps to `placesNearby` on the Google Maps Services SDK. Note that Places
32
+ * Nearby Search is a separate product line from Geocoding: it requires the
33
+ * Places API to be enabled for the supplied API key and is billed per
34
+ * request. Results are deduped across multi-type requests by `place_id`.
35
+ */
36
+ findPoisNear(latitude: number, longitude: number, radiusMeters: number, options?: PoiSearchOptions): Promise<Location[]>;
37
+ /**
38
+ * Map Google Places Nearby Search result to standardized Location. The
39
+ * Places schema is a superset of Geocoding (adds `name`, `vicinity`,
40
+ * `types[]` semantics slightly different from geocoding `types`) so this
41
+ * is a separate mapper from `mapGoogleResultToLocation`.
42
+ */
43
+ private mapGooglePoiToLocation;
44
+ /**
45
+ * Map Google Geocoding API result to standardized Location
46
+ */
47
+ private mapGoogleResultToLocation;
48
+ }
49
+ //# sourceMappingURL=google.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAazB;;GAEG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAA6B;gBAE9B,OAAO,EAAE,iBAAiB;IAUtC;;OAEG;YACW,SAAS;IAgBvB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAmEhD;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,EAAE,CAAC;IA4EtB;;;;;;;OAOG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,QAAQ,EAAE,CAAC;IAgItB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;OAEG;IACH,OAAO,CAAC,yBAAyB;CAgDlC"}
@@ -0,0 +1,74 @@
1
+ import { GeoProvider, Location, OpenStreetMapOptions, PoiSearchOptions } from '../shared/types';
2
+ /**
3
+ * OpenStreetMap provider using Nominatim API with in-memory caching
4
+ */
5
+ export declare class OpenStreetMapProvider implements GeoProvider {
6
+ private baseUrl;
7
+ /**
8
+ * Overpass endpoint for POI search. The public instance has a community
9
+ * use-policy similar to Nominatim's — be polite, cache aggressively, and
10
+ * consider a self-hosted instance if you'd hit it hard.
11
+ */
12
+ private overpassUrl;
13
+ private userAgent;
14
+ private rateLimitDelay;
15
+ private lastRequestTime;
16
+ private timeout;
17
+ private maxResults;
18
+ private cache;
19
+ constructor(options: OpenStreetMapOptions);
20
+ /**
21
+ * Initializes the memory cache for geocoding results
22
+ */
23
+ private initCache;
24
+ /**
25
+ * Generates a cache key for geocoding requests
26
+ */
27
+ private getCacheKey;
28
+ /**
29
+ * Enforce rate limiting by waiting if necessary
30
+ */
31
+ private enforceRateLimit;
32
+ /**
33
+ * Make HTTP request to Nominatim API
34
+ */
35
+ private fetchNominatim;
36
+ /**
37
+ * Look up locations based on a query string
38
+ */
39
+ lookup(query: string): Promise<Location[]>;
40
+ /**
41
+ * Reverse geocode from coordinates to location
42
+ */
43
+ reverseGeocode(latitude: number, longitude: number): Promise<Location[]>;
44
+ /**
45
+ * Find POIs near a coordinate using the public Overpass API. Nominatim
46
+ * only does address-level reverse geocoding — Overpass is the right tool
47
+ * when you want "every café within 200m" kind of queries.
48
+ */
49
+ findPoisNear(latitude: number, longitude: number, radiusMeters: number, options?: PoiSearchOptions): Promise<Location[]>;
50
+ /**
51
+ * Build an Overpass QL query that collects nodes + ways with POI-shaped
52
+ * tags within `radiusMeters` of the center point. `out center tags`
53
+ * coerces ways/relations into point geometries so downstream mapping
54
+ * doesn't have to deal with geometry types.
55
+ */
56
+ private buildOverpassQuery;
57
+ /**
58
+ * Map an Overpass element to a standardized Location. Returns null for
59
+ * tagless elements or ways without a `center` (which can happen when the
60
+ * server falls back to geometry-less output under load).
61
+ */
62
+ private mapOverpassElementToLocation;
63
+ /**
64
+ * Produce a readable label from a tag-only element (e.g. a shop with no
65
+ * `name`). Picks the first POI-shaped tag value as a fallback so the
66
+ * caller still gets something meaningful to show operators.
67
+ */
68
+ private derivePoiLabelFromTags;
69
+ /**
70
+ * Map Nominatim result to standardized Location
71
+ */
72
+ private mapNominatimResultToLocation;
73
+ }
74
+ //# sourceMappingURL=openstreetmap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openstreetmap.d.ts","sourceRoot":"","sources":["../../src/providers/openstreetmap.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AA2EzB;;GAEG;AACH,qBAAa,qBAAsB,YAAW,WAAW;IACvD,OAAO,CAAC,OAAO,CAAyC;IACxD;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAA6B;gBAE9B,OAAO,EAAE,oBAAoB;IAUzC;;OAEG;YACW,SAAS;IAgBvB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;YACW,gBAAgB;IAY9B;;OAEG;YACW,cAAc;IAkE5B;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAuChD;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,EAAE,CAAC;IAqDtB;;;;OAIG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,QAAQ,EAAE,CAAC;IA0GtB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAwC1B;;;;OAIG;IACH,OAAO,CAAC,4BAA4B;IAmCpC;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAQ9B;;OAEG;IACH,OAAO,CAAC,4BAA4B;CA2CrC"}