@ue-too/border 0.9.4 → 0.10.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/README.md +484 -4
- package/greateCircle.d.ts +101 -0
- package/index.d.ts +117 -0
- package/index.js +2 -172
- package/index.js.map +5 -5
- package/package.json +2 -2
- package/projection.d.ts +113 -0
- package/rhumbLine.d.ts +104 -0
package/README.md
CHANGED
|
@@ -1,6 +1,486 @@
|
|
|
1
|
-
# border
|
|
1
|
+
# @ue-too/border
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Following the tutorial from [Chris Veness](https://www.movable-type.co.uk/scripts/latlong.html).
|
|
3
|
+
Geodesy and map projection library for TypeScript.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@ue-too/border)
|
|
6
|
+
[](https://github.com/ue-too/ue-too/blob/main/LICENSE.txt)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`@ue-too/border` provides geodesy calculations and map projections for working with geographic coordinates on the Earth's surface. Based on algorithms from [Movable Type Scripts](https://www.movable-type.co.uk/scripts/latlong.html) by Chris Veness, this library includes utilities for great circle navigation, rhumb line navigation, and coordinate transformations.
|
|
11
|
+
|
|
12
|
+
### Key Features
|
|
13
|
+
|
|
14
|
+
- **Great Circle Navigation**: Shortest path calculations on a sphere
|
|
15
|
+
- **Rhumb Line Navigation**: Constant bearing paths for easier navigation
|
|
16
|
+
- **Map Projections**: Mercator and orthographic projections with inverse transformations
|
|
17
|
+
- **Distance Calculations**: Accurate distances between geographic coordinates
|
|
18
|
+
- **Bearing Calculations**: Initial and constant bearings for navigation
|
|
19
|
+
- **Intermediate Points**: Find points along paths
|
|
20
|
+
- **Midpoint Calculation**: Find midpoints between coordinates
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Using Bun:
|
|
25
|
+
```bash
|
|
26
|
+
bun add @ue-too/border
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Using npm:
|
|
30
|
+
```bash
|
|
31
|
+
npm install @ue-too/border
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
Here's a simple example calculating distance and bearing between two cities:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
greatCircleDistance,
|
|
41
|
+
initialBearingOfGreatCircle
|
|
42
|
+
} from '@ue-too/border';
|
|
43
|
+
|
|
44
|
+
// New York to London
|
|
45
|
+
const nyc = { latitude: 40.7128, longitude: -74.0060 };
|
|
46
|
+
const london = { latitude: 51.5074, longitude: -0.1278 };
|
|
47
|
+
|
|
48
|
+
// Calculate great circle distance (in meters)
|
|
49
|
+
const distance = greatCircleDistance(nyc, london);
|
|
50
|
+
console.log('Distance:', (distance / 1000).toFixed(0), 'km'); // ~5570 km
|
|
51
|
+
|
|
52
|
+
// Calculate initial bearing (in degrees)
|
|
53
|
+
const bearing = initialBearingOfGreatCircle(nyc, london);
|
|
54
|
+
console.log('Bearing:', bearing.toFixed(1), '°'); // ~51.4° (northeast)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Core Concepts
|
|
58
|
+
|
|
59
|
+
### Great Circles
|
|
60
|
+
|
|
61
|
+
A great circle is the shortest path between two points on a sphere (like the Earth). Airlines typically fly great circle routes to minimize distance.
|
|
62
|
+
|
|
63
|
+
- **Advantages**: Shortest distance, fuel-efficient
|
|
64
|
+
- **Characteristics**: Bearing changes continuously along the path (except when traveling due north/south or along the equator)
|
|
65
|
+
|
|
66
|
+
### Rhumb Lines
|
|
67
|
+
|
|
68
|
+
A rhumb line (loxodrome) is a path of constant bearing. While slightly longer than great circles, they're easier to navigate.
|
|
69
|
+
|
|
70
|
+
- **Advantages**: Constant bearing, simpler navigation
|
|
71
|
+
- **Characteristics**: Spirals toward poles, longer distance than great circles (except along equator or meridians)
|
|
72
|
+
|
|
73
|
+
### Map Projections
|
|
74
|
+
|
|
75
|
+
Map projections transform spherical coordinates (latitude/longitude) to flat 2D coordinates for display.
|
|
76
|
+
|
|
77
|
+
- **Mercator**: Preserves angles, distorts size at high latitudes
|
|
78
|
+
- **Orthographic**: Perspective view from space, shows one hemisphere
|
|
79
|
+
|
|
80
|
+
## Core APIs
|
|
81
|
+
|
|
82
|
+
### Great Circle Functions
|
|
83
|
+
|
|
84
|
+
#### `greatCircleDistance(from, to)`
|
|
85
|
+
|
|
86
|
+
Calculate the great circle distance between two points.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
function greatCircleDistance(
|
|
90
|
+
from: GeoCoord,
|
|
91
|
+
to: GeoCoord
|
|
92
|
+
): number; // Returns distance in meters
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Example:**
|
|
96
|
+
```typescript
|
|
97
|
+
const distance = greatCircleDistance(
|
|
98
|
+
{ latitude: 51.5074, longitude: -0.1278 }, // London
|
|
99
|
+
{ latitude: 48.8566, longitude: 2.3522 } // Paris
|
|
100
|
+
);
|
|
101
|
+
console.log(distance / 1000, 'km'); // ~344 km
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `initialBearingOfGreatCircle(from, to)`
|
|
105
|
+
|
|
106
|
+
Calculate the initial bearing (direction) for a great circle path.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
function initialBearingOfGreatCircle(
|
|
110
|
+
from: GeoCoord,
|
|
111
|
+
to: GeoCoord
|
|
112
|
+
): number; // Returns bearing in degrees (0-360)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
```typescript
|
|
117
|
+
const bearing = initialBearingOfGreatCircle(
|
|
118
|
+
{ latitude: 40.7128, longitude: -74.0060 }, // NYC
|
|
119
|
+
{ latitude: 51.5074, longitude: -0.1278 } // London
|
|
120
|
+
);
|
|
121
|
+
console.log(bearing, '°'); // ~51.4° (northeast)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### `destinationFromOriginOnGreatCircle(origin, bearing, distance)`
|
|
125
|
+
|
|
126
|
+
Find the destination point given origin, bearing, and distance.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
function destinationFromOriginOnGreatCircle(
|
|
130
|
+
origin: GeoCoord,
|
|
131
|
+
bearing: number, // Degrees
|
|
132
|
+
distance: number // Meters
|
|
133
|
+
): GeoCoord;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Example:**
|
|
137
|
+
```typescript
|
|
138
|
+
const start = { latitude: 51.5074, longitude: -0.1278 };
|
|
139
|
+
const destination = destinationFromOriginOnGreatCircle(start, 90, 100000);
|
|
140
|
+
console.log('100km east:', destination);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### `midpointOnGreatCircle(from, to)`
|
|
144
|
+
|
|
145
|
+
Find the midpoint along a great circle path.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
function midpointOnGreatCircle(
|
|
149
|
+
from: GeoCoord,
|
|
150
|
+
to: GeoCoord
|
|
151
|
+
): GeoCoord;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `intermediatePointOnGreatCircle(from, to, fraction)`
|
|
155
|
+
|
|
156
|
+
Find a point at a given fraction along the great circle path.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
function intermediatePointOnGreatCircle(
|
|
160
|
+
from: GeoCoord,
|
|
161
|
+
to: GeoCoord,
|
|
162
|
+
fraction: number // 0.0 to 1.0
|
|
163
|
+
): GeoCoord;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Example:**
|
|
167
|
+
```typescript
|
|
168
|
+
// Find the point 25% of the way from NYC to London
|
|
169
|
+
const point = intermediatePointOnGreatCircle(nyc, london, 0.25);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Rhumb Line Functions
|
|
173
|
+
|
|
174
|
+
#### `rhumbDistance(from, to)`
|
|
175
|
+
|
|
176
|
+
Calculate the rhumb line distance (constant bearing path).
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
function rhumbDistance(
|
|
180
|
+
from: GeoCoord,
|
|
181
|
+
to: GeoCoord
|
|
182
|
+
): number; // Returns distance in meters
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### `rhumbBearing(from, to)`
|
|
186
|
+
|
|
187
|
+
Calculate the constant bearing for a rhumb line.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
function rhumbBearing(
|
|
191
|
+
from: GeoCoord,
|
|
192
|
+
to: GeoCoord
|
|
193
|
+
): number; // Returns bearing in degrees (0-360)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### `destinationFromOriginOnRhumbLine(origin, bearing, distance)`
|
|
197
|
+
|
|
198
|
+
Find the destination on a rhumb line given origin, bearing, and distance.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
function destinationFromOriginOnRhumbLine(
|
|
202
|
+
origin: GeoCoord,
|
|
203
|
+
bearing: number, // Degrees
|
|
204
|
+
distance: number // Meters
|
|
205
|
+
): GeoCoord;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### `midpointOnRhumbLine(from, to)`
|
|
209
|
+
|
|
210
|
+
Find the midpoint along a rhumb line.
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
function midpointOnRhumbLine(
|
|
214
|
+
from: GeoCoord,
|
|
215
|
+
to: GeoCoord
|
|
216
|
+
): GeoCoord;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Map Projection Functions
|
|
220
|
+
|
|
221
|
+
#### `mercatorProjection(coord)`
|
|
222
|
+
|
|
223
|
+
Convert geographic coordinates to Mercator projection.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
function mercatorProjection(
|
|
227
|
+
coord: GeoCoord
|
|
228
|
+
): Point; // Returns {x, y} in normalized coordinates
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Example:**
|
|
232
|
+
```typescript
|
|
233
|
+
const point = mercatorProjection({ latitude: 51.5074, longitude: -0.1278 });
|
|
234
|
+
console.log('Mercator coordinates:', point);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `inverseMercatorProjection(point)`
|
|
238
|
+
|
|
239
|
+
Convert Mercator coordinates back to geographic.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
function inverseMercatorProjection(
|
|
243
|
+
point: Point
|
|
244
|
+
): GeoCoord; // Returns {latitude, longitude}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `orthoProjection(coord, origin)`
|
|
248
|
+
|
|
249
|
+
Convert geographic coordinates to orthographic projection (hemisphere view).
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
function orthoProjection(
|
|
253
|
+
coord: GeoCoord,
|
|
254
|
+
origin: GeoCoord // Center of projection
|
|
255
|
+
): {
|
|
256
|
+
coord: Point; // Projected point
|
|
257
|
+
clipped: boolean; // True if coord is on the back hemisphere
|
|
258
|
+
};
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Example:**
|
|
262
|
+
```typescript
|
|
263
|
+
// Project London as seen from the North Pole
|
|
264
|
+
const result = orthoProjection(
|
|
265
|
+
{ latitude: 51.5074, longitude: -0.1278 },
|
|
266
|
+
{ latitude: 90, longitude: 0 }
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
if (!result.clipped) {
|
|
270
|
+
console.log('Visible at:', result.coord);
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Type Definitions
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
type GeoCoord = {
|
|
278
|
+
latitude: number; // Degrees, -90 to 90
|
|
279
|
+
longitude: number; // Degrees, -180 to 180
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
type Point = {
|
|
283
|
+
x: number;
|
|
284
|
+
y: number;
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Common Use Cases
|
|
289
|
+
|
|
290
|
+
### Calculate Flight Distance and Route
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import {
|
|
294
|
+
greatCircleDistance,
|
|
295
|
+
initialBearingOfGreatCircle,
|
|
296
|
+
intermediatePointOnGreatCircle
|
|
297
|
+
} from '@ue-too/border';
|
|
298
|
+
|
|
299
|
+
const departure = { latitude: 40.6413, longitude: -73.7781 }; // JFK
|
|
300
|
+
const arrival = { latitude: 51.4700, longitude: -0.4543 }; // LHR
|
|
301
|
+
|
|
302
|
+
// Total distance
|
|
303
|
+
const distance = greatCircleDistance(departure, arrival);
|
|
304
|
+
console.log('Flight distance:', (distance / 1000).toFixed(0), 'km');
|
|
305
|
+
|
|
306
|
+
// Initial heading
|
|
307
|
+
const heading = initialBearingOfGreatCircle(departure, arrival);
|
|
308
|
+
console.log('Initial heading:', heading.toFixed(1), '°');
|
|
309
|
+
|
|
310
|
+
// Waypoint halfway through
|
|
311
|
+
const midpoint = intermediatePointOnGreatCircle(departure, arrival, 0.5);
|
|
312
|
+
console.log('Midpoint:', midpoint);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Display Map with Mercator Projection
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { mercatorProjection } from '@ue-too/border';
|
|
319
|
+
|
|
320
|
+
const cities = [
|
|
321
|
+
{ name: 'New York', latitude: 40.7128, longitude: -74.0060 },
|
|
322
|
+
{ name: 'London', latitude: 51.5074, longitude: -0.1278 },
|
|
323
|
+
{ name: 'Tokyo', latitude: 35.6762, longitude: 139.6503 }
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
// Project to screen coordinates
|
|
327
|
+
cities.forEach(city => {
|
|
328
|
+
const point = mercatorProjection(city);
|
|
329
|
+
|
|
330
|
+
// Scale to canvas (assuming 1000x600 canvas)
|
|
331
|
+
const x = (point.x + 180) / 360 * 1000;
|
|
332
|
+
const y = (1 - (point.y + 90) / 180) * 600;
|
|
333
|
+
|
|
334
|
+
drawCity(x, y, city.name);
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Navigate with Constant Bearing
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import {
|
|
342
|
+
rhumbBearing,
|
|
343
|
+
destinationFromOriginOnRhumbLine
|
|
344
|
+
} from '@ue-too/border';
|
|
345
|
+
|
|
346
|
+
const start = { latitude: 50.0, longitude: -5.0 };
|
|
347
|
+
const target = { latitude: 58.0, longitude: 3.0 };
|
|
348
|
+
|
|
349
|
+
// Get constant bearing to maintain
|
|
350
|
+
const bearing = rhumbBearing(start, target);
|
|
351
|
+
console.log('Sail on bearing:', bearing.toFixed(1), '°');
|
|
352
|
+
|
|
353
|
+
// Calculate positions every 50 nautical miles
|
|
354
|
+
const nauticalMileInMeters = 1852;
|
|
355
|
+
let currentPos = start;
|
|
356
|
+
|
|
357
|
+
for (let i = 1; i <= 10; i++) {
|
|
358
|
+
currentPos = destinationFromOriginOnRhumbLine(
|
|
359
|
+
currentPos,
|
|
360
|
+
bearing,
|
|
361
|
+
50 * nauticalMileInMeters
|
|
362
|
+
);
|
|
363
|
+
console.log(`Waypoint ${i}:`, currentPos);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Find Nearest City
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { greatCircleDistance } from '@ue-too/border';
|
|
371
|
+
|
|
372
|
+
const userLocation = { latitude: 48.8566, longitude: 2.3522 }; // Paris
|
|
373
|
+
|
|
374
|
+
const cities = [
|
|
375
|
+
{ name: 'London', coord: { latitude: 51.5074, longitude: -0.1278 } },
|
|
376
|
+
{ name: 'Berlin', coord: { latitude: 52.5200, longitude: 13.4050 } },
|
|
377
|
+
{ name: 'Madrid', coord: { latitude: 40.4168, longitude: -3.7038 } },
|
|
378
|
+
{ name: 'Rome', coord: { latitude: 41.9028, longitude: 12.4964 } }
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
const distances = cities.map(city => ({
|
|
382
|
+
...city,
|
|
383
|
+
distance: greatCircleDistance(userLocation, city.coord)
|
|
384
|
+
}));
|
|
385
|
+
|
|
386
|
+
distances.sort((a, b) => a.distance - b.distance);
|
|
387
|
+
console.log('Nearest city:', distances[0].name,
|
|
388
|
+
'- ', (distances[0].distance / 1000).toFixed(0), 'km');
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Globe Visualization with Orthographic Projection
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import { orthoProjection } from '@ue-too/border';
|
|
395
|
+
|
|
396
|
+
// View centered on Europe
|
|
397
|
+
const viewCenter = { latitude: 50, longitude: 10 };
|
|
398
|
+
|
|
399
|
+
const cities = [
|
|
400
|
+
{ name: 'London', coord: { latitude: 51.5074, longitude: -0.1278 } },
|
|
401
|
+
{ name: 'Paris', coord: { latitude: 48.8566, longitude: 2.3522 } },
|
|
402
|
+
{ name: 'Berlin', coord: { latitude: 52.5200, longitude: 13.4050 } }
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
cities.forEach(city => {
|
|
406
|
+
const result = orthoProjection(city.coord, viewCenter);
|
|
407
|
+
|
|
408
|
+
if (!result.clipped) {
|
|
409
|
+
// City is visible on this hemisphere
|
|
410
|
+
const screenX = result.coord.x * 400 + 400; // Scale to canvas
|
|
411
|
+
const screenY = result.coord.y * 400 + 400;
|
|
412
|
+
|
|
413
|
+
drawCityOnGlobe(screenX, screenY, city.name);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## API Reference
|
|
419
|
+
|
|
420
|
+
For complete API documentation with detailed type information, see the [TypeDoc-generated documentation](../../docs/border).
|
|
421
|
+
|
|
422
|
+
## TypeScript Support
|
|
423
|
+
|
|
424
|
+
This package is written in TypeScript with complete type definitions:
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { GeoCoord, Point, greatCircleDistance } from '@ue-too/border';
|
|
428
|
+
|
|
429
|
+
// Coordinates are fully typed
|
|
430
|
+
const coord: GeoCoord = { latitude: 51.5074, longitude: -0.1278 };
|
|
431
|
+
|
|
432
|
+
// Function signatures are type-safe
|
|
433
|
+
const distance: number = greatCircleDistance(coord1, coord2);
|
|
434
|
+
|
|
435
|
+
// Projection results are typed
|
|
436
|
+
const point: Point = mercatorProjection(coord);
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Design Philosophy
|
|
440
|
+
|
|
441
|
+
This library follows these principles:
|
|
442
|
+
|
|
443
|
+
- **Accuracy**: Uses proven geodesy algorithms from academic sources
|
|
444
|
+
- **Simplicity**: Clean, focused API for common geodesy tasks
|
|
445
|
+
- **Type Safety**: Full TypeScript type definitions
|
|
446
|
+
- **Performance**: Efficient calculations suitable for real-time applications
|
|
447
|
+
- **Practicality**: Focused on real-world mapping and navigation use cases
|
|
448
|
+
|
|
449
|
+
## Performance Considerations
|
|
450
|
+
|
|
451
|
+
- **Distance calculations**: O(1) - simple trigonometric calculations
|
|
452
|
+
- **Projections**: O(1) - direct mathematical transformations
|
|
453
|
+
- **Intermediate points**: O(1) - no iteration required
|
|
454
|
+
|
|
455
|
+
**Performance Tips:**
|
|
456
|
+
- Cache distance and bearing calculations if coordinates don't change
|
|
457
|
+
- For many points, batch projection calculations
|
|
458
|
+
- Use appropriate projection for your use case (Mercator for general mapping, orthographic for globes)
|
|
459
|
+
|
|
460
|
+
## Limitations
|
|
461
|
+
|
|
462
|
+
- **Earth model**: Assumes spherical Earth (not ellipsoidal) - sufficient for most applications but less accurate for high-precision surveying
|
|
463
|
+
- **Coordinate range**: Latitude must be in [-90, 90], longitude in [-180, 180]
|
|
464
|
+
- **Polar regions**: Some calculations may be less accurate near poles
|
|
465
|
+
- **Mercator distortion**: Mercator projection heavily distorts areas near poles
|
|
466
|
+
|
|
467
|
+
## Related Packages
|
|
468
|
+
|
|
469
|
+
- **[@ue-too/math](../math)**: Vector operations for point calculations
|
|
470
|
+
- **[@ue-too/curve](../curve)**: Bezier curves for drawing map features
|
|
471
|
+
- **[@ue-too/board](../board)**: Canvas board for rendering maps
|
|
472
|
+
|
|
473
|
+
## Further Reading
|
|
474
|
+
|
|
475
|
+
- [Movable Type Scripts](https://www.movable-type.co.uk/scripts/latlong.html) - Comprehensive geodesy reference
|
|
476
|
+
- [Map Projections](https://en.wikipedia.org/wiki/Map_projection) - Understanding different projections
|
|
477
|
+
- [Great Circle Navigation](https://en.wikipedia.org/wiki/Great-circle_navigation) - Theory and applications
|
|
478
|
+
- [Rhumb Line](https://en.wikipedia.org/wiki/Rhumb_line) - Constant bearing navigation
|
|
479
|
+
|
|
480
|
+
## License
|
|
481
|
+
|
|
482
|
+
MIT
|
|
483
|
+
|
|
484
|
+
## Repository
|
|
485
|
+
|
|
486
|
+
[https://github.com/ue-too/ue-too](https://github.com/ue-too/ue-too)
|
package/greateCircle.d.ts
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
import { GeoCoord } from "./projection";
|
|
2
|
+
/**
|
|
3
|
+
* Calculates an intermediate point along a great circle path.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Given two points on Earth's surface, this finds a point at a specified
|
|
7
|
+
* fraction along the great circle (shortest) path between them.
|
|
8
|
+
*
|
|
9
|
+
* Uses the spherical interpolation formula for accurate results on a sphere.
|
|
10
|
+
*
|
|
11
|
+
* @param startCoord - The starting geographic coordinate
|
|
12
|
+
* @param endCoord - The ending geographic coordinate
|
|
13
|
+
* @param fraction - The fraction along the path (0 = start, 1 = end, 0.5 = midpoint)
|
|
14
|
+
* @returns The intermediate point at the specified fraction
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const nyc = { latitude: 40.7128, longitude: -74.0060 };
|
|
19
|
+
* const london = { latitude: 51.5074, longitude: -0.1278 };
|
|
20
|
+
*
|
|
21
|
+
* // Find point 25% of the way from NYC to London
|
|
22
|
+
* const quarter = intermediatePointOnGreatCircle(nyc, london, 0.25);
|
|
23
|
+
*
|
|
24
|
+
* // Find point 75% of the way
|
|
25
|
+
* const threeQuarters = intermediatePointOnGreatCircle(nyc, london, 0.75);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @category Great Circle
|
|
29
|
+
*/
|
|
2
30
|
export declare function intermediatePointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord, fraction: number): GeoCoord;
|
|
31
|
+
/**
|
|
32
|
+
* Calculates the midpoint along a great circle path.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* This is a specialized, optimized version of {@link intermediatePointOnGreatCircle}
|
|
36
|
+
* for finding the exact midpoint (fraction = 0.5).
|
|
37
|
+
*
|
|
38
|
+
* @param startCoord - The starting geographic coordinate
|
|
39
|
+
* @param endCoord - The ending geographic coordinate
|
|
40
|
+
* @returns The midpoint on the great circle path
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const start = { latitude: 50.0, longitude: -5.0 };
|
|
45
|
+
* const end = { latitude: 58.0, longitude: 3.0 };
|
|
46
|
+
* const mid = midPointOnGreatCircle(start, end);
|
|
47
|
+
* console.log('Midpoint:', mid);
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @category Great Circle
|
|
51
|
+
*/
|
|
3
52
|
export declare function midPointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord;
|
|
4
53
|
/**
|
|
5
54
|
* Calculate the initial bearing between two points on the Earth's surface.
|
|
@@ -10,5 +59,57 @@ export declare function midPointOnGreatCircle(startCoord: GeoCoord, endCoord: Ge
|
|
|
10
59
|
* @returns The bearing in degrees.
|
|
11
60
|
*/
|
|
12
61
|
export declare function initialBearingOfGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): number;
|
|
62
|
+
/**
|
|
63
|
+
* Calculates the great circle distance between two points on Earth.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* Uses the haversine formula to calculate the shortest distance over Earth's
|
|
67
|
+
* surface between two geographic coordinates. This is the "as-the-crow-flies"
|
|
68
|
+
* distance.
|
|
69
|
+
*
|
|
70
|
+
* The calculation assumes Earth's mean radius of 6,371,000 meters and treats
|
|
71
|
+
* Earth as a perfect sphere.
|
|
72
|
+
*
|
|
73
|
+
* @param startCoord - The starting geographic coordinate
|
|
74
|
+
* @param endCoord - The ending geographic coordinate
|
|
75
|
+
* @returns The distance in meters
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const nyc = { latitude: 40.7128, longitude: -74.0060 };
|
|
80
|
+
* const london = { latitude: 51.5074, longitude: -0.1278 };
|
|
81
|
+
*
|
|
82
|
+
* const distance = greatCircleDistance(nyc, london);
|
|
83
|
+
* console.log('Distance:', distance / 1000, 'km'); // ~5570 km
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @category Great Circle
|
|
87
|
+
*/
|
|
13
88
|
export declare function greatCircleDistance(startCoord: GeoCoord, endCoord: GeoCoord): number;
|
|
89
|
+
/**
|
|
90
|
+
* Calculates the destination point given a start point, bearing, and distance on a great circle.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Starting from a given point and traveling along a great circle at a specific
|
|
94
|
+
* initial bearing for a given distance, this calculates where you'll end up.
|
|
95
|
+
*
|
|
96
|
+
* Note: The bearing will change along the path (except when traveling due north/south
|
|
97
|
+
* or along the equator) because great circles are not straight lines on most map projections.
|
|
98
|
+
*
|
|
99
|
+
* @param startCoord - The starting geographic coordinate
|
|
100
|
+
* @param bearing - The initial bearing in degrees (0 = north, 90 = east, etc.)
|
|
101
|
+
* @param distance - The distance to travel in meters
|
|
102
|
+
* @returns The destination coordinate
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const start = { latitude: 40.7128, longitude: -74.0060 }; // NYC
|
|
107
|
+
*
|
|
108
|
+
* // Travel 1000km northeast from NYC
|
|
109
|
+
* const destination = destinationFromOriginOnGreatCircle(start, 45, 1000000);
|
|
110
|
+
* console.log('Destination:', destination);
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @category Great Circle
|
|
114
|
+
*/
|
|
14
115
|
export declare function destinationFromOriginOnGreatCircle(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord;
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Geodesy and map projection library for TypeScript.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The `@ue-too/border` package provides geodesy calculations and map projections
|
|
7
|
+
* for working with geographic coordinates on the Earth's surface. It includes utilities
|
|
8
|
+
* for great circle navigation, rhumb line navigation, and coordinate transformations.
|
|
9
|
+
*
|
|
10
|
+
* ## Core Concepts
|
|
11
|
+
*
|
|
12
|
+
* - **Great Circles**: Shortest path between two points on a sphere (like flight routes)
|
|
13
|
+
* - **Rhumb Lines**: Constant bearing paths (easier for navigation, slightly longer)
|
|
14
|
+
* - **Map Projections**: Converting geographic coordinates to 2D points and vice versa
|
|
15
|
+
*
|
|
16
|
+
* ## Key Features
|
|
17
|
+
*
|
|
18
|
+
* ### Great Circle Navigation
|
|
19
|
+
* - Calculate distance between two geographic points
|
|
20
|
+
* - Find initial bearing for great circle path
|
|
21
|
+
* - Calculate intermediate points along great circle
|
|
22
|
+
* - Find destination given origin, bearing, and distance
|
|
23
|
+
* - Calculate midpoint on great circle
|
|
24
|
+
*
|
|
25
|
+
* ### Rhumb Line Navigation
|
|
26
|
+
* - Calculate rhumb line distance (constant bearing path)
|
|
27
|
+
* - Find bearing for rhumb line
|
|
28
|
+
* - Calculate destination on rhumb line
|
|
29
|
+
* - Find midpoint on rhumb line
|
|
30
|
+
*
|
|
31
|
+
* ### Map Projections
|
|
32
|
+
* - **Mercator Projection**: Cylindrical map projection preserving angles
|
|
33
|
+
* - **Orthographic Projection**: Perspective view of hemisphere from space
|
|
34
|
+
* - Inverse projections for converting back to geographic coordinates
|
|
35
|
+
*
|
|
36
|
+
* ## Main Exports
|
|
37
|
+
*
|
|
38
|
+
* - {@link GeoCoord} - Geographic coordinate type (latitude/longitude)
|
|
39
|
+
* - {@link mercatorProjection} - Convert geo coordinates to Mercator projection
|
|
40
|
+
* - {@link orthoProjection} - Convert geo coordinates to orthographic projection
|
|
41
|
+
* - {@link greatCircleDistance} - Calculate great circle distance
|
|
42
|
+
* - {@link initialBearingOfGreatCircle} - Calculate initial bearing
|
|
43
|
+
* - {@link rhumbDistance} - Calculate rhumb line distance
|
|
44
|
+
* - {@link rhumbBearing} - Calculate rhumb line bearing
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* Great circle navigation
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import {
|
|
50
|
+
* greatCircleDistance,
|
|
51
|
+
* initialBearingOfGreatCircle,
|
|
52
|
+
* destinationFromOriginOnGreatCircle
|
|
53
|
+
* } from '@ue-too/border';
|
|
54
|
+
*
|
|
55
|
+
* // New York to London
|
|
56
|
+
* const nyc = { latitude: 40.7128, longitude: -74.0060 };
|
|
57
|
+
* const london = { latitude: 51.5074, longitude: -0.1278 };
|
|
58
|
+
*
|
|
59
|
+
* // Calculate distance in meters
|
|
60
|
+
* const distance = greatCircleDistance(nyc, london);
|
|
61
|
+
* console.log('Distance:', distance / 1000, 'km'); // ~5570 km
|
|
62
|
+
*
|
|
63
|
+
* // Calculate initial bearing
|
|
64
|
+
* const bearing = initialBearingOfGreatCircle(nyc, london);
|
|
65
|
+
* console.log('Bearing:', bearing, 'degrees'); // ~51 degrees (northeast)
|
|
66
|
+
*
|
|
67
|
+
* // Find point 1000km along the path
|
|
68
|
+
* const intermediate = destinationFromOriginOnGreatCircle(nyc, bearing, 1000000);
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* Rhumb line navigation
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { rhumbDistance, rhumbBearing, destinationFromOriginOnRhumbLine } from '@ue-too/border';
|
|
75
|
+
*
|
|
76
|
+
* const start = { latitude: 50.0, longitude: -5.0 };
|
|
77
|
+
* const end = { latitude: 58.0, longitude: 3.0 };
|
|
78
|
+
*
|
|
79
|
+
* // Calculate rhumb line distance
|
|
80
|
+
* const dist = rhumbDistance(start, end);
|
|
81
|
+
*
|
|
82
|
+
* // Calculate constant bearing
|
|
83
|
+
* const bearing = rhumbBearing(start, end);
|
|
84
|
+
*
|
|
85
|
+
* // Navigate 100km on constant bearing
|
|
86
|
+
* const destination = destinationFromOriginOnRhumbLine(start, bearing, 100000);
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* Map projections
|
|
91
|
+
* ```typescript
|
|
92
|
+
* import {
|
|
93
|
+
* mercatorProjection,
|
|
94
|
+
* inverseMercatorProjection,
|
|
95
|
+
* orthoProjection
|
|
96
|
+
* } from '@ue-too/border';
|
|
97
|
+
*
|
|
98
|
+
* // Convert geographic to Mercator
|
|
99
|
+
* const coord = { latitude: 51.5074, longitude: -0.1278 };
|
|
100
|
+
* const point = mercatorProjection(coord);
|
|
101
|
+
* console.log('Mercator point:', point); // {x: ..., y: ...}
|
|
102
|
+
*
|
|
103
|
+
* // Convert back to geographic
|
|
104
|
+
* const geoCoord = inverseMercatorProjection(point);
|
|
105
|
+
*
|
|
106
|
+
* // Orthographic projection (hemisphere view)
|
|
107
|
+
* const origin = { latitude: 45.0, longitude: 0.0 };
|
|
108
|
+
* const result = orthoProjection(coord, origin);
|
|
109
|
+
* if (!result.clipped) {
|
|
110
|
+
* console.log('Visible at:', result.coord);
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @see {@link projection} for map projection functions
|
|
115
|
+
* @see {@link greateCircle} for great circle navigation
|
|
116
|
+
* @see {@link rhumbLine} for rhumb line navigation
|
|
117
|
+
*/
|
|
1
118
|
export * from "./projection";
|
|
2
119
|
export * from "./greateCircle";
|
|
3
120
|
export * from "./rhumbLine";
|
package/index.js
CHANGED
|
@@ -1,173 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
function mercatorProjection(interestPoint, centerLongitude = 0) {
|
|
3
|
-
const r = 6371000;
|
|
4
|
-
const latitude = interestPoint.latitude * Math.PI / 180;
|
|
5
|
-
const longitude = interestPoint.longitude * Math.PI / 180;
|
|
6
|
-
let x = r * normalizeAngleMinusPiToPi(longitude - centerLongitude * Math.PI / 180);
|
|
7
|
-
let y = r * Math.log(Math.tan(Math.PI / 4 + latitude / 2));
|
|
8
|
-
return { x, y };
|
|
9
|
-
}
|
|
10
|
-
function inverseMercatorProjection(point, centerLongitude = 0) {
|
|
11
|
-
const r = 6371000;
|
|
12
|
-
const longitude = point.x / r + centerLongitude;
|
|
13
|
-
const latitude = 2 * Math.atan(Math.exp(point.y / r)) - Math.PI / 2;
|
|
14
|
-
return { latitude: latitude * 180 / Math.PI, longitude: longitude * 180 / Math.PI };
|
|
15
|
-
}
|
|
16
|
-
function orthoProjection(interestPoint, origin) {
|
|
17
|
-
const r = 6371000;
|
|
18
|
-
const latitude = interestPoint.latitude * Math.PI / 180;
|
|
19
|
-
const longitude = interestPoint.longitude * Math.PI / 180;
|
|
20
|
-
const x = r * Math.cos(latitude) * Math.sin(longitude - origin.longitude * Math.PI / 180);
|
|
21
|
-
const y = r * (Math.cos(origin.latitude * Math.PI / 180) * Math.sin(latitude) - Math.sin(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180));
|
|
22
|
-
const clipped = Math.sin(origin.latitude * Math.PI / 180) * Math.sin(latitude) + Math.cos(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180);
|
|
23
|
-
return { clipped: clipped < 0, coord: { x, y } };
|
|
24
|
-
}
|
|
25
|
-
function inverseOrthoProjection(interestPoint, origin) {
|
|
26
|
-
const r = 6371000;
|
|
27
|
-
const rho = Math.sqrt(interestPoint.x * interestPoint.x + interestPoint.y * interestPoint.y);
|
|
28
|
-
const c = Math.asin(rho / r);
|
|
29
|
-
const latitude = Math.asin(Math.cos(c) * Math.sin(origin.latitude * Math.PI / 180) + interestPoint.y * Math.sin(c) * Math.cos(origin.latitude * Math.PI / 180) / rho) * 180 / Math.PI;
|
|
30
|
-
const longitude = origin.longitude + Math.atan2(interestPoint.x * Math.sin(c), rho * Math.cos(c) * Math.cos(origin.latitude * Math.PI / 180) - interestPoint.y * Math.sin(c) * Math.sin(origin.latitude * Math.PI / 180)) * 180 / Math.PI;
|
|
31
|
-
return { latitude, longitude };
|
|
32
|
-
}
|
|
33
|
-
function normalizeAngleMinusPiToPi(angle) {
|
|
34
|
-
angle = angle % (Math.PI * 2);
|
|
35
|
-
angle = (angle + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
|
|
36
|
-
return angle;
|
|
37
|
-
}
|
|
38
|
-
// src/greateCircle.ts
|
|
39
|
-
function intermediatePointOnGreatCircle(startCoord, endCoord, fraction) {
|
|
40
|
-
const Δ_ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;
|
|
41
|
-
const Δ_2 = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;
|
|
42
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
43
|
-
const λ1 = startCoord.longitude * Math.PI / 180;
|
|
44
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
45
|
-
const λ2 = endCoord.longitude * Math.PI / 180;
|
|
46
|
-
const angularDistA = Math.sin(Δ_ / 2) * Math.sin(Δ_ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δ_2 / 2) * Math.sin(Δ_2 / 2);
|
|
47
|
-
const c = 2 * Math.atan2(Math.sqrt(angularDistA), Math.sqrt(1 - angularDistA));
|
|
48
|
-
const a = Math.sin((1 - fraction) * c) / Math.sin(c);
|
|
49
|
-
const b = Math.sin(fraction * c) / Math.sin(c);
|
|
50
|
-
const x = a * Math.cos(φ1) * Math.cos(λ1) + b * Math.cos(φ2) * Math.cos(λ2);
|
|
51
|
-
const y = a * Math.cos(φ1) * Math.sin(λ1) + b * Math.cos(φ2) * Math.sin(λ2);
|
|
52
|
-
const z = a * Math.sin(φ1) * b * Math.sin(φ2);
|
|
53
|
-
const φi = Math.atan2(z, Math.sqrt(x * x + y * y));
|
|
54
|
-
const λi = Math.atan2(y, x);
|
|
55
|
-
return { latitude: φi, longitude: λi };
|
|
56
|
-
}
|
|
57
|
-
function midPointOnGreatCircle(startCoord, endCoord) {
|
|
58
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
59
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
60
|
-
const λ1 = startCoord.longitude * Math.PI / 180;
|
|
61
|
-
const λ2 = endCoord.longitude * Math.PI / 180;
|
|
62
|
-
const Bx = Math.cos(φ2) * Math.cos(λ2 - λ1);
|
|
63
|
-
const By = Math.cos(φ2) * Math.sin(λ2 - λ1);
|
|
64
|
-
const φ3 = Math.atan2(Math.sin(φ1) + Math.sin(φ2), Math.sqrt((Math.cos(φ1) + Bx) * (Math.cos(φ1) + Bx) + By * By));
|
|
65
|
-
const λ3 = λ1 + Math.atan2(By, Math.cos(φ1) + Bx);
|
|
66
|
-
return { latitude: φ3, longitude: λ3 };
|
|
67
|
-
}
|
|
68
|
-
function initialBearingOfGreatCircle(startCoord, endCoord) {
|
|
69
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
70
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
71
|
-
const λ1 = startCoord.longitude * Math.PI / 180;
|
|
72
|
-
const λ2 = endCoord.longitude * Math.PI / 180;
|
|
73
|
-
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
|
|
74
|
-
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
|
|
75
|
-
const θ = Math.atan2(y, x);
|
|
76
|
-
const brng = (θ * 180 / Math.PI + 360) % 360;
|
|
77
|
-
return brng;
|
|
78
|
-
}
|
|
79
|
-
function greatCircleDistance(startCoord, endCoord) {
|
|
80
|
-
const R = 6371000;
|
|
81
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
82
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
83
|
-
const Δ_ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;
|
|
84
|
-
const Δ_2 = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;
|
|
85
|
-
const a = Math.sin(Δ_ / 2) * Math.sin(Δ_ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δ_2 / 2) * Math.sin(Δ_2 / 2);
|
|
86
|
-
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
87
|
-
const d = R * c;
|
|
88
|
-
return d;
|
|
89
|
-
}
|
|
90
|
-
function destinationFromOriginOnGreatCircle(startCoord, bearing, distance) {
|
|
91
|
-
const R = 6371000;
|
|
92
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
93
|
-
const λ1 = startCoord.longitude * Math.PI / 180;
|
|
94
|
-
const θ = bearing * Math.PI / 180;
|
|
95
|
-
const d = distance / R;
|
|
96
|
-
const φ2 = Math.asin(Math.sin(φ1) * Math.cos(d) + Math.cos(φ1) * Math.sin(d) * Math.cos(θ));
|
|
97
|
-
const λ2 = λ1 + Math.atan2(Math.sin(θ) * Math.sin(d) * Math.cos(φ1), Math.cos(d) - Math.sin(φ1) * Math.sin(φ2));
|
|
98
|
-
return { latitude: φ2, longitude: λ2 };
|
|
99
|
-
}
|
|
100
|
-
// src/rhumbLine.ts
|
|
101
|
-
function rhumbDistance(startCoord, endCoord) {
|
|
102
|
-
const R = 6371000;
|
|
103
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
104
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
105
|
-
const Δ_ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;
|
|
106
|
-
let Δ_2 = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;
|
|
107
|
-
const Δ_3 = Math.log(Math.tan(Math.PI / 4 + φ2 / 2) / Math.tan(Math.PI / 4 + φ1 / 2));
|
|
108
|
-
const q = Math.abs(Δ_3) > 0.00000000001 ? Δ_ / Δ_3 : Math.cos(φ1);
|
|
109
|
-
if (Math.abs(Δ_2) > Math.PI)
|
|
110
|
-
Δ_2 = Δ_2 > 0 ? -(2 * Math.PI - Δ_2) : 2 * Math.PI + Δ_2;
|
|
111
|
-
const dist = Math.sqrt(Δ_ * Δ_ + q * q * Δ_2 * Δ_2) * R;
|
|
112
|
-
return dist;
|
|
113
|
-
}
|
|
114
|
-
function rhumbBearing(startCoord, endCoord) {
|
|
115
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
116
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
117
|
-
let Δ_ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;
|
|
118
|
-
const Δ_2 = Math.log(Math.tan(Math.PI / 4 + φ2 / 2) / Math.tan(Math.PI / 4 + φ1 / 2));
|
|
119
|
-
if (Math.abs(Δ_) > Math.PI)
|
|
120
|
-
Δ_ = Δ_ > 0 ? -(2 * Math.PI - Δ_) : 2 * Math.PI + Δ_;
|
|
121
|
-
const brng = Math.atan2(Δ_, Δ_2) * 180 / Math.PI;
|
|
122
|
-
return brng;
|
|
123
|
-
}
|
|
124
|
-
function destinationFromOriginOnRhumbLine(startCoord, bearing, distance) {
|
|
125
|
-
const R = 6371000;
|
|
126
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
127
|
-
const λ1 = startCoord.longitude * Math.PI / 180;
|
|
128
|
-
const θ = bearing * Math.PI / 180;
|
|
129
|
-
const d = distance;
|
|
130
|
-
const δ = d / R;
|
|
131
|
-
const Δ_ = δ * Math.cos(θ);
|
|
132
|
-
let φ2 = φ1 + Δ_;
|
|
133
|
-
const Δ_2 = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));
|
|
134
|
-
const q = Math.abs(Δ_2) > 0.00000000001 ? Δ_ / Δ_2 : Math.cos(φ1);
|
|
135
|
-
const Δ_3 = δ * Math.sin(θ) / q;
|
|
136
|
-
const λ2 = λ1 + Δ_3;
|
|
137
|
-
if (Math.abs(φ2) > Math.PI / 2)
|
|
138
|
-
φ2 = φ2 > 0 ? Math.PI - φ2 : -Math.PI - φ2;
|
|
139
|
-
return { latitude: φ2, longitude: λ2 };
|
|
140
|
-
}
|
|
141
|
-
function midPointOnRhumbLine(startCoord, endCoord) {
|
|
142
|
-
let λ1 = startCoord.longitude * Math.PI / 180;
|
|
143
|
-
const λ2 = endCoord.longitude * Math.PI / 180;
|
|
144
|
-
const φ1 = startCoord.latitude * Math.PI / 180;
|
|
145
|
-
const φ2 = endCoord.latitude * Math.PI / 180;
|
|
146
|
-
if (Math.abs(λ2 - λ1) > Math.PI)
|
|
147
|
-
λ1 += 2 * Math.PI;
|
|
148
|
-
const φ3 = (φ1 + φ2) / 2;
|
|
149
|
-
const f1 = Math.tan(Math.PI / 4 + φ1 / 2);
|
|
150
|
-
const f2 = Math.tan(Math.PI / 4 + φ2 / 2);
|
|
151
|
-
const f3 = Math.tan(Math.PI / 4 + φ3 / 2);
|
|
152
|
-
let λ3 = ((λ2 - λ1) * Math.log(f3) + λ1 * Math.log(f2) - λ2 * Math.log(f1)) / Math.log(f2 / f1);
|
|
153
|
-
if (!isFinite(λ3))
|
|
154
|
-
λ3 = (λ1 + λ2) / 2;
|
|
155
|
-
return { latitude: φ3, longitude: λ3 };
|
|
156
|
-
}
|
|
157
|
-
export {
|
|
158
|
-
rhumbDistance,
|
|
159
|
-
rhumbBearing,
|
|
160
|
-
orthoProjection,
|
|
161
|
-
midPointOnRhumbLine,
|
|
162
|
-
midPointOnGreatCircle,
|
|
163
|
-
mercatorProjection,
|
|
164
|
-
inverseOrthoProjection,
|
|
165
|
-
inverseMercatorProjection,
|
|
166
|
-
intermediatePointOnGreatCircle,
|
|
167
|
-
initialBearingOfGreatCircle,
|
|
168
|
-
greatCircleDistance,
|
|
169
|
-
destinationFromOriginOnRhumbLine,
|
|
170
|
-
destinationFromOriginOnGreatCircle
|
|
171
|
-
};
|
|
1
|
+
function w(c,e=0){let p=c.latitude*Math.PI/180,l=c.longitude*Math.PI/180,m=6371000*D(l-e*Math.PI/180),i=6371000*Math.log(Math.tan(Math.PI/4+p/2));return{x:m,y:i}}function R(c,e=0){let p=c.x/6371000+e;return{latitude:(2*Math.atan(Math.exp(c.y/6371000))-Math.PI/2)*180/Math.PI,longitude:p*180/Math.PI}}function g(c,e){let p=c.latitude*Math.PI/180,l=c.longitude*Math.PI/180,m=6371000*Math.cos(p)*Math.sin(l-e.longitude*Math.PI/180),i=6371000*(Math.cos(e.latitude*Math.PI/180)*Math.sin(p)-Math.sin(e.latitude*Math.PI/180)*Math.cos(p)*Math.cos(l-e.longitude*Math.PI/180));return{clipped:Math.sin(e.latitude*Math.PI/180)*Math.sin(p)+Math.cos(e.latitude*Math.PI/180)*Math.cos(p)*Math.cos(l-e.longitude*Math.PI/180)<0,coord:{x:m,y:i}}}function E(c,e){let p=Math.sqrt(c.x*c.x+c.y*c.y),l=Math.asin(p/6371000),m=Math.asin(Math.cos(l)*Math.sin(e.latitude*Math.PI/180)+c.y*Math.sin(l)*Math.cos(e.latitude*Math.PI/180)/p)*180/Math.PI,i=e.longitude+Math.atan2(c.x*Math.sin(l),p*Math.cos(l)*Math.cos(e.latitude*Math.PI/180)-c.y*Math.sin(l)*Math.sin(e.latitude*Math.PI/180))*180/Math.PI;return{latitude:m,longitude:i}}function D(c){return c=c%(Math.PI*2),c=(c+3*Math.PI)%(2*Math.PI)-Math.PI,c}function B(c,e,b){let p=(e.latitude-c.latitude)*Math.PI/180,l=(e.longitude-c.longitude)*Math.PI/180,m=c.latitude*Math.PI/180,i=c.longitude*Math.PI/180,v=e.latitude*Math.PI/180,j=e.longitude*Math.PI/180,u=Math.sin(p/2)*Math.sin(p/2)+Math.cos(m)*Math.cos(v)*Math.sin(l/2)*Math.sin(l/2),k=2*Math.atan2(Math.sqrt(u),Math.sqrt(1-u)),h=Math.sin((1-b)*k)/Math.sin(k),f=Math.sin(b*k)/Math.sin(k),a=h*Math.cos(m)*Math.cos(i)+f*Math.cos(v)*Math.cos(j),O=h*Math.cos(m)*Math.sin(i)+f*Math.cos(v)*Math.sin(j),G=h*Math.sin(m)*f*Math.sin(v),M=Math.atan2(G,Math.sqrt(a*a+O*O)),F=Math.atan2(O,a);return{latitude:M,longitude:F}}function K(c,e){let b=c.latitude*Math.PI/180,p=e.latitude*Math.PI/180,l=c.longitude*Math.PI/180,m=e.longitude*Math.PI/180,i=Math.cos(p)*Math.cos(m-l),v=Math.cos(p)*Math.sin(m-l),j=Math.atan2(Math.sin(b)+Math.sin(p),Math.sqrt((Math.cos(b)+i)*(Math.cos(b)+i)+v*v)),u=l+Math.atan2(v,Math.cos(b)+i);return{latitude:j,longitude:u}}function N(c,e){let b=c.latitude*Math.PI/180,p=e.latitude*Math.PI/180,l=c.longitude*Math.PI/180,m=e.longitude*Math.PI/180,i=Math.sin(m-l)*Math.cos(p),v=Math.cos(b)*Math.sin(p)-Math.sin(b)*Math.cos(p)*Math.cos(m-l);return(Math.atan2(i,v)*180/Math.PI+360)%360}function S(c,e){let p=c.latitude*Math.PI/180,l=e.latitude*Math.PI/180,m=(e.latitude-c.latitude)*Math.PI/180,i=(e.longitude-c.longitude)*Math.PI/180,v=Math.sin(m/2)*Math.sin(m/2)+Math.cos(p)*Math.cos(l)*Math.sin(i/2)*Math.sin(i/2);return 6371000*(2*Math.atan2(Math.sqrt(v),Math.sqrt(1-v)))}function V(c,e,b){let l=c.latitude*Math.PI/180,m=c.longitude*Math.PI/180,i=e*Math.PI/180,v=b/6371000,j=Math.asin(Math.sin(l)*Math.cos(v)+Math.cos(l)*Math.sin(v)*Math.cos(i)),u=m+Math.atan2(Math.sin(i)*Math.sin(v)*Math.cos(l),Math.cos(v)-Math.sin(l)*Math.sin(j));return{latitude:j,longitude:u}}function H(c,e){let p=c.latitude*Math.PI/180,l=e.latitude*Math.PI/180,m=(e.latitude-c.latitude)*Math.PI/180,i=(e.longitude-c.longitude)*Math.PI/180,v=Math.log(Math.tan(Math.PI/4+l/2)/Math.tan(Math.PI/4+p/2)),j=Math.abs(v)>0.00000000001?m/v:Math.cos(p);if(Math.abs(i)>Math.PI)i=i>0?-(2*Math.PI-i):2*Math.PI+i;return Math.sqrt(m*m+j*j*i*i)*6371000}function J(c,e){let b=c.latitude*Math.PI/180,p=e.latitude*Math.PI/180,l=(e.longitude-c.longitude)*Math.PI/180,m=Math.log(Math.tan(Math.PI/4+p/2)/Math.tan(Math.PI/4+b/2));if(Math.abs(l)>Math.PI)l=l>0?-(2*Math.PI-l):2*Math.PI+l;return Math.atan2(l,m)*180/Math.PI}function L(c,e,b){let l=c.latitude*Math.PI/180,m=c.longitude*Math.PI/180,i=e*Math.PI/180,j=b/6371000,u=j*Math.cos(i),k=l+u,h=Math.log(Math.tan(k/2+Math.PI/4)/Math.tan(l/2+Math.PI/4)),f=Math.abs(h)>0.00000000001?u/h:Math.cos(l),a=j*Math.sin(i)/f,O=m+a;if(Math.abs(k)>Math.PI/2)k=k>0?Math.PI-k:-Math.PI-k;return{latitude:k,longitude:O}}function Q(c,e){let b=c.longitude*Math.PI/180,p=e.longitude*Math.PI/180,l=c.latitude*Math.PI/180,m=e.latitude*Math.PI/180;if(Math.abs(p-b)>Math.PI)b+=2*Math.PI;let i=(l+m)/2,v=Math.tan(Math.PI/4+l/2),j=Math.tan(Math.PI/4+m/2),u=Math.tan(Math.PI/4+i/2),k=((p-b)*Math.log(u)+b*Math.log(j)-p*Math.log(v))/Math.log(j/v);if(!isFinite(k))k=(b+p)/2;return{latitude:i,longitude:k}}export{H as rhumbDistance,J as rhumbBearing,g as orthoProjection,Q as midPointOnRhumbLine,K as midPointOnGreatCircle,w as mercatorProjection,E as inverseOrthoProjection,R as inverseMercatorProjection,B as intermediatePointOnGreatCircle,N as initialBearingOfGreatCircle,S as greatCircleDistance,L as destinationFromOriginOnRhumbLine,V as destinationFromOriginOnGreatCircle};
|
|
172
2
|
|
|
173
|
-
//# debugId=
|
|
3
|
+
//# debugId=ADDDACDC1E32424A64756E2164756E21
|
package/index.js.map
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/projection.ts", "../src/greateCircle.ts", "../src/rhumbLine.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { Point } from \"@ue-too/math\";\n\nexport type GeoCoord = {\n longitude: number;\n latitude: number;\n}\n\nexport function mercatorProjection(interestPoint: GeoCoord, centerLongitude: number = 0): Point{\n const r = 6371000;\n const latitude = interestPoint.latitude * Math.PI / 180;\n const longitude = interestPoint.longitude * Math.PI / 180;\n let x = r * normalizeAngleMinusPiToPi(longitude - centerLongitude * Math.PI / 180);\n let y = r * Math.log(Math.tan(Math.PI / 4 + latitude / 2));\n return {x: x, y: y};\n}\n\nexport function inverseMercatorProjection(point: Point, centerLongitude: number = 0): GeoCoord{\n const r = 6371000;\n const longitude = point.x / r + centerLongitude;\n const latitude = 2 * Math.atan(Math.exp(point.y / r)) - Math.PI / 2;\n return {latitude: latitude * 180 / Math.PI, longitude: longitude * 180 / Math.PI};\n}\n\nexport function orthoProjection(interestPoint: GeoCoord, origin: GeoCoord): {clipped: boolean, coord: Point}{\n const r = 6371000;\n const latitude = interestPoint.latitude * Math.PI / 180;\n const longitude = interestPoint.longitude * Math.PI / 180;\n const x = r * Math.cos(latitude) * Math.sin(longitude - origin.longitude * Math.PI / 180);\n const y = r * (Math.cos(origin.latitude * Math.PI / 180) * Math.sin(latitude) - Math.sin(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180));\n const clipped = Math.sin(origin.latitude * Math.PI / 180) * Math.sin(latitude) + Math.cos(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180);\n\n return {clipped: clipped < 0, coord: {x: x, y: y}};\n}\n\nexport function inverseOrthoProjection(interestPoint: Point, origin: GeoCoord): GeoCoord {\n const r = 6371000;\n const rho = Math.sqrt(interestPoint.x * interestPoint.x + interestPoint.y * interestPoint.y);\n const c = Math.asin(rho / r);\n const latitude = Math.asin(Math.cos(c) * Math.sin(origin.latitude * Math.PI / 180) + (interestPoint.y * Math.sin(c) * Math.cos(origin.latitude * Math.PI / 180)) / rho) * 180 / Math.PI;\n const longitude = origin.longitude + Math.atan2(interestPoint.x * Math.sin(c), rho * Math.cos(c)*Math.cos(origin.latitude * Math.PI / 180) - interestPoint.y * Math.sin(c) * Math.sin(origin.latitude * Math.PI / 180)) * 180 / Math.PI;\n return {latitude: latitude, longitude: longitude};\n}\n\nfunction normalizeAngleMinusPiToPi(angle: number): number {\n // Reduce the angle\n angle = angle % (Math.PI * 2);\n\n // Force it to be in the range -π to π\n angle = (angle + 3 * Math.PI) % (2 * Math.PI) - Math.PI;\n\n return angle;\n}\n",
|
|
6
|
-
"import { GeoCoord } from \"./projection\";\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n/* Geodesy representation conversion functions (c) Chris Veness 2002-2022 */\n/* MIT Licence */\n/* www.movable-type.co.uk/scripts/latlong.html */\n/* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n\nexport function intermediatePointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord, fraction: number): GeoCoord{\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;\n const Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n const φ1 = startCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const angularDistA = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +\n Math.cos(φ1) * Math.cos(φ2) *\n Math.sin(Δλ / 2) * Math.sin(Δλ / 2);\n const c = 2 * Math.atan2(Math.sqrt(angularDistA), Math.sqrt(1 - angularDistA));\n \n const a = Math.sin((1 - fraction) * c) / Math.sin(c);\n const b = Math.sin(fraction * c) / Math.sin(c);\n const x = a * Math.cos(φ1) * Math.cos(λ1) + b * Math.cos(φ2) * Math.cos(λ2);\n const y = a * Math.cos(φ1) * Math.sin(λ1) + b * Math.cos(φ2) * Math.sin(λ2);\n const z = a * Math.sin(φ1) * b * Math.sin(φ2);\n const φi = Math.atan2(z, Math.sqrt(x * x + y * y));\n const λi = Math.atan2(y, x);\n return {latitude: φi, longitude: λi};\n}\n\nexport function midPointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord{\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const Bx = Math.cos(φ2) * Math.cos(λ2 - λ1);\n const By = Math.cos(φ2) * Math.sin(λ2 - λ1);\n const φ3 = Math.atan2(Math.sin(φ1) + Math.sin(φ2),\n Math.sqrt( (Math.cos(φ1) + Bx) * (Math.cos(φ1) + Bx) + By * By ) );\n const λ3 = λ1 + Math.atan2(By, Math.cos(φ1) + Bx);\n return {latitude: φ3, longitude: λ3};\n}\n\n/**\n * Calculate the initial bearing between two points on the Earth's surface. \n * (traveling along the great circle would result in different bearing from the start point to the end point)\n * \n * @param startCoord - The starting point in GeoCoord format.\n * @param endCoord - The ending point in GeoCoord format.\n * @returns The bearing in degrees.\n */\nexport function initialBearingOfGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const y = Math.sin(λ2-λ1) * Math.cos(φ2);\n const x = Math.cos(φ1) * Math.sin(φ2) -\n Math.sin(φ1) * Math.cos(φ2)*Math.cos(λ2 - λ1);\n const θ = Math.atan2(y, x);\n const brng = (θ * 180 / Math.PI + 360) % 360; // in degrees\n return brng;\n}\n\nexport function greatCircleDistance(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI / 180;\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;\n const Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n\n const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +\n Math.cos(φ1) * Math.cos(φ2) *\n Math.sin(Δλ / 2) * Math.sin(Δλ / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const d = R * c; // in metres\n return d;\n} \n\nexport function destinationFromOriginOnGreatCircle(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const λ1 = startCoord.longitude * Math.PI / 180;\n const θ = bearing * Math.PI / 180;\n const d = distance / R; // angular distance in radians\n const φ2 = Math.asin(Math.sin(φ1) * Math.cos(d) +\n Math.cos(φ1) * Math.sin(d) * Math.cos(θ) );\n const λ2 = λ1 + Math.atan2(Math.sin(θ) * Math.sin(d) * Math.cos(φ1),\n Math.cos(d) - Math.sin(φ1) * Math.sin(φ2));\n return {latitude: φ2, longitude: λ2};\n}\n",
|
|
7
|
-
"import { GeoCoord } from \"./projection\";\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n/* Geodesy representation conversion functions (c) Chris Veness 2002-2022 */\n/* MIT Licence */\n/* www.movable-type.co.uk/scripts/latlong.html */\n/* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\nexport function rhumbDistance(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI/180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI/180;\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI/180;\n let Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI/180;\n const Δψ = Math.log(Math.tan(Math.PI / 4 + φ2 / 2)/Math.tan(Math.PI / 4 + φ1 / 2));\n const q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0\n\n // if dLon over 180° take shorter rhumb line across the anti-meridian:\n if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? - (2 * Math.PI - Δλ) : (2 * Math.PI + Δλ);\n\n const dist = Math.sqrt(Δφ * Δφ + q * q * Δλ * Δλ) * R;\n return dist;\n}\n\nexport function rhumbBearing(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI / 180;\n let Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n const Δψ = Math.log(Math.tan(Math.PI / 4 + φ2 / 2) / Math.tan(Math.PI / 4 + φ1 / 2));\n\n // if dLon over 180° take shorter rhumb line across the anti-meridian:\n if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? -(2 * Math.PI-Δλ) : (2 * Math.PI + Δλ);\n\n const brng = Math.atan2(Δλ, Δψ) * 180 / Math.PI;\n return brng;\n}\n\nexport function destinationFromOriginOnRhumbLine(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const λ1 = startCoord.longitude * Math.PI / 180;\n const θ = bearing * Math.PI / 180;\n const d = distance;\n const δ = d / R;\n const Δφ = δ * Math.cos(θ);\n let φ2 = φ1 + Δφ;\n\n const Δψ = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));\n const q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0\n\n const Δλ = δ * Math.sin(θ) / q;\n const λ2 = λ1 + Δλ;\n\n // check for some daft bugger going past the pole, normalise latitude if so\n if (Math.abs(φ2) > Math.PI / 2) φ2 = φ2 > 0 ? Math.PI - φ2 : -Math.PI - φ2;\n return {latitude: φ2, longitude: λ2};\n}\n\nexport function midPointOnRhumbLine(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord{\n let λ1 = startCoord.longitude * Math.PI / 180; \n const λ2 = endCoord.longitude * Math.PI / 180;\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180; \n if (Math.abs(λ2 - λ1) > Math.PI) λ1 += 2 * Math.PI; // crossing anti-meridian\n\n const φ3 = (φ1 + φ2) / 2;\n const f1 = Math.tan(Math.PI / 4 + φ1 / 2);\n const f2 = Math.tan(Math.PI / 4 + φ2 / 2);\n const f3 = Math.tan(Math.PI / 4 + φ3 / 2);\n let λ3 = ( (λ2 - λ1) * Math.log(f3) + λ1 * Math.log(f2) - λ2 * Math.log(f1) ) / Math.log(f2 / f1);\n\n if (!isFinite(λ3)) λ3 = (λ1 + λ2) / 2; // parallel of latitude\n return {latitude: φ3, longitude: λ3};\n}\n"
|
|
5
|
+
"import { Point } from \"@ue-too/math\";\n\n/**\n * Geographic coordinate representing a location on Earth's surface.\n *\n * @remarks\n * Coordinates use the WGS84 standard:\n * - Latitude: -90 to 90 degrees (negative = south, positive = north)\n * - Longitude: -180 to 180 degrees (negative = west, positive = east)\n *\n * @category Types\n */\nexport type GeoCoord = {\n /** Longitude in degrees (-180 to 180) */\n longitude: number;\n /** Latitude in degrees (-90 to 90) */\n latitude: number;\n}\n\n/**\n * Projects a geographic coordinate to Mercator projection.\n *\n * @remarks\n * The Mercator projection is a cylindrical map projection that preserves angles\n * and shapes locally. It's widely used for navigation because straight lines on\n * the map represent constant bearings (rhumb lines).\n *\n * The projection uses Earth's mean radius of 6,371,000 meters.\n *\n * Note: The Mercator projection becomes increasingly distorted near the poles.\n *\n * @param interestPoint - The geographic coordinate to project\n * @param centerLongitude - The central meridian in degrees (default: 0)\n * @returns The projected point in meters from the central meridian\n *\n * @example\n * ```typescript\n * const coord = { latitude: 51.5074, longitude: -0.1278 }; // London\n * const point = mercatorProjection(coord);\n * console.log(point); // { x: -14232.4, y: 6711665.7 }\n *\n * // With custom center longitude\n * const pointCentered = mercatorProjection(coord, -0.1278);\n * console.log(pointCentered.x); // Close to 0\n * ```\n *\n * @category Projections\n */\nexport function mercatorProjection(interestPoint: GeoCoord, centerLongitude: number = 0): Point{\n const r = 6371000;\n const latitude = interestPoint.latitude * Math.PI / 180;\n const longitude = interestPoint.longitude * Math.PI / 180;\n let x = r * normalizeAngleMinusPiToPi(longitude - centerLongitude * Math.PI / 180);\n let y = r * Math.log(Math.tan(Math.PI / 4 + latitude / 2));\n return {x: x, y: y};\n}\n\n/**\n * Converts a Mercator projection point back to geographic coordinates.\n *\n * @remarks\n * This is the inverse of {@link mercatorProjection}. Given a point in Mercator\n * projection space (in meters), it returns the corresponding latitude/longitude.\n *\n * @param point - The point in Mercator projection (in meters)\n * @param centerLongitude - The central meridian in degrees (must match the forward projection)\n * @returns The geographic coordinate\n *\n * @example\n * ```typescript\n * const point = { x: -14232.4, y: 6711665.7 };\n * const coord = inverseMercatorProjection(point);\n * console.log(coord); // { latitude: ~51.5, longitude: ~-0.13 }\n * ```\n *\n * @category Projections\n */\nexport function inverseMercatorProjection(point: Point, centerLongitude: number = 0): GeoCoord{\n const r = 6371000;\n const longitude = point.x / r + centerLongitude;\n const latitude = 2 * Math.atan(Math.exp(point.y / r)) - Math.PI / 2;\n return {latitude: latitude * 180 / Math.PI, longitude: longitude * 180 / Math.PI};\n}\n\n/**\n * Projects a geographic coordinate to orthographic projection.\n *\n * @remarks\n * The orthographic projection shows Earth as it would appear from space,\n * displaying one hemisphere at a time. It's useful for globe-like visualizations.\n *\n * Points on the back hemisphere (not visible from the origin viewpoint) are\n * marked as clipped.\n *\n * The projection uses Earth's mean radius of 6,371,000 meters.\n *\n * @param interestPoint - The geographic coordinate to project\n * @param origin - The center point of the hemisphere to view\n * @returns Object with clipped flag and projected coordinate\n *\n * @example\n * ```typescript\n * const origin = { latitude: 45.0, longitude: 0.0 }; // View centered on France\n * const coord = { latitude: 51.5, longitude: -0.1 }; // London\n *\n * const result = orthoProjection(coord, origin);\n * if (!result.clipped) {\n * console.log('London is visible at:', result.coord);\n * } else {\n * console.log('London is on the back of the globe');\n * }\n * ```\n *\n * @category Projections\n */\nexport function orthoProjection(interestPoint: GeoCoord, origin: GeoCoord): {clipped: boolean, coord: Point}{\n const r = 6371000;\n const latitude = interestPoint.latitude * Math.PI / 180;\n const longitude = interestPoint.longitude * Math.PI / 180;\n const x = r * Math.cos(latitude) * Math.sin(longitude - origin.longitude * Math.PI / 180);\n const y = r * (Math.cos(origin.latitude * Math.PI / 180) * Math.sin(latitude) - Math.sin(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180));\n const clipped = Math.sin(origin.latitude * Math.PI / 180) * Math.sin(latitude) + Math.cos(origin.latitude * Math.PI / 180) * Math.cos(latitude) * Math.cos(longitude - origin.longitude * Math.PI / 180);\n\n return {clipped: clipped < 0, coord: {x: x, y: y}};\n}\n\n/**\n * Converts an orthographic projection point back to geographic coordinates.\n *\n * @remarks\n * This is the inverse of {@link orthoProjection}. Given a point in orthographic\n * projection space (in meters), it returns the corresponding latitude/longitude.\n *\n * @param interestPoint - The point in orthographic projection (in meters)\n * @param origin - The center point of the hemisphere (must match the forward projection)\n * @returns The geographic coordinate\n *\n * @example\n * ```typescript\n * const origin = { latitude: 45.0, longitude: 0.0 };\n * const point = { x: 100000, y: 200000 }; // Some point in projection space\n * const coord = inverseOrthoProjection(point, origin);\n * console.log(coord); // { latitude: ..., longitude: ... }\n * ```\n *\n * @category Projections\n */\nexport function inverseOrthoProjection(interestPoint: Point, origin: GeoCoord): GeoCoord {\n const r = 6371000;\n const rho = Math.sqrt(interestPoint.x * interestPoint.x + interestPoint.y * interestPoint.y);\n const c = Math.asin(rho / r);\n const latitude = Math.asin(Math.cos(c) * Math.sin(origin.latitude * Math.PI / 180) + (interestPoint.y * Math.sin(c) * Math.cos(origin.latitude * Math.PI / 180)) / rho) * 180 / Math.PI;\n const longitude = origin.longitude + Math.atan2(interestPoint.x * Math.sin(c), rho * Math.cos(c)*Math.cos(origin.latitude * Math.PI / 180) - interestPoint.y * Math.sin(c) * Math.sin(origin.latitude * Math.PI / 180)) * 180 / Math.PI;\n return {latitude: latitude, longitude: longitude};\n}\n\nfunction normalizeAngleMinusPiToPi(angle: number): number {\n // Reduce the angle\n angle = angle % (Math.PI * 2);\n\n // Force it to be in the range -π to π\n angle = (angle + 3 * Math.PI) % (2 * Math.PI) - Math.PI;\n\n return angle;\n}\n",
|
|
6
|
+
"import { GeoCoord } from \"./projection\";\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n/* Geodesy representation conversion functions (c) Chris Veness 2002-2022 */\n/* MIT Licence */\n/* www.movable-type.co.uk/scripts/latlong.html */\n/* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n\n/**\n * Calculates an intermediate point along a great circle path.\n *\n * @remarks\n * Given two points on Earth's surface, this finds a point at a specified\n * fraction along the great circle (shortest) path between them.\n *\n * Uses the spherical interpolation formula for accurate results on a sphere.\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @param fraction - The fraction along the path (0 = start, 1 = end, 0.5 = midpoint)\n * @returns The intermediate point at the specified fraction\n *\n * @example\n * ```typescript\n * const nyc = { latitude: 40.7128, longitude: -74.0060 };\n * const london = { latitude: 51.5074, longitude: -0.1278 };\n *\n * // Find point 25% of the way from NYC to London\n * const quarter = intermediatePointOnGreatCircle(nyc, london, 0.25);\n *\n * // Find point 75% of the way\n * const threeQuarters = intermediatePointOnGreatCircle(nyc, london, 0.75);\n * ```\n *\n * @category Great Circle\n */\nexport function intermediatePointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord, fraction: number): GeoCoord{\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;\n const Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n const φ1 = startCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const angularDistA = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +\n Math.cos(φ1) * Math.cos(φ2) *\n Math.sin(Δλ / 2) * Math.sin(Δλ / 2);\n const c = 2 * Math.atan2(Math.sqrt(angularDistA), Math.sqrt(1 - angularDistA));\n \n const a = Math.sin((1 - fraction) * c) / Math.sin(c);\n const b = Math.sin(fraction * c) / Math.sin(c);\n const x = a * Math.cos(φ1) * Math.cos(λ1) + b * Math.cos(φ2) * Math.cos(λ2);\n const y = a * Math.cos(φ1) * Math.sin(λ1) + b * Math.cos(φ2) * Math.sin(λ2);\n const z = a * Math.sin(φ1) * b * Math.sin(φ2);\n const φi = Math.atan2(z, Math.sqrt(x * x + y * y));\n const λi = Math.atan2(y, x);\n return {latitude: φi, longitude: λi};\n}\n\n/**\n * Calculates the midpoint along a great circle path.\n *\n * @remarks\n * This is a specialized, optimized version of {@link intermediatePointOnGreatCircle}\n * for finding the exact midpoint (fraction = 0.5).\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @returns The midpoint on the great circle path\n *\n * @example\n * ```typescript\n * const start = { latitude: 50.0, longitude: -5.0 };\n * const end = { latitude: 58.0, longitude: 3.0 };\n * const mid = midPointOnGreatCircle(start, end);\n * console.log('Midpoint:', mid);\n * ```\n *\n * @category Great Circle\n */\nexport function midPointOnGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord{\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const Bx = Math.cos(φ2) * Math.cos(λ2 - λ1);\n const By = Math.cos(φ2) * Math.sin(λ2 - λ1);\n const φ3 = Math.atan2(Math.sin(φ1) + Math.sin(φ2),\n Math.sqrt( (Math.cos(φ1) + Bx) * (Math.cos(φ1) + Bx) + By * By ) );\n const λ3 = λ1 + Math.atan2(By, Math.cos(φ1) + Bx);\n return {latitude: φ3, longitude: λ3};\n}\n\n/**\n * Calculate the initial bearing between two points on the Earth's surface. \n * (traveling along the great circle would result in different bearing from the start point to the end point)\n * \n * @param startCoord - The starting point in GeoCoord format.\n * @param endCoord - The ending point in GeoCoord format.\n * @returns The bearing in degrees.\n */\nexport function initialBearingOfGreatCircle(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180;\n const λ1 = startCoord.longitude * Math.PI / 180;\n const λ2 = endCoord.longitude * Math.PI / 180;\n const y = Math.sin(λ2-λ1) * Math.cos(φ2);\n const x = Math.cos(φ1) * Math.sin(φ2) -\n Math.sin(φ1) * Math.cos(φ2)*Math.cos(λ2 - λ1);\n const θ = Math.atan2(y, x);\n const brng = (θ * 180 / Math.PI + 360) % 360; // in degrees\n return brng;\n}\n\n/**\n * Calculates the great circle distance between two points on Earth.\n *\n * @remarks\n * Uses the haversine formula to calculate the shortest distance over Earth's\n * surface between two geographic coordinates. This is the \"as-the-crow-flies\"\n * distance.\n *\n * The calculation assumes Earth's mean radius of 6,371,000 meters and treats\n * Earth as a perfect sphere.\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @returns The distance in meters\n *\n * @example\n * ```typescript\n * const nyc = { latitude: 40.7128, longitude: -74.0060 };\n * const london = { latitude: 51.5074, longitude: -0.1278 };\n *\n * const distance = greatCircleDistance(nyc, london);\n * console.log('Distance:', distance / 1000, 'km'); // ~5570 km\n * ```\n *\n * @category Great Circle\n */\nexport function greatCircleDistance(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI / 180;\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI / 180;\n const Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n\n const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +\n Math.cos(φ1) * Math.cos(φ2) *\n Math.sin(Δλ / 2) * Math.sin(Δλ / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const d = R * c; // in metres\n return d;\n} \n\n/**\n * Calculates the destination point given a start point, bearing, and distance on a great circle.\n *\n * @remarks\n * Starting from a given point and traveling along a great circle at a specific\n * initial bearing for a given distance, this calculates where you'll end up.\n *\n * Note: The bearing will change along the path (except when traveling due north/south\n * or along the equator) because great circles are not straight lines on most map projections.\n *\n * @param startCoord - The starting geographic coordinate\n * @param bearing - The initial bearing in degrees (0 = north, 90 = east, etc.)\n * @param distance - The distance to travel in meters\n * @returns The destination coordinate\n *\n * @example\n * ```typescript\n * const start = { latitude: 40.7128, longitude: -74.0060 }; // NYC\n *\n * // Travel 1000km northeast from NYC\n * const destination = destinationFromOriginOnGreatCircle(start, 45, 1000000);\n * console.log('Destination:', destination);\n * ```\n *\n * @category Great Circle\n */\nexport function destinationFromOriginOnGreatCircle(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const λ1 = startCoord.longitude * Math.PI / 180;\n const θ = bearing * Math.PI / 180;\n const d = distance / R; // angular distance in radians\n const φ2 = Math.asin(Math.sin(φ1) * Math.cos(d) +\n Math.cos(φ1) * Math.sin(d) * Math.cos(θ) );\n const λ2 = λ1 + Math.atan2(Math.sin(θ) * Math.sin(d) * Math.cos(φ1),\n Math.cos(d) - Math.sin(φ1) * Math.sin(φ2));\n return {latitude: φ2, longitude: λ2};\n}\n",
|
|
7
|
+
"import { GeoCoord } from \"./projection\";\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n/* Geodesy representation conversion functions (c) Chris Veness 2002-2022 */\n/* MIT Licence */\n/* www.movable-type.co.uk/scripts/latlong.html */\n/* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n/**\n * Calculates the distance along a rhumb line between two points.\n *\n * @remarks\n * A rhumb line (also called loxodrome) is a path of constant bearing. Unlike great\n * circles, rhumb lines appear as straight lines on Mercator projections, making them\n * easier for navigation.\n *\n * Rhumb lines are generally slightly longer than great circle routes, except when\n * traveling due east/west along the equator or due north/south.\n *\n * Uses Earth's mean radius of 6,371,000 meters.\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @returns The distance in meters along the rhumb line\n *\n * @example\n * ```typescript\n * const start = { latitude: 50.0, longitude: -5.0 };\n * const end = { latitude: 58.0, longitude: 3.0 };\n *\n * const distance = rhumbDistance(start, end);\n * console.log('Rhumb distance:', distance / 1000, 'km');\n *\n * // Compare with great circle distance\n * const gcDistance = greatCircleDistance(start, end);\n * console.log('Great circle is', (distance - gcDistance) / 1000, 'km shorter');\n * ```\n *\n * @category Rhumb Line\n */\nexport function rhumbDistance(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI/180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI/180;\n const Δφ = (endCoord.latitude - startCoord.latitude) * Math.PI/180;\n let Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI/180;\n const Δψ = Math.log(Math.tan(Math.PI / 4 + φ2 / 2)/Math.tan(Math.PI / 4 + φ1 / 2));\n const q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0\n\n // if dLon over 180° take shorter rhumb line across the anti-meridian:\n if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? - (2 * Math.PI - Δλ) : (2 * Math.PI + Δλ);\n\n const dist = Math.sqrt(Δφ * Δφ + q * q * Δλ * Δλ) * R;\n return dist;\n}\n\n/**\n * Calculates the constant bearing along a rhumb line.\n *\n * @remarks\n * Unlike great circles where the bearing changes along the path, rhumb lines\n * maintain a constant bearing. This makes them simpler for navigation - you can\n * follow a single compass direction.\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @returns The constant bearing in degrees (0-360)\n *\n * @example\n * ```typescript\n * const start = { latitude: 50.0, longitude: -5.0 };\n * const end = { latitude: 58.0, longitude: 3.0 };\n *\n * const bearing = rhumbBearing(start, end);\n * console.log('Constant bearing:', bearing, 'degrees');\n *\n * // This bearing stays constant along the entire path\n * ```\n *\n * @category Rhumb Line\n */\nexport function rhumbBearing(startCoord: GeoCoord, endCoord: GeoCoord): number{\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const φ2 = endCoord.latitude * Math.PI / 180;\n let Δλ = (endCoord.longitude - startCoord.longitude) * Math.PI / 180;\n const Δψ = Math.log(Math.tan(Math.PI / 4 + φ2 / 2) / Math.tan(Math.PI / 4 + φ1 / 2));\n\n // if dLon over 180° take shorter rhumb line across the anti-meridian:\n if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? -(2 * Math.PI-Δλ) : (2 * Math.PI + Δλ);\n\n const brng = Math.atan2(Δλ, Δψ) * 180 / Math.PI;\n return brng;\n}\n\n/**\n * Calculates the destination point given a start point, constant bearing, and distance on a rhumb line.\n *\n * @remarks\n * Starting from a given point and traveling at a constant bearing for a given\n * distance, this calculates where you'll end up. The bearing remains constant\n * throughout the journey.\n *\n * This is the rhumb line equivalent of {@link destinationFromOriginOnGreatCircle}.\n *\n * @param startCoord - The starting geographic coordinate\n * @param bearing - The constant bearing in degrees (0 = north, 90 = east, etc.)\n * @param distance - The distance to travel in meters\n * @returns The destination coordinate\n *\n * @example\n * ```typescript\n * const start = { latitude: 40.0, longitude: -74.0 };\n *\n * // Travel 500km on constant bearing of 45 degrees (northeast)\n * const destination = destinationFromOriginOnRhumbLine(start, 45, 500000);\n * console.log('Destination:', destination);\n * ```\n *\n * @category Rhumb Line\n */\nexport function destinationFromOriginOnRhumbLine(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord{\n const R = 6371e3; // metres\n const φ1 = startCoord.latitude * Math.PI / 180; // φ, λ in radians\n const λ1 = startCoord.longitude * Math.PI / 180;\n const θ = bearing * Math.PI / 180;\n const d = distance;\n const δ = d / R;\n const Δφ = δ * Math.cos(θ);\n let φ2 = φ1 + Δφ;\n\n const Δψ = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));\n const q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0\n\n const Δλ = δ * Math.sin(θ) / q;\n const λ2 = λ1 + Δλ;\n\n // check for some daft bugger going past the pole, normalise latitude if so\n if (Math.abs(φ2) > Math.PI / 2) φ2 = φ2 > 0 ? Math.PI - φ2 : -Math.PI - φ2;\n return {latitude: φ2, longitude: λ2};\n}\n\n/**\n * Calculates the midpoint along a rhumb line.\n *\n * @remarks\n * Finds the point exactly halfway along a rhumb line path between two points.\n *\n * @param startCoord - The starting geographic coordinate\n * @param endCoord - The ending geographic coordinate\n * @returns The midpoint on the rhumb line path\n *\n * @example\n * ```typescript\n * const start = { latitude: 50.0, longitude: -5.0 };\n * const end = { latitude: 58.0, longitude: 3.0 };\n *\n * const mid = midPointOnRhumbLine(start, end);\n * console.log('Midpoint:', mid);\n * ```\n *\n * @category Rhumb Line\n */\nexport function midPointOnRhumbLine(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord{\n let λ1 = startCoord.longitude * Math.PI / 180; \n const λ2 = endCoord.longitude * Math.PI / 180;\n const φ1 = startCoord.latitude * Math.PI / 180;\n const φ2 = endCoord.latitude * Math.PI / 180; \n if (Math.abs(λ2 - λ1) > Math.PI) λ1 += 2 * Math.PI; // crossing anti-meridian\n\n const φ3 = (φ1 + φ2) / 2;\n const f1 = Math.tan(Math.PI / 4 + φ1 / 2);\n const f2 = Math.tan(Math.PI / 4 + φ2 / 2);\n const f3 = Math.tan(Math.PI / 4 + φ3 / 2);\n let λ3 = ( (λ2 - λ1) * Math.log(f3) + λ1 * Math.log(f2) - λ2 * Math.log(f1) ) / Math.log(f2 / f1);\n\n if (!isFinite(λ3)) λ3 = (λ1 + λ2) / 2; // parallel of latitude\n return {latitude: φ3, longitude: λ3};\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "ADDDACDC1E32424A64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ue-too/border",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/ue-too/ue-too.git"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"./package.json": "./package.json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@ue-too/math": "^0.
|
|
23
|
+
"@ue-too/math": "^0.10.0"
|
|
24
24
|
},
|
|
25
25
|
"main": "./index.js",
|
|
26
26
|
"types": "./index.d.ts",
|
package/projection.d.ts
CHANGED
|
@@ -1,12 +1,125 @@
|
|
|
1
1
|
import { Point } from "@ue-too/math";
|
|
2
|
+
/**
|
|
3
|
+
* Geographic coordinate representing a location on Earth's surface.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Coordinates use the WGS84 standard:
|
|
7
|
+
* - Latitude: -90 to 90 degrees (negative = south, positive = north)
|
|
8
|
+
* - Longitude: -180 to 180 degrees (negative = west, positive = east)
|
|
9
|
+
*
|
|
10
|
+
* @category Types
|
|
11
|
+
*/
|
|
2
12
|
export type GeoCoord = {
|
|
13
|
+
/** Longitude in degrees (-180 to 180) */
|
|
3
14
|
longitude: number;
|
|
15
|
+
/** Latitude in degrees (-90 to 90) */
|
|
4
16
|
latitude: number;
|
|
5
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Projects a geographic coordinate to Mercator projection.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* The Mercator projection is a cylindrical map projection that preserves angles
|
|
23
|
+
* and shapes locally. It's widely used for navigation because straight lines on
|
|
24
|
+
* the map represent constant bearings (rhumb lines).
|
|
25
|
+
*
|
|
26
|
+
* The projection uses Earth's mean radius of 6,371,000 meters.
|
|
27
|
+
*
|
|
28
|
+
* Note: The Mercator projection becomes increasingly distorted near the poles.
|
|
29
|
+
*
|
|
30
|
+
* @param interestPoint - The geographic coordinate to project
|
|
31
|
+
* @param centerLongitude - The central meridian in degrees (default: 0)
|
|
32
|
+
* @returns The projected point in meters from the central meridian
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const coord = { latitude: 51.5074, longitude: -0.1278 }; // London
|
|
37
|
+
* const point = mercatorProjection(coord);
|
|
38
|
+
* console.log(point); // { x: -14232.4, y: 6711665.7 }
|
|
39
|
+
*
|
|
40
|
+
* // With custom center longitude
|
|
41
|
+
* const pointCentered = mercatorProjection(coord, -0.1278);
|
|
42
|
+
* console.log(pointCentered.x); // Close to 0
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @category Projections
|
|
46
|
+
*/
|
|
6
47
|
export declare function mercatorProjection(interestPoint: GeoCoord, centerLongitude?: number): Point;
|
|
48
|
+
/**
|
|
49
|
+
* Converts a Mercator projection point back to geographic coordinates.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* This is the inverse of {@link mercatorProjection}. Given a point in Mercator
|
|
53
|
+
* projection space (in meters), it returns the corresponding latitude/longitude.
|
|
54
|
+
*
|
|
55
|
+
* @param point - The point in Mercator projection (in meters)
|
|
56
|
+
* @param centerLongitude - The central meridian in degrees (must match the forward projection)
|
|
57
|
+
* @returns The geographic coordinate
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const point = { x: -14232.4, y: 6711665.7 };
|
|
62
|
+
* const coord = inverseMercatorProjection(point);
|
|
63
|
+
* console.log(coord); // { latitude: ~51.5, longitude: ~-0.13 }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @category Projections
|
|
67
|
+
*/
|
|
7
68
|
export declare function inverseMercatorProjection(point: Point, centerLongitude?: number): GeoCoord;
|
|
69
|
+
/**
|
|
70
|
+
* Projects a geographic coordinate to orthographic projection.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* The orthographic projection shows Earth as it would appear from space,
|
|
74
|
+
* displaying one hemisphere at a time. It's useful for globe-like visualizations.
|
|
75
|
+
*
|
|
76
|
+
* Points on the back hemisphere (not visible from the origin viewpoint) are
|
|
77
|
+
* marked as clipped.
|
|
78
|
+
*
|
|
79
|
+
* The projection uses Earth's mean radius of 6,371,000 meters.
|
|
80
|
+
*
|
|
81
|
+
* @param interestPoint - The geographic coordinate to project
|
|
82
|
+
* @param origin - The center point of the hemisphere to view
|
|
83
|
+
* @returns Object with clipped flag and projected coordinate
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const origin = { latitude: 45.0, longitude: 0.0 }; // View centered on France
|
|
88
|
+
* const coord = { latitude: 51.5, longitude: -0.1 }; // London
|
|
89
|
+
*
|
|
90
|
+
* const result = orthoProjection(coord, origin);
|
|
91
|
+
* if (!result.clipped) {
|
|
92
|
+
* console.log('London is visible at:', result.coord);
|
|
93
|
+
* } else {
|
|
94
|
+
* console.log('London is on the back of the globe');
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @category Projections
|
|
99
|
+
*/
|
|
8
100
|
export declare function orthoProjection(interestPoint: GeoCoord, origin: GeoCoord): {
|
|
9
101
|
clipped: boolean;
|
|
10
102
|
coord: Point;
|
|
11
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* Converts an orthographic projection point back to geographic coordinates.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* This is the inverse of {@link orthoProjection}. Given a point in orthographic
|
|
109
|
+
* projection space (in meters), it returns the corresponding latitude/longitude.
|
|
110
|
+
*
|
|
111
|
+
* @param interestPoint - The point in orthographic projection (in meters)
|
|
112
|
+
* @param origin - The center point of the hemisphere (must match the forward projection)
|
|
113
|
+
* @returns The geographic coordinate
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const origin = { latitude: 45.0, longitude: 0.0 };
|
|
118
|
+
* const point = { x: 100000, y: 200000 }; // Some point in projection space
|
|
119
|
+
* const coord = inverseOrthoProjection(point, origin);
|
|
120
|
+
* console.log(coord); // { latitude: ..., longitude: ... }
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @category Projections
|
|
124
|
+
*/
|
|
12
125
|
export declare function inverseOrthoProjection(interestPoint: Point, origin: GeoCoord): GeoCoord;
|
package/rhumbLine.d.ts
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
import { GeoCoord } from "./projection";
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the distance along a rhumb line between two points.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A rhumb line (also called loxodrome) is a path of constant bearing. Unlike great
|
|
7
|
+
* circles, rhumb lines appear as straight lines on Mercator projections, making them
|
|
8
|
+
* easier for navigation.
|
|
9
|
+
*
|
|
10
|
+
* Rhumb lines are generally slightly longer than great circle routes, except when
|
|
11
|
+
* traveling due east/west along the equator or due north/south.
|
|
12
|
+
*
|
|
13
|
+
* Uses Earth's mean radius of 6,371,000 meters.
|
|
14
|
+
*
|
|
15
|
+
* @param startCoord - The starting geographic coordinate
|
|
16
|
+
* @param endCoord - The ending geographic coordinate
|
|
17
|
+
* @returns The distance in meters along the rhumb line
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const start = { latitude: 50.0, longitude: -5.0 };
|
|
22
|
+
* const end = { latitude: 58.0, longitude: 3.0 };
|
|
23
|
+
*
|
|
24
|
+
* const distance = rhumbDistance(start, end);
|
|
25
|
+
* console.log('Rhumb distance:', distance / 1000, 'km');
|
|
26
|
+
*
|
|
27
|
+
* // Compare with great circle distance
|
|
28
|
+
* const gcDistance = greatCircleDistance(start, end);
|
|
29
|
+
* console.log('Great circle is', (distance - gcDistance) / 1000, 'km shorter');
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @category Rhumb Line
|
|
33
|
+
*/
|
|
2
34
|
export declare function rhumbDistance(startCoord: GeoCoord, endCoord: GeoCoord): number;
|
|
35
|
+
/**
|
|
36
|
+
* Calculates the constant bearing along a rhumb line.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* Unlike great circles where the bearing changes along the path, rhumb lines
|
|
40
|
+
* maintain a constant bearing. This makes them simpler for navigation - you can
|
|
41
|
+
* follow a single compass direction.
|
|
42
|
+
*
|
|
43
|
+
* @param startCoord - The starting geographic coordinate
|
|
44
|
+
* @param endCoord - The ending geographic coordinate
|
|
45
|
+
* @returns The constant bearing in degrees (0-360)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const start = { latitude: 50.0, longitude: -5.0 };
|
|
50
|
+
* const end = { latitude: 58.0, longitude: 3.0 };
|
|
51
|
+
*
|
|
52
|
+
* const bearing = rhumbBearing(start, end);
|
|
53
|
+
* console.log('Constant bearing:', bearing, 'degrees');
|
|
54
|
+
*
|
|
55
|
+
* // This bearing stays constant along the entire path
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @category Rhumb Line
|
|
59
|
+
*/
|
|
3
60
|
export declare function rhumbBearing(startCoord: GeoCoord, endCoord: GeoCoord): number;
|
|
61
|
+
/**
|
|
62
|
+
* Calculates the destination point given a start point, constant bearing, and distance on a rhumb line.
|
|
63
|
+
*
|
|
64
|
+
* @remarks
|
|
65
|
+
* Starting from a given point and traveling at a constant bearing for a given
|
|
66
|
+
* distance, this calculates where you'll end up. The bearing remains constant
|
|
67
|
+
* throughout the journey.
|
|
68
|
+
*
|
|
69
|
+
* This is the rhumb line equivalent of {@link destinationFromOriginOnGreatCircle}.
|
|
70
|
+
*
|
|
71
|
+
* @param startCoord - The starting geographic coordinate
|
|
72
|
+
* @param bearing - The constant bearing in degrees (0 = north, 90 = east, etc.)
|
|
73
|
+
* @param distance - The distance to travel in meters
|
|
74
|
+
* @returns The destination coordinate
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const start = { latitude: 40.0, longitude: -74.0 };
|
|
79
|
+
*
|
|
80
|
+
* // Travel 500km on constant bearing of 45 degrees (northeast)
|
|
81
|
+
* const destination = destinationFromOriginOnRhumbLine(start, 45, 500000);
|
|
82
|
+
* console.log('Destination:', destination);
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @category Rhumb Line
|
|
86
|
+
*/
|
|
4
87
|
export declare function destinationFromOriginOnRhumbLine(startCoord: GeoCoord, bearing: number, distance: number): GeoCoord;
|
|
88
|
+
/**
|
|
89
|
+
* Calculates the midpoint along a rhumb line.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* Finds the point exactly halfway along a rhumb line path between two points.
|
|
93
|
+
*
|
|
94
|
+
* @param startCoord - The starting geographic coordinate
|
|
95
|
+
* @param endCoord - The ending geographic coordinate
|
|
96
|
+
* @returns The midpoint on the rhumb line path
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const start = { latitude: 50.0, longitude: -5.0 };
|
|
101
|
+
* const end = { latitude: 58.0, longitude: 3.0 };
|
|
102
|
+
*
|
|
103
|
+
* const mid = midPointOnRhumbLine(start, end);
|
|
104
|
+
* console.log('Midpoint:', mid);
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @category Rhumb Line
|
|
108
|
+
*/
|
|
5
109
|
export declare function midPointOnRhumbLine(startCoord: GeoCoord, endCoord: GeoCoord): GeoCoord;
|