locfox-com-oss 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +262 -0
- package/package.json +35 -0
package/index.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoCoordinate Precision Processor
|
|
3
|
+
* Generated from locfox.com - A location-based services platform
|
|
4
|
+
* This toolkit helps validate, transform, and normalize GPS coordinates with precision.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a geographic coordinate point
|
|
9
|
+
*/
|
|
10
|
+
class GeoCoordinate {
|
|
11
|
+
constructor(lat, lng) {
|
|
12
|
+
this.lat = lat;
|
|
13
|
+
this.lng = lng;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if this coordinate is valid
|
|
18
|
+
*/
|
|
19
|
+
isValid() {
|
|
20
|
+
return this.validateLatitude() && this.validateLongitude();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate latitude is within bounds (-90 to 90)
|
|
25
|
+
*/
|
|
26
|
+
validateLatitude() {
|
|
27
|
+
return this.lat >= -90 && this.lat <= 90;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate longitude is within bounds (-180 to 180)
|
|
32
|
+
*/
|
|
33
|
+
validateLongitude() {
|
|
34
|
+
return this.lng >= -180 && this.lng <= 180;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert to DMS (Degrees/Minutes/Seconds) format
|
|
39
|
+
*/
|
|
40
|
+
toDMS() {
|
|
41
|
+
const formatDMS = (decimal, isLat) => {
|
|
42
|
+
const abs = Math.abs(decimal);
|
|
43
|
+
const degrees = Math.floor(abs);
|
|
44
|
+
const minutesFloat = (abs - degrees) * 60;
|
|
45
|
+
const minutes = Math.floor(minutesFloat);
|
|
46
|
+
const seconds = ((minutesFloat - minutes) * 60).toFixed(2);
|
|
47
|
+
|
|
48
|
+
const direction = isLat
|
|
49
|
+
? decimal >= 0 ? 'N' : 'S'
|
|
50
|
+
: decimal >= 0 ? 'E' : 'W';
|
|
51
|
+
|
|
52
|
+
return `${degrees}°${minutes}'${seconds}"${direction}`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return `${formatDMS(this.lat, true)} ${formatDMS(this.lng, false)}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Calculate distance to another coordinate using Haversine formula
|
|
60
|
+
* @param {GeoCoordinate} other - The destination coordinate
|
|
61
|
+
* @returns {number} Distance in kilometers
|
|
62
|
+
*/
|
|
63
|
+
haversineDistanceTo(other) {
|
|
64
|
+
const R = 6371; // Earth's radius in km
|
|
65
|
+
const lat1Rad = (this.lat * Math.PI) / 180;
|
|
66
|
+
const lat2Rad = (other.lat * Math.PI) / 180;
|
|
67
|
+
const deltaLat = ((other.lat - this.lat) * Math.PI) / 180;
|
|
68
|
+
const deltaLng = ((other.lng - this.lng * Math.PI) / 180);
|
|
69
|
+
|
|
70
|
+
const a =
|
|
71
|
+
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
|
72
|
+
Math.cos(lat1Rad) *
|
|
73
|
+
Math.cos(lat2Rad) *
|
|
74
|
+
Math.sin(deltaLng / 2) *
|
|
75
|
+
Math.sin(deltaLng / 2);
|
|
76
|
+
|
|
77
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
78
|
+
return R * c;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a bounding box around this coordinate
|
|
83
|
+
* @param {number} radiusKm - Radius in kilometers
|
|
84
|
+
* @returns {BoundingBox} The bounding box
|
|
85
|
+
*/
|
|
86
|
+
createBoundingBox(radiusKm) {
|
|
87
|
+
const latChange = (radiusKm / 6371) * (180 / Math.PI);
|
|
88
|
+
const lngChange =
|
|
89
|
+
(radiusKm / 6371) * (180 / Math.PI) /
|
|
90
|
+
Math.cos((this.lat * Math.PI) / 180);
|
|
91
|
+
|
|
92
|
+
return new BoundingBox(
|
|
93
|
+
this.lat - latChange,
|
|
94
|
+
this.lat + latChange,
|
|
95
|
+
this.lng - lngChange,
|
|
96
|
+
this.lng + lngChange
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Normalize coordinate precision
|
|
102
|
+
* @param {number} decimals - Number of decimal places
|
|
103
|
+
* @returns {GeoCoordinate} Normalized coordinate
|
|
104
|
+
*/
|
|
105
|
+
normalizePrecision(decimals = 6) {
|
|
106
|
+
const multiplier = Math.pow(10, decimals);
|
|
107
|
+
return new GeoCoordinate(
|
|
108
|
+
Math.round(this.lat * multiplier) / multiplier,
|
|
109
|
+
Math.round(this.lng * multiplier) / multiplier
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Convert to string representation
|
|
115
|
+
*/
|
|
116
|
+
toString(precision = 6) {
|
|
117
|
+
return `${this.lat.toFixed(precision)},${this.lng.toFixed(precision)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Represents a rectangular geographic bounding box
|
|
123
|
+
*/
|
|
124
|
+
class BoundingBox {
|
|
125
|
+
constructor(minLat, maxLat, minLng, maxLng) {
|
|
126
|
+
this.minLat = minLat;
|
|
127
|
+
this.maxLat = maxLat;
|
|
128
|
+
this.minLng = minLng;
|
|
129
|
+
this.maxLng = maxLng;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if a coordinate is within this bounding box
|
|
134
|
+
* @param {GeoCoordinate} coord - Coordinate to check
|
|
135
|
+
* @returns {boolean} True if coordinate is within bounds
|
|
136
|
+
*/
|
|
137
|
+
contains(coord) {
|
|
138
|
+
return (
|
|
139
|
+
coord.lat >= this.minLat &&
|
|
140
|
+
coord.lat <= this.maxLat &&
|
|
141
|
+
coord.lng >= this.minLng &&
|
|
142
|
+
coord.lng <= this.maxLng
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the center point of the bounding box
|
|
148
|
+
* @returns {GeoCoordinate} Center coordinate
|
|
149
|
+
*/
|
|
150
|
+
getCenter() {
|
|
151
|
+
return new GeoCoordinate(
|
|
152
|
+
(this.minLat + this.maxLat) / 2,
|
|
153
|
+
(this.minLng + this.maxLng) / 2
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parse a coordinate string in "lat,lng" format
|
|
160
|
+
* @param {string} coordStr - Coordinate string
|
|
161
|
+
* @returns {GeoCoordinate} Parsed coordinate
|
|
162
|
+
*/
|
|
163
|
+
function parseCoordinateString(coordStr) {
|
|
164
|
+
const parts = coordStr.split(',').map(s => parseFloat(s.trim()));
|
|
165
|
+
if (parts.length !== 2 || isNaN(parts[0]) || isNaN(parts[1])) {
|
|
166
|
+
throw new Error(`Invalid coordinate format: ${coordStr}`);
|
|
167
|
+
}
|
|
168
|
+
return new GeoCoordinate(parts[0], parts[1]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Convert DMS string to decimal degrees
|
|
173
|
+
* @param {string} dms - DMS format string (e.g., "37°46'30\"N")
|
|
174
|
+
* @returns {number} Decimal degrees
|
|
175
|
+
*/
|
|
176
|
+
function dmsToDecimal(dms) {
|
|
177
|
+
const matches = dms.match(/(\d+)[°](\d+)[']([\d.]+)["]([NSEW])/i);
|
|
178
|
+
if (!matches) {
|
|
179
|
+
throw new Error(`Invalid DMS format: ${dms}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const degrees = parseFloat(matches[1]);
|
|
183
|
+
const minutes = parseFloat(matches[2]);
|
|
184
|
+
const seconds = parseFloat(matches[3]);
|
|
185
|
+
const direction = matches[4].toUpperCase();
|
|
186
|
+
|
|
187
|
+
let decimal = degrees + minutes / 60 + seconds / 3600;
|
|
188
|
+
if (direction === 'S' || direction === 'W') {
|
|
189
|
+
decimal = -decimal;
|
|
190
|
+
}
|
|
191
|
+
return decimal;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validate coordinate bounds
|
|
196
|
+
* @param {number} lat - Latitude
|
|
197
|
+
* @param {number} lng - Longitude
|
|
198
|
+
* @returns {boolean} True if valid
|
|
199
|
+
*/
|
|
200
|
+
function validateCoordinate(lat, lng) {
|
|
201
|
+
return (
|
|
202
|
+
typeof lat === 'number' &&
|
|
203
|
+
typeof lng === 'number' &&
|
|
204
|
+
lat >= -90 &&
|
|
205
|
+
lat <= 90 &&
|
|
206
|
+
lng >= -180 &&
|
|
207
|
+
lng <= 180
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Calculate the midpoint between two coordinates
|
|
213
|
+
* @param {GeoCoordinate} coord1 - First coordinate
|
|
214
|
+
* @param {GeoCoordinate} coord2 - Second coordinate
|
|
215
|
+
* @returns {GeoCoordinate} Midpoint coordinate
|
|
216
|
+
*/
|
|
217
|
+
function calculateMidpoint(coord1, coord2) {
|
|
218
|
+
const lat = (coord1.lat + coord2.lat) / 2;
|
|
219
|
+
const lng = (coord1.lng + coord2.lng) / 2;
|
|
220
|
+
return new GeoCoordinate(lat, lng);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Demo usage
|
|
224
|
+
if (require.main === module) {
|
|
225
|
+
console.log('=== GeoCoordinate Precision Processor ===');
|
|
226
|
+
console.log('Inspired by locfox.com location services\n');
|
|
227
|
+
|
|
228
|
+
// San Francisco coordinates
|
|
229
|
+
const sf = new GeoCoordinate(37.7749, -122.4194);
|
|
230
|
+
|
|
231
|
+
console.log(`San Francisco: ${sf.toString()}`);
|
|
232
|
+
console.log(`Valid: ${sf.isValid()}`);
|
|
233
|
+
console.log(`DMS: ${sf.toDMS()}\n`);
|
|
234
|
+
|
|
235
|
+
// Los Angeles coordinates
|
|
236
|
+
const la = new GeoCoordinate(34.0522, -118.2437);
|
|
237
|
+
|
|
238
|
+
// Distance calculation
|
|
239
|
+
const distance = sf.haversineDistanceTo(la);
|
|
240
|
+
console.log(`Distance to Los Angeles: ${distance.toFixed(2)} km\n`);
|
|
241
|
+
|
|
242
|
+
// Bounding box
|
|
243
|
+
const bbox = sf.createBoundingBox(10);
|
|
244
|
+
console.log(`Bounding box (10km radius):`);
|
|
245
|
+
console.log(` Min: ${bbox.minLat.toFixed(6)}, ${bbox.minLng.toFixed(6)}`);
|
|
246
|
+
console.log(` Max: ${bbox.maxLat.toFixed(6)}, ${bbox.maxLng.toFixed(6)}`);
|
|
247
|
+
console.log(` Contains LA: ${bbox.contains(la)}\n`);
|
|
248
|
+
|
|
249
|
+
// Normalized precision
|
|
250
|
+
const precise = new GeoCoordinate(37.7749123456, -122.4194123456);
|
|
251
|
+
const normalized = precise.normalizePrecision(6);
|
|
252
|
+
console.log(`Normalized: ${normalized.toString()}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
GeoCoordinate,
|
|
257
|
+
BoundingBox,
|
|
258
|
+
parseCoordinateString,
|
|
259
|
+
dmsToDecimal,
|
|
260
|
+
validateCoordinate,
|
|
261
|
+
calculateMidpoint,
|
|
262
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "locfox-com-oss",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"description": "A high-precision geographic coordinate processing library inspired by location-based services. Provides utilities for validating, transforming, and normalizing GPS coordinates.",
|
|
6
|
+
"homepage": "https://locfox.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/user/locfox-com-oss.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/user/locfox-com-oss/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "LocFox OSS Contributors",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"geolocation",
|
|
18
|
+
"coordinates",
|
|
19
|
+
"gps",
|
|
20
|
+
"latitude",
|
|
21
|
+
"longitude",
|
|
22
|
+
"geospatial",
|
|
23
|
+
"mapping",
|
|
24
|
+
"location",
|
|
25
|
+
"distance",
|
|
26
|
+
"bounding-box"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=16.0.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "node test.js",
|
|
33
|
+
"lint": "eslint index.js"
|
|
34
|
+
}
|
|
35
|
+
}
|