@happyvertical/smrt-places 0.30.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/AGENTS.md +29 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +185 -0
- package/dist/index.d.ts +606 -0
- package/dist/index.js +966 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +2382 -0
- package/dist/smrt-knowledge.json +900 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +113 -0
- package/dist/utils.js +110 -0
- package/dist/utils.js.map +1 -0
- package/package.json +76 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for `PlaceCollection.discoverNearby`. Mirrors the inputs to
|
|
5
|
+
* `@happyvertical/geo`'s `findPoisNear` plus a couple of persistence knobs
|
|
6
|
+
* (PlaceType override, parent linkage) to match `lookupOrCreate`'s shape.
|
|
7
|
+
*/
|
|
8
|
+
export declare interface DiscoverNearbyOptions {
|
|
9
|
+
/** Which geo provider to use. Defaults to openstreetmap. */
|
|
10
|
+
geoProvider?: 'google' | 'openstreetmap';
|
|
11
|
+
/**
|
|
12
|
+
* POI category filter, forwarded to the provider. See
|
|
13
|
+
* `@happyvertical/geo`'s `PoiSearchOptions.types` for provider-specific
|
|
14
|
+
* interpretation.
|
|
15
|
+
*/
|
|
16
|
+
types?: string[];
|
|
17
|
+
/** Free-text keyword filter (provider-dependent; Google only). */
|
|
18
|
+
keyword?: string;
|
|
19
|
+
/** Max results per provider call. Default 20. */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** Preferred language for place names (Google only). */
|
|
22
|
+
language?: string;
|
|
23
|
+
/** Override the PlaceType slug assigned to created rows. */
|
|
24
|
+
typeSlug?: string;
|
|
25
|
+
/** Set parent place on created rows. */
|
|
26
|
+
parentId?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Geographic data structure
|
|
31
|
+
* All fields optional to support both real-world and abstract places
|
|
32
|
+
*/
|
|
33
|
+
export declare interface GeoData {
|
|
34
|
+
latitude: number | null;
|
|
35
|
+
longitude: number | null;
|
|
36
|
+
streetNumber?: string;
|
|
37
|
+
streetName?: string;
|
|
38
|
+
city?: string;
|
|
39
|
+
region?: string;
|
|
40
|
+
country?: string;
|
|
41
|
+
postalCode?: string;
|
|
42
|
+
countryCode?: string;
|
|
43
|
+
timezone?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Options for lookupOrCreate method
|
|
48
|
+
*/
|
|
49
|
+
export declare interface LookupOrCreateOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Which geo provider to use for lookups
|
|
52
|
+
*/
|
|
53
|
+
geoProvider?: 'google' | 'openstreetmap';
|
|
54
|
+
/**
|
|
55
|
+
* Force a specific place type
|
|
56
|
+
*/
|
|
57
|
+
typeSlug?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Set parent place if known
|
|
60
|
+
*/
|
|
61
|
+
parentId?: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to create if not found (default: true)
|
|
64
|
+
*/
|
|
65
|
+
createIfNotFound?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Coordinates for reverse geocoding
|
|
68
|
+
*/
|
|
69
|
+
coords?: {
|
|
70
|
+
lat: number;
|
|
71
|
+
lng: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Place hierarchy structure
|
|
77
|
+
*/
|
|
78
|
+
export declare interface PlaceHierarchy {
|
|
79
|
+
ancestors: any[];
|
|
80
|
+
current: any;
|
|
81
|
+
descendants: any[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Options for creating/updating a Place
|
|
86
|
+
*/
|
|
87
|
+
export declare interface PlaceOptions extends SmrtObjectOptions {
|
|
88
|
+
id?: string;
|
|
89
|
+
tenantId?: string | null;
|
|
90
|
+
typeId?: string;
|
|
91
|
+
parentId?: string | null;
|
|
92
|
+
name?: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
latitude?: number | null;
|
|
95
|
+
longitude?: number | null;
|
|
96
|
+
streetNumber?: string;
|
|
97
|
+
streetName?: string;
|
|
98
|
+
city?: string;
|
|
99
|
+
region?: string;
|
|
100
|
+
country?: string;
|
|
101
|
+
postalCode?: string;
|
|
102
|
+
countryCode?: string;
|
|
103
|
+
timezone?: string;
|
|
104
|
+
externalId?: string;
|
|
105
|
+
source?: string;
|
|
106
|
+
metadata?: Record<string, any> | string;
|
|
107
|
+
createdAt?: Date;
|
|
108
|
+
updatedAt?: Date;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Options for creating/updating a PlaceType
|
|
113
|
+
*/
|
|
114
|
+
export declare interface PlaceTypeOptions extends SmrtObjectOptions {
|
|
115
|
+
id?: string;
|
|
116
|
+
slug?: string;
|
|
117
|
+
name?: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
createdAt?: Date;
|
|
120
|
+
updatedAt?: Date;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Options for `PlaceCollection.resolveTrackPlaces`. Extends
|
|
125
|
+
* `DiscoverNearbyOptions` with per-track knobs that control how the track
|
|
126
|
+
* is bucketed + throttled before hitting the geo provider.
|
|
127
|
+
*/
|
|
128
|
+
export declare interface ResolveTrackPlacesOptions extends DiscoverNearbyOptions {
|
|
129
|
+
/**
|
|
130
|
+
* Per-point POI search radius in meters. Default 50.
|
|
131
|
+
*/
|
|
132
|
+
radiusMeters?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Collapse points within this distance (m) into a single bucket so we
|
|
135
|
+
* don't re-query the same ~50m area dozens of times for a slow drive-by.
|
|
136
|
+
* Default 50.
|
|
137
|
+
*/
|
|
138
|
+
bucketMeters?: number;
|
|
139
|
+
/**
|
|
140
|
+
* Minimum delay between provider requests. Default 1100ms — safely above
|
|
141
|
+
* OSM's 1 req/sec community limit. Google can usually handle faster; set
|
|
142
|
+
* to 100ms or less for paid tiers.
|
|
143
|
+
*/
|
|
144
|
+
throttleMs?: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Result of `PlaceCollection.resolveTrackPlaces`. Tracks are typically
|
|
149
|
+
* long-running operations so the return payload includes counters that let
|
|
150
|
+
* callers surface progress or estimate cost without re-walking.
|
|
151
|
+
*/
|
|
152
|
+
export declare interface TrackPlacesResult {
|
|
153
|
+
/** De-duplicated Place rows touched during resolution. */
|
|
154
|
+
places: any[];
|
|
155
|
+
/** Number of provider requests issued. */
|
|
156
|
+
requestCount: number;
|
|
157
|
+
/** Number of bucketed points that reused existing Place rows. */
|
|
158
|
+
cacheHitCount: number;
|
|
159
|
+
/** Number of distinct geographic buckets walked. */
|
|
160
|
+
bucketCount: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A single point along a track. Matches the `{ lat, lng }` convention used
|
|
165
|
+
* by `LookupOrCreateOptions.coords` so callers don't have to shuffle shapes.
|
|
166
|
+
*/
|
|
167
|
+
export declare interface TrackPoint {
|
|
168
|
+
lat: number;
|
|
169
|
+
lng: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { }
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Location } from '@happyvertical/geo';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if two coordinates are within a threshold distance
|
|
5
|
+
*
|
|
6
|
+
* @param lat1 - First latitude
|
|
7
|
+
* @param lng1 - First longitude
|
|
8
|
+
* @param lat2 - Second latitude
|
|
9
|
+
* @param lng2 - Second longitude
|
|
10
|
+
* @param thresholdKm - Distance threshold in kilometers
|
|
11
|
+
* @returns True if coordinates are within threshold
|
|
12
|
+
*/
|
|
13
|
+
export declare function areCoordinatesNear(lat1: number, lng1: number, lat2: number, lng2: number, thresholdKm?: number): boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Calculate distance between two coordinates using Haversine formula
|
|
17
|
+
*
|
|
18
|
+
* @param lat1 - First latitude
|
|
19
|
+
* @param lng1 - First longitude
|
|
20
|
+
* @param lat2 - Second latitude
|
|
21
|
+
* @param lng2 - Second longitude
|
|
22
|
+
* @returns Distance in kilometers
|
|
23
|
+
*/
|
|
24
|
+
export declare function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format coordinates as string
|
|
28
|
+
*
|
|
29
|
+
* @param latitude - Latitude value
|
|
30
|
+
* @param longitude - Longitude value
|
|
31
|
+
* @param precision - Number of decimal places (default: 6)
|
|
32
|
+
* @returns Formatted coordinate string
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatCoordinates(latitude: number, longitude: number, precision?: number): string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate a display name from address components
|
|
38
|
+
*
|
|
39
|
+
* @param components - Address components
|
|
40
|
+
* @returns Formatted display name
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateDisplayName(components: Partial<GeoData>): string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Geographic data structure
|
|
46
|
+
* All fields optional to support both real-world and abstract places
|
|
47
|
+
*/
|
|
48
|
+
declare interface GeoData {
|
|
49
|
+
latitude: number | null;
|
|
50
|
+
longitude: number | null;
|
|
51
|
+
streetNumber?: string;
|
|
52
|
+
streetName?: string;
|
|
53
|
+
city?: string;
|
|
54
|
+
region?: string;
|
|
55
|
+
country?: string;
|
|
56
|
+
postalCode?: string;
|
|
57
|
+
countryCode?: string;
|
|
58
|
+
timezone?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert Location from @happyvertical/geo to GeoData
|
|
63
|
+
*
|
|
64
|
+
* @param location - Location from geocoding
|
|
65
|
+
* @returns GeoData object
|
|
66
|
+
*/
|
|
67
|
+
export declare function locationToGeoData(location: Location): GeoData;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Map Location type from @happyvertical/geo to PlaceType slug
|
|
71
|
+
*
|
|
72
|
+
* @param locationType - Location type from geocoding provider
|
|
73
|
+
* @returns PlaceType slug
|
|
74
|
+
*/
|
|
75
|
+
export declare function mapLocationTypeToPlaceType(locationType: string): string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Normalize address components by trimming and removing empty values
|
|
79
|
+
*
|
|
80
|
+
* @param components - Address components
|
|
81
|
+
* @returns Normalized components
|
|
82
|
+
*/
|
|
83
|
+
export declare function normalizeAddressComponents(components: Partial<GeoData>): Partial<GeoData>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse coordinate string to lat/lng
|
|
87
|
+
*
|
|
88
|
+
* Supports formats:
|
|
89
|
+
* - "lat, lng"
|
|
90
|
+
* - "lat,lng"
|
|
91
|
+
* - "lat lng"
|
|
92
|
+
*
|
|
93
|
+
* @param coordString - Coordinate string
|
|
94
|
+
* @returns Object with lat and lng, or null if invalid
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseCoordinates(coordString: string): {
|
|
97
|
+
lat: number;
|
|
98
|
+
lng: number;
|
|
99
|
+
} | null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validate geographic coordinates
|
|
103
|
+
*
|
|
104
|
+
* @param latitude - Latitude value
|
|
105
|
+
* @param longitude - Longitude value
|
|
106
|
+
* @returns Object with valid flag and optional error message
|
|
107
|
+
*/
|
|
108
|
+
export declare function validateCoordinates(latitude: number, longitude: number): {
|
|
109
|
+
valid: boolean;
|
|
110
|
+
error?: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { }
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
function mapLocationTypeToPlaceType(locationType) {
|
|
2
|
+
const typeMap = {
|
|
3
|
+
// Standard types
|
|
4
|
+
country: "country",
|
|
5
|
+
region: "region",
|
|
6
|
+
city: "city",
|
|
7
|
+
address: "address",
|
|
8
|
+
point_of_interest: "point_of_interest",
|
|
9
|
+
// Additional mappings
|
|
10
|
+
state: "region",
|
|
11
|
+
province: "region",
|
|
12
|
+
town: "city",
|
|
13
|
+
village: "city",
|
|
14
|
+
building: "building",
|
|
15
|
+
room: "room",
|
|
16
|
+
zone: "zone"
|
|
17
|
+
};
|
|
18
|
+
return typeMap[locationType.toLowerCase()] || "address";
|
|
19
|
+
}
|
|
20
|
+
function locationToGeoData(location) {
|
|
21
|
+
const components = location.addressComponents || {};
|
|
22
|
+
return {
|
|
23
|
+
latitude: location.latitude,
|
|
24
|
+
longitude: location.longitude,
|
|
25
|
+
streetNumber: components.streetNumber,
|
|
26
|
+
streetName: components.streetName,
|
|
27
|
+
city: components.city,
|
|
28
|
+
region: components.region,
|
|
29
|
+
country: components.country,
|
|
30
|
+
postalCode: components.postalCode,
|
|
31
|
+
countryCode: location.countryCode,
|
|
32
|
+
timezone: location.timezone
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function validateCoordinates(latitude, longitude) {
|
|
36
|
+
if (latitude < -90 || latitude > 90) {
|
|
37
|
+
return { valid: false, error: "Invalid latitude (must be -90 to 90)" };
|
|
38
|
+
}
|
|
39
|
+
if (longitude < -180 || longitude > 180) {
|
|
40
|
+
return { valid: false, error: "Invalid longitude (must be -180 to 180)" };
|
|
41
|
+
}
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
function calculateDistance(lat1, lng1, lat2, lng2) {
|
|
45
|
+
const R = 6371;
|
|
46
|
+
const dLat = toRad(lat2 - lat1);
|
|
47
|
+
const dLng = toRad(lng2 - lng1);
|
|
48
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
|
49
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
50
|
+
return R * c;
|
|
51
|
+
}
|
|
52
|
+
function toRad(degrees) {
|
|
53
|
+
return degrees * (Math.PI / 180);
|
|
54
|
+
}
|
|
55
|
+
function formatCoordinates(latitude, longitude, precision = 6) {
|
|
56
|
+
return `${latitude.toFixed(precision)}, ${longitude.toFixed(precision)}`;
|
|
57
|
+
}
|
|
58
|
+
function parseCoordinates(coordString) {
|
|
59
|
+
const parts = coordString.trim().replace(/\s+/g, " ").replace(/,\s*/g, ",").split(/[,\s]+/);
|
|
60
|
+
if (parts.length !== 2) return null;
|
|
61
|
+
const lat = parseFloat(parts[0]);
|
|
62
|
+
const lng = parseFloat(parts[1]);
|
|
63
|
+
if (Number.isNaN(lat) || Number.isNaN(lng)) return null;
|
|
64
|
+
const validation = validateCoordinates(lat, lng);
|
|
65
|
+
if (!validation.valid) return null;
|
|
66
|
+
return { lat, lng };
|
|
67
|
+
}
|
|
68
|
+
function normalizeAddressComponents(components) {
|
|
69
|
+
const normalized = {};
|
|
70
|
+
for (const [key, value] of Object.entries(components)) {
|
|
71
|
+
if (value === null || value === void 0) continue;
|
|
72
|
+
if (typeof value === "string") {
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed) {
|
|
75
|
+
normalized[key] = trimmed;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
normalized[key] = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
function generateDisplayName(components) {
|
|
84
|
+
const parts = [];
|
|
85
|
+
if (components.streetNumber && components.streetName) {
|
|
86
|
+
parts.push(`${components.streetNumber} ${components.streetName}`);
|
|
87
|
+
} else if (components.streetName) {
|
|
88
|
+
parts.push(components.streetName);
|
|
89
|
+
}
|
|
90
|
+
if (components.city) parts.push(components.city);
|
|
91
|
+
if (components.region) parts.push(components.region);
|
|
92
|
+
if (components.country) parts.push(components.country);
|
|
93
|
+
return parts.join(", ");
|
|
94
|
+
}
|
|
95
|
+
function areCoordinatesNear(lat1, lng1, lat2, lng2, thresholdKm = 0.1) {
|
|
96
|
+
const distance = calculateDistance(lat1, lng1, lat2, lng2);
|
|
97
|
+
return distance <= thresholdKm;
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
areCoordinatesNear,
|
|
101
|
+
calculateDistance,
|
|
102
|
+
formatCoordinates,
|
|
103
|
+
generateDisplayName,
|
|
104
|
+
locationToGeoData,
|
|
105
|
+
mapLocationTypeToPlaceType,
|
|
106
|
+
normalizeAddressComponents,
|
|
107
|
+
parseCoordinates,
|
|
108
|
+
validateCoordinates
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["/**\n * Utility functions for @have/places package\n */\n\nimport type { Location } from '@happyvertical/geo';\nimport type { GeoData } from './types';\n\n/**\n * Map Location type from @happyvertical/geo to PlaceType slug\n *\n * @param locationType - Location type from geocoding provider\n * @returns PlaceType slug\n */\nexport function mapLocationTypeToPlaceType(locationType: string): string {\n const typeMap: Record<string, string> = {\n // Standard types\n country: 'country',\n region: 'region',\n city: 'city',\n address: 'address',\n point_of_interest: 'point_of_interest',\n\n // Additional mappings\n state: 'region',\n province: 'region',\n town: 'city',\n village: 'city',\n building: 'building',\n room: 'room',\n zone: 'zone',\n };\n\n return typeMap[locationType.toLowerCase()] || 'address';\n}\n\n/**\n * Convert Location from @happyvertical/geo to GeoData\n *\n * @param location - Location from geocoding\n * @returns GeoData object\n */\nexport function locationToGeoData(location: Location): GeoData {\n const components = location.addressComponents || {};\n\n return {\n latitude: location.latitude,\n longitude: location.longitude,\n streetNumber: components.streetNumber,\n streetName: components.streetName,\n city: components.city,\n region: components.region,\n country: components.country,\n postalCode: components.postalCode,\n countryCode: location.countryCode,\n timezone: location.timezone,\n };\n}\n\n/**\n * Validate geographic coordinates\n *\n * @param latitude - Latitude value\n * @param longitude - Longitude value\n * @returns Object with valid flag and optional error message\n */\nexport function validateCoordinates(\n latitude: number,\n longitude: number,\n): { valid: boolean; error?: string } {\n if (latitude < -90 || latitude > 90) {\n return { valid: false, error: 'Invalid latitude (must be -90 to 90)' };\n }\n\n if (longitude < -180 || longitude > 180) {\n return { valid: false, error: 'Invalid longitude (must be -180 to 180)' };\n }\n\n return { valid: true };\n}\n\n/**\n * Calculate distance between two coordinates using Haversine formula\n *\n * @param lat1 - First latitude\n * @param lng1 - First longitude\n * @param lat2 - Second latitude\n * @param lng2 - Second longitude\n * @returns Distance in kilometers\n */\nexport function calculateDistance(\n lat1: number,\n lng1: number,\n lat2: number,\n lng2: number,\n): number {\n const R = 6371; // Earth radius in km\n const dLat = toRad(lat2 - lat1);\n const dLng = toRad(lng2 - lng1);\n\n const a =\n Math.sin(dLat / 2) * Math.sin(dLat / 2) +\n Math.cos(toRad(lat1)) *\n Math.cos(toRad(lat2)) *\n Math.sin(dLng / 2) *\n Math.sin(dLng / 2);\n\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n return R * c;\n}\n\n/**\n * Convert degrees to radians\n */\nfunction toRad(degrees: number): number {\n return degrees * (Math.PI / 180);\n}\n\n/**\n * Format coordinates as string\n *\n * @param latitude - Latitude value\n * @param longitude - Longitude value\n * @param precision - Number of decimal places (default: 6)\n * @returns Formatted coordinate string\n */\nexport function formatCoordinates(\n latitude: number,\n longitude: number,\n precision: number = 6,\n): string {\n return `${latitude.toFixed(precision)}, ${longitude.toFixed(precision)}`;\n}\n\n/**\n * Parse coordinate string to lat/lng\n *\n * Supports formats:\n * - \"lat, lng\"\n * - \"lat,lng\"\n * - \"lat lng\"\n *\n * @param coordString - Coordinate string\n * @returns Object with lat and lng, or null if invalid\n */\nexport function parseCoordinates(\n coordString: string,\n): { lat: number; lng: number } | null {\n // Remove extra whitespace and split\n const parts = coordString\n .trim()\n .replace(/\\s+/g, ' ')\n .replace(/,\\s*/g, ',')\n .split(/[,\\s]+/);\n\n if (parts.length !== 2) return null;\n\n const lat = parseFloat(parts[0]);\n const lng = parseFloat(parts[1]);\n\n if (Number.isNaN(lat) || Number.isNaN(lng)) return null;\n\n const validation = validateCoordinates(lat, lng);\n if (!validation.valid) return null;\n\n return { lat, lng };\n}\n\n/**\n * Normalize address components by trimming and removing empty values\n *\n * @param components - Address components\n * @returns Normalized components\n */\nexport function normalizeAddressComponents(\n components: Partial<GeoData>,\n): Partial<GeoData> {\n const normalized: Partial<GeoData> = {};\n\n for (const [key, value] of Object.entries(components)) {\n if (value === null || value === undefined) continue;\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed) {\n normalized[key as keyof GeoData] = trimmed as any;\n }\n } else {\n normalized[key as keyof GeoData] = value as any;\n }\n }\n\n return normalized;\n}\n\n/**\n * Generate a display name from address components\n *\n * @param components - Address components\n * @returns Formatted display name\n */\nexport function generateDisplayName(components: Partial<GeoData>): string {\n const parts: string[] = [];\n\n if (components.streetNumber && components.streetName) {\n parts.push(`${components.streetNumber} ${components.streetName}`);\n } else if (components.streetName) {\n parts.push(components.streetName);\n }\n\n if (components.city) parts.push(components.city);\n if (components.region) parts.push(components.region);\n if (components.country) parts.push(components.country);\n\n return parts.join(', ');\n}\n\n/**\n * Check if two coordinates are within a threshold distance\n *\n * @param lat1 - First latitude\n * @param lng1 - First longitude\n * @param lat2 - Second latitude\n * @param lng2 - Second longitude\n * @param thresholdKm - Distance threshold in kilometers\n * @returns True if coordinates are within threshold\n */\nexport function areCoordinatesNear(\n lat1: number,\n lng1: number,\n lat2: number,\n lng2: number,\n thresholdKm: number = 0.1,\n): boolean {\n const distance = calculateDistance(lat1, lng1, lat2, lng2);\n return distance <= thresholdKm;\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/smrt-places",
|
|
3
|
+
"version": "0.30.0",
|
|
4
|
+
"description": "Hierarchical place management with geo integration and SMRT framework support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"CLAUDE.md",
|
|
11
|
+
"AGENTS.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./utils": {
|
|
19
|
+
"types": "./dist/utils.d.ts",
|
|
20
|
+
"import": "./dist/utils.js"
|
|
21
|
+
},
|
|
22
|
+
"./manifest": "./dist/manifest.json",
|
|
23
|
+
"./manifest.json": "./dist/manifest.json"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@happyvertical/ai": "^0.74.7",
|
|
27
|
+
"@happyvertical/cache": "^0.74.7",
|
|
28
|
+
"@happyvertical/files": "^0.74.7",
|
|
29
|
+
"@happyvertical/geo": "^0.74.7",
|
|
30
|
+
"@happyvertical/logger": "^0.74.7",
|
|
31
|
+
"@happyvertical/sql": "^0.74.7",
|
|
32
|
+
"@happyvertical/utils": "^0.74.7",
|
|
33
|
+
"@happyvertical/smrt-assets": "0.30.0",
|
|
34
|
+
"@happyvertical/smrt-core": "0.30.0",
|
|
35
|
+
"@happyvertical/smrt-tenancy": "0.30.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "25.0.9",
|
|
39
|
+
"fast-glob": "3.3.3",
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vite": "^7.3.1",
|
|
42
|
+
"vitest": "^4.0.17",
|
|
43
|
+
"@happyvertical/smrt-vitest": "0.30.0"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"ai",
|
|
47
|
+
"agents",
|
|
48
|
+
"places",
|
|
49
|
+
"locations",
|
|
50
|
+
"geocoding",
|
|
51
|
+
"geography",
|
|
52
|
+
"hierarchy",
|
|
53
|
+
"smrt"
|
|
54
|
+
],
|
|
55
|
+
"author": "HappyVertical",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"registry": "https://registry.npmjs.org",
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "https://github.com/happyvertical/smrt.git",
|
|
64
|
+
"directory": "packages/places"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "vite build --mode library",
|
|
68
|
+
"build:fresh": "npm run clean && npm run build",
|
|
69
|
+
"build:watch": "vite build --mode library --watch",
|
|
70
|
+
"clean": "rm -rf dist",
|
|
71
|
+
"dev": "vite dev",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:watch": "vitest",
|
|
74
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
75
|
+
}
|
|
76
|
+
}
|