@mailwoman/spatial 4.9.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 +3 -0
- package/bbox.ts +445 -0
- package/countries/codes.ts +531 -0
- package/countries/index.ts +8 -0
- package/countries/names.ts +274 -0
- package/feature.ts +90 -0
- package/geometries/collection.ts +37 -0
- package/geometries/index.ts +12 -0
- package/geometries/line-string.ts +47 -0
- package/geometries/point.ts +419 -0
- package/geometries/polygon.ts +237 -0
- package/google/index.ts +7 -0
- package/google/place-id.ts +44 -0
- package/h3/index.ts +90 -0
- package/index.ts +16 -0
- package/objects.ts +116 -0
- package/package.json +44 -0
- package/position.ts +269 -0
- package/projection.ts +23 -0
- package/regions/codes.ts +43 -0
- package/regions/index.ts +8 -0
- package/regions/names.ts +29 -0
- package/sdk/index.ts +7 -0
- package/sdk/well-known-text.ts +86 -0
- package/tsconfig.json +15 -0
- package/typedoc.json +4 -0
package/h3/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cellToLatLng } from "h3-js"
|
|
8
|
+
import type { Tagged } from "type-fest"
|
|
9
|
+
import { GeoPoint, type PointLiteral } from "../geometries/point.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A H3 cell index, full 64 bits.
|
|
13
|
+
*
|
|
14
|
+
* @type {string}
|
|
15
|
+
* @title H3 Cell Index
|
|
16
|
+
* @pattern ^[0-9a-f]{15}$
|
|
17
|
+
*/
|
|
18
|
+
export type H3Cell = Tagged<string, "H3Cell">
|
|
19
|
+
|
|
20
|
+
export function isH3Cell(value: string): value is H3Cell {
|
|
21
|
+
return /^[0-9a-f]{15}$/.test(value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A H3 cell index, shortened to 48 bits.
|
|
26
|
+
*
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @title H3 Cell Index (Short)
|
|
29
|
+
* @pattern ^[0-9a-f]{12}$
|
|
30
|
+
*/
|
|
31
|
+
export type H3CellShort = Tagged<string, "H3CellShort">
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Given a full H3 cell index, shorten it to 48 bits.
|
|
35
|
+
*/
|
|
36
|
+
export function shortenH3Cell(cell: H3Cell): H3CellShort {
|
|
37
|
+
// ...and convert it to a 48-bit cell address.
|
|
38
|
+
const cellBigInt = BigInt(`0x${cell}`)
|
|
39
|
+
// 8 f 2 aa 84 5a 18 ac 6b
|
|
40
|
+
// aa 84 5a 18 ac 6b
|
|
41
|
+
|
|
42
|
+
// Extract the cell address without the resolution
|
|
43
|
+
const h3CellShortBigInt = cellBigInt & 0xfffffffffffffn
|
|
44
|
+
|
|
45
|
+
const h3CellShortHex = h3CellShortBigInt.toString(16)
|
|
46
|
+
|
|
47
|
+
return h3CellShortHex as H3CellShort
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//2 aa 84 5a 18 ac 6b
|
|
51
|
+
|
|
52
|
+
// 8 f2 aa 84 5a 18 ac 6b
|
|
53
|
+
/**
|
|
54
|
+
* Given a short cell address, expand it to a full H3 cell index.
|
|
55
|
+
*/
|
|
56
|
+
export function expandH3Cell(h3CellShort: H3CellShort, resolution = 15): H3Cell {
|
|
57
|
+
// Convert the short cell address back to BigInt
|
|
58
|
+
const h3CellShortBigInt = BigInt(`0x${h3CellShort}`)
|
|
59
|
+
|
|
60
|
+
const resolutionHex = resolution.toString(16)
|
|
61
|
+
// Reassemble the H3 cell index portion...
|
|
62
|
+
const cellBigInt = h3CellShortBigInt << BigInt(8 * (15 - resolution))
|
|
63
|
+
// Back to a string...
|
|
64
|
+
const partialCell = cellBigInt.toString(16)
|
|
65
|
+
// Finally, we add the resolution back to the cell index.
|
|
66
|
+
const cell = `8${resolutionHex}${partialCell}`
|
|
67
|
+
|
|
68
|
+
return cell as H3Cell
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Given a geographic point, return a short cell address.
|
|
73
|
+
*/
|
|
74
|
+
export function shortCellToPoint(shortCell: H3CellShort, resolution = 15): GeoPoint {
|
|
75
|
+
const cell = expandH3Cell(shortCell, resolution)
|
|
76
|
+
|
|
77
|
+
// Convert the H3 cell index back to latitude and longitude
|
|
78
|
+
const [latitude, longitude] = cellToLatLng(cell)
|
|
79
|
+
|
|
80
|
+
return new GeoPoint([longitude, latitude])
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function cellToPointLiteral(cell: H3Cell): PointLiteral {
|
|
84
|
+
const [latitude, longitude] = cellToLatLng(cell)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
type: "Point",
|
|
88
|
+
coordinates: [longitude, latitude],
|
|
89
|
+
}
|
|
90
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./bbox.js"
|
|
8
|
+
export * from "./countries/index.js"
|
|
9
|
+
export * from "./feature.js"
|
|
10
|
+
export * from "./geometries/index.js"
|
|
11
|
+
export * from "./google/index.js"
|
|
12
|
+
export * from "./h3/index.js"
|
|
13
|
+
export * from "./objects.js"
|
|
14
|
+
export * from "./position.js"
|
|
15
|
+
export * from "./projection.js"
|
|
16
|
+
export * from "./regions/index.js"
|
package/objects.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type BBox2DLiteral, type BBox3DLiteral, is2DBBox } from "./bbox.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GeoJSON object types.
|
|
11
|
+
*/
|
|
12
|
+
export type GeometryType =
|
|
13
|
+
| "Point"
|
|
14
|
+
| "MultiPoint"
|
|
15
|
+
| "LineString"
|
|
16
|
+
| "MultiLineString"
|
|
17
|
+
| "Polygon"
|
|
18
|
+
| "MultiPolygon"
|
|
19
|
+
| "GeometryCollection"
|
|
20
|
+
| "FeatureCollection"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Shadow enum-like record of valid GeoJSON object types.
|
|
24
|
+
*/
|
|
25
|
+
export const GeometryType = {
|
|
26
|
+
Point: "Point",
|
|
27
|
+
MultiPoint: "MultiPoint",
|
|
28
|
+
LineString: "LineString",
|
|
29
|
+
MultiLineString: "MultiLineString",
|
|
30
|
+
Polygon: "Polygon",
|
|
31
|
+
MultiPolygon: "MultiPolygon",
|
|
32
|
+
GeometryCollection: "GeometryCollection",
|
|
33
|
+
FeatureCollection: "FeatureCollection",
|
|
34
|
+
} as const satisfies { [K in GeometryType]: K }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The base GeoJSON object.
|
|
38
|
+
*
|
|
39
|
+
* The GeoJSON specification also allows foreign members
|
|
40
|
+
* (https://tools.ietf.org/html/rfc7946#section-6.1) to be included in the object.
|
|
41
|
+
*
|
|
42
|
+
* @see {@link https://tools.ietf.org/html/rfc7946#section-3 GeoJSON Object}
|
|
43
|
+
* @title Geo Object
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export interface GeoObjectLiteral {
|
|
47
|
+
/**
|
|
48
|
+
* Specifies the type of GeoJSON object.
|
|
49
|
+
*/
|
|
50
|
+
type: GeometryType
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A unique identifier for the feature, such as a UUID, a serial number, or a name.
|
|
54
|
+
*/
|
|
55
|
+
id?: string | number | undefined | null
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A bounding box of the coordinate range of the object's Geometries, Features, or Feature
|
|
59
|
+
* Collections.
|
|
60
|
+
*
|
|
61
|
+
* This is useful when defining the extent of a GeoJSON object, i.e. the minimum and maximum
|
|
62
|
+
* coordinates of the object's Geometries, Features, or Feature Collections.
|
|
63
|
+
*
|
|
64
|
+
* @see {@link https://tools.ietf.org/html/rfc7946#section-5 GeoJSON Bounding Boxes}
|
|
65
|
+
*/
|
|
66
|
+
bbox?: BBox2DLiteral | BBox3DLiteral
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Coordinate reference system for GeoJSON objects.
|
|
70
|
+
*
|
|
71
|
+
* @see {@link https://tools.ietf.org/html/rfc7946#section-4 Coordinate Reference Systems}
|
|
72
|
+
* @title Coordinate Reference System
|
|
73
|
+
*/
|
|
74
|
+
crs?: {
|
|
75
|
+
type: "name"
|
|
76
|
+
|
|
77
|
+
properties: {
|
|
78
|
+
/**
|
|
79
|
+
* The name of the coordinate reference system.
|
|
80
|
+
*
|
|
81
|
+
* @default "EPSG:4326"
|
|
82
|
+
*/
|
|
83
|
+
name: string
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Abstract base-class for all GeoJSON class constructors.
|
|
90
|
+
*/
|
|
91
|
+
export abstract class GeoObject implements GeoObjectLiteral {
|
|
92
|
+
/**
|
|
93
|
+
* The JSON literal type of the GeoJSON object.
|
|
94
|
+
*/
|
|
95
|
+
abstract type: GeometryType
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A bounding box of the coordinate range of the object's Geometries, Features, or Feature.
|
|
99
|
+
*
|
|
100
|
+
* @see {@linkcode BBox2DLiteral} for 2-dimensional bounding boxes.
|
|
101
|
+
* @see {@linkcode BBox3DLiteral} for 3-dimensional bounding boxes.
|
|
102
|
+
* @see
|
|
103
|
+
*/
|
|
104
|
+
bbox?: BBox2DLiteral | BBox3DLiteral
|
|
105
|
+
|
|
106
|
+
protected constructor(bbox?: BBox2DLiteral | BBox3DLiteral) {
|
|
107
|
+
this.bbox = bbox
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Predicate to determine if the GeoJSON object is 2-dimensional.
|
|
112
|
+
*/
|
|
113
|
+
public is2D() {
|
|
114
|
+
return is2DBBox(this.bbox)
|
|
115
|
+
}
|
|
116
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mailwoman/spatial",
|
|
3
|
+
"version": "4.9.0",
|
|
4
|
+
"description": "Spatial analysis, geocoding, and other geo-related utilities.",
|
|
5
|
+
"license": "AGPL-3.0",
|
|
6
|
+
"contributors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Teffen Ellis",
|
|
9
|
+
"email": "teffen@sister.software"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
"./schema/*.json": "./dist/schema/*.json",
|
|
16
|
+
"./sdk": "./out/sdk/index.js",
|
|
17
|
+
".": "./out/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@mailwoman/core": "workspace:*",
|
|
21
|
+
"geo-coordinates-parser": "^1.7.4",
|
|
22
|
+
"h3-js": "^4.4.0",
|
|
23
|
+
"wkx": "^0.5.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@googlemaps/google-maps-services-js": "^3.4.2",
|
|
27
|
+
"@types/google.maps": "^3.65.1",
|
|
28
|
+
"type-fest": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"@googlemaps/google-maps-services-js": "^3.4.2",
|
|
32
|
+
"@types/google.maps": "^3.65.1"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.5.1"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"geo",
|
|
39
|
+
"spatial"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/position.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* This file contains types and utilities for working with geographic positions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type LatLngLiteral } from "@googlemaps/google-maps-services-js"
|
|
10
|
+
import { GeoPoint, type GeoPointInput } from "@mailwoman/spatial"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An ordered pair of coordinates in the form of [longitude, latitude].
|
|
14
|
+
*
|
|
15
|
+
* Note that unlike the typical order, GeoJSON coordinates are in the order of [longitude, latitude]
|
|
16
|
+
* to match the order of [x, y] in Cartesian coordinates.
|
|
17
|
+
*
|
|
18
|
+
* @category Position
|
|
19
|
+
* @category GeoJSON
|
|
20
|
+
* @see {@linkcode Coordinates3D} for 3D coordinates.
|
|
21
|
+
*/
|
|
22
|
+
export type Coordinates2D = [
|
|
23
|
+
/**
|
|
24
|
+
* The longitude of the point, i.e. the x-coordinate.
|
|
25
|
+
*
|
|
26
|
+
* @minimum -180
|
|
27
|
+
* @maximum 180
|
|
28
|
+
*/
|
|
29
|
+
longitude: number,
|
|
30
|
+
/**
|
|
31
|
+
* The latitude of the point, i.e. the y-coordinate.
|
|
32
|
+
*
|
|
33
|
+
* @minimum -90
|
|
34
|
+
* @maximum 90
|
|
35
|
+
*/
|
|
36
|
+
latitude: number,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Orders the given coordinates as [longitude, latitude].
|
|
41
|
+
*
|
|
42
|
+
* This is useful when converting into GeoJSON format.
|
|
43
|
+
*
|
|
44
|
+
* @category GeoJSON
|
|
45
|
+
* @category Position
|
|
46
|
+
*/
|
|
47
|
+
export function orderCoordPairToGeoJSON([latitude, longitude]: [number, number]): Coordinates2D {
|
|
48
|
+
return [longitude, latitude]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Orders the given coordinates as [latitude, longitude].
|
|
53
|
+
*
|
|
54
|
+
* This is useful when converting into Google Maps format.
|
|
55
|
+
*
|
|
56
|
+
* @category GeoJSON
|
|
57
|
+
* @category Position
|
|
58
|
+
*/
|
|
59
|
+
export function orderGeoJSONToCoordPair([longitude, latitude]: Coordinates2D): [number, number] {
|
|
60
|
+
return [latitude, longitude]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Given an input which appears to be reversed GeoJSON coordinates (i.e. [latitude, longitude]),
|
|
65
|
+
* returns the coordinates in the correct order of [longitude, latitude].
|
|
66
|
+
*
|
|
67
|
+
* Note that this is a heuristic and is only accurate for North American coordinates.
|
|
68
|
+
*
|
|
69
|
+
* @category GeoJSON
|
|
70
|
+
* @category Position
|
|
71
|
+
*/
|
|
72
|
+
export function inferGeoJSONCoordOrder([coordA, coordB]: [number, number]): Coordinates2D {
|
|
73
|
+
// Latitude values typically range from -90 to 90
|
|
74
|
+
const isCoordALat = coordA >= -90 && coordA <= 90
|
|
75
|
+
const isCoordBLat = coordB >= -90 && coordB <= 90
|
|
76
|
+
|
|
77
|
+
if (isCoordALat && !isCoordBLat) {
|
|
78
|
+
// coordA is latitude, coordB is longitude
|
|
79
|
+
return [coordB, coordA]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isCoordALat && isCoordBLat) {
|
|
83
|
+
// coordB is latitude, coordA is longitude
|
|
84
|
+
return [coordA, coordB]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// In case both appear to be latitudes (unlikely) or longitudes (out of range for US),
|
|
88
|
+
// assume coordA is longitude and coordB is latitude as default.
|
|
89
|
+
return [coordA, coordB]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* An ordered triple of coordinates in the form of [longitude, latitude, altitude].
|
|
94
|
+
*
|
|
95
|
+
* @category Position
|
|
96
|
+
* @category GeoJSON
|
|
97
|
+
* @see {@linkcode Coordinates2D} for 2D coordinates.
|
|
98
|
+
*/
|
|
99
|
+
export type Coordinates3D = [
|
|
100
|
+
/**
|
|
101
|
+
* The longitude of the point, i.e. the x-coordinate.
|
|
102
|
+
*
|
|
103
|
+
* @minimum -180
|
|
104
|
+
* @maximum 180
|
|
105
|
+
*/
|
|
106
|
+
longitude: number,
|
|
107
|
+
/**
|
|
108
|
+
* The latitude of the point, i.e. the y-coordinate.
|
|
109
|
+
*
|
|
110
|
+
* @minimum -90
|
|
111
|
+
* @maximum 90
|
|
112
|
+
*/
|
|
113
|
+
latitude: number,
|
|
114
|
+
/**
|
|
115
|
+
* The altitude of the point, i.e. the z-coordinate.
|
|
116
|
+
*/
|
|
117
|
+
altitude: number,
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A record of internal coordinates, typically used by the US Census.
|
|
122
|
+
*/
|
|
123
|
+
export type InternalPointCoordinates = {
|
|
124
|
+
/**
|
|
125
|
+
* Internal Longitude (X) Coordinates
|
|
126
|
+
*
|
|
127
|
+
* @minimum -180
|
|
128
|
+
* @maximum 180
|
|
129
|
+
*/
|
|
130
|
+
x: number
|
|
131
|
+
/**
|
|
132
|
+
* Internal Latitude (Y) Coordinates
|
|
133
|
+
*
|
|
134
|
+
* @minimum -90
|
|
135
|
+
* @maximum 90
|
|
136
|
+
*/
|
|
137
|
+
y: number
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Type-predicate to determine if the given input is a GeoJSON Point geometry.
|
|
142
|
+
*
|
|
143
|
+
* @category Type Predicates
|
|
144
|
+
* @category GeoJSON
|
|
145
|
+
*/
|
|
146
|
+
export function isCoordPairLiteral(input: unknown): input is [number, number] | [number, number, number] {
|
|
147
|
+
if (!Array.isArray(input)) return false
|
|
148
|
+
|
|
149
|
+
if (input.length !== 2 && input.length !== 3) return false
|
|
150
|
+
|
|
151
|
+
return input.every((coord) => typeof coord === "number")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Type-predicate to determine if the given input is a {@linkcode LatLngLiteral} object.
|
|
156
|
+
*
|
|
157
|
+
* @category Position
|
|
158
|
+
* @category Type Predicates
|
|
159
|
+
* @see {@link https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngLiteral Google Maps API Documentation}
|
|
160
|
+
*/
|
|
161
|
+
export function isGoogleMapsLatLngLiteral(input: unknown): input is LatLngLiteral {
|
|
162
|
+
if (!input || typeof input !== "object") return false
|
|
163
|
+
|
|
164
|
+
if (!Object.hasOwn(input, "lat") || !Object.hasOwn(input, "lng")) return false
|
|
165
|
+
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Type-predicate to determine if the given input is a {@linkcode InternalPointCoordinates} object.
|
|
171
|
+
*
|
|
172
|
+
* @category Position
|
|
173
|
+
* @category Type Predicates
|
|
174
|
+
*/
|
|
175
|
+
export function isInterpolatedCoordinates(input: unknown): input is InternalPointCoordinates {
|
|
176
|
+
if (!input || typeof input !== "object") return false
|
|
177
|
+
|
|
178
|
+
if (!("x" in input)) return false
|
|
179
|
+
if (!("y" in input)) return false
|
|
180
|
+
|
|
181
|
+
return typeof input.x === "number" && typeof input.y === "number"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Given a longitude value, wraps it to the range of [-180, 180].
|
|
186
|
+
*
|
|
187
|
+
* This is useful when normalizing longitude values.
|
|
188
|
+
*
|
|
189
|
+
* @category Position
|
|
190
|
+
* @param longitude The longitude value to wrap.
|
|
191
|
+
*/
|
|
192
|
+
export function wrapLongitude(longitude: number): number {
|
|
193
|
+
return ((((longitude + 180) % 360) + 360) % 360) - 180
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Given a latitude value, clamps it to the range of [-90, 90].
|
|
198
|
+
*
|
|
199
|
+
* This is useful when normalizing latitude values.
|
|
200
|
+
*
|
|
201
|
+
* @category Position
|
|
202
|
+
* @param value The latitude value to clamp.
|
|
203
|
+
*/
|
|
204
|
+
export function clampLatitude(value: number): number {
|
|
205
|
+
return Math.min(90, Math.max(-90, value))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Conversion factors for converting between degrees and radians.
|
|
210
|
+
*
|
|
211
|
+
* @category Position
|
|
212
|
+
* @see {@link https://en.wikipedia.org/wiki/Radian Wikipedia: Radian}
|
|
213
|
+
* @see {@link https://en.wikipedia.org/wiki/Degree_(angle) Wikipedia: Degree (angle)}
|
|
214
|
+
*/
|
|
215
|
+
export const ConversionFactor = {
|
|
216
|
+
DegreesToRadians: (Math.PI / 180) as unknown as 0.01745329251,
|
|
217
|
+
RadiansToDegrees: (180 / Math.PI) as unknown as 57.2957795131,
|
|
218
|
+
} as const
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Available conversion units for the radius of the Earth.
|
|
222
|
+
*/
|
|
223
|
+
export type EarthRadiusUnit = "km" | "miles" | "meters"
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Radius of the Earth in various units
|
|
227
|
+
*/
|
|
228
|
+
const RADII = {
|
|
229
|
+
km: 6371,
|
|
230
|
+
miles: 3958.8,
|
|
231
|
+
meters: 6371000,
|
|
232
|
+
} as const satisfies Record<EarthRadiusUnit, number>
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Calculate the distance between two points on the Earth's surface.
|
|
236
|
+
*
|
|
237
|
+
* @category Position
|
|
238
|
+
* @param point1 The first point to calculate the distance from.
|
|
239
|
+
* @param point2 The second point to calculate the distance to.
|
|
240
|
+
* @param unit The unit of measurement to return the distance in.
|
|
241
|
+
*
|
|
242
|
+
* @returns The distance between the two points in the specified unit.
|
|
243
|
+
*/
|
|
244
|
+
export function haversine(point1: GeoPointInput, point2: GeoPointInput, unit: EarthRadiusUnit = "km"): number {
|
|
245
|
+
const p1 = GeoPoint.from(point1)
|
|
246
|
+
const p2 = GeoPoint.from(point2)
|
|
247
|
+
|
|
248
|
+
if (!p1 || !p2) return NaN
|
|
249
|
+
|
|
250
|
+
const lat1 = p1.latitude
|
|
251
|
+
const lon1 = p1.longitude
|
|
252
|
+
const lat2 = p2.latitude
|
|
253
|
+
const lon2 = p2.longitude
|
|
254
|
+
|
|
255
|
+
const dLat = (lat2 - lat1) * ConversionFactor.DegreesToRadians
|
|
256
|
+
|
|
257
|
+
const dLon = (lon2 - lon1) * ConversionFactor.DegreesToRadians
|
|
258
|
+
|
|
259
|
+
const a =
|
|
260
|
+
Math.pow(Math.sin(dLat / 2), 2) +
|
|
261
|
+
Math.cos(lat1 * ConversionFactor.DegreesToRadians) *
|
|
262
|
+
Math.cos(lat2 * ConversionFactor.DegreesToRadians) *
|
|
263
|
+
Math.pow(Math.sin(dLon / 2), 2)
|
|
264
|
+
|
|
265
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
|
266
|
+
const radius = RADII[unit]
|
|
267
|
+
|
|
268
|
+
return radius * c
|
|
269
|
+
}
|
package/projection.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A coordinate system used to represent the Earth's surface.
|
|
9
|
+
*
|
|
10
|
+
* @category Geo
|
|
11
|
+
*/
|
|
12
|
+
export enum CoordinateProjection {
|
|
13
|
+
/**
|
|
14
|
+
* Coordinate system used in Google Earth and GSP systems.
|
|
15
|
+
*
|
|
16
|
+
* It represents Earth as a three-dimensional ellipsoid.
|
|
17
|
+
*/
|
|
18
|
+
WGS84 = "4326",
|
|
19
|
+
/**
|
|
20
|
+
* North American Datum 1983, a geodetic reference system used in the TIGER/Line data.
|
|
21
|
+
*/
|
|
22
|
+
NAD83 = "4269",
|
|
23
|
+
}
|
package/regions/codes.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RegionName } from "./names.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* M.49 region codes for continents.
|
|
11
|
+
*/
|
|
12
|
+
export const RegionCodes = [
|
|
13
|
+
// ---
|
|
14
|
+
"AF",
|
|
15
|
+
"AN",
|
|
16
|
+
"AS",
|
|
17
|
+
"EU",
|
|
18
|
+
"NA",
|
|
19
|
+
"OC",
|
|
20
|
+
"SA",
|
|
21
|
+
] as const satisfies readonly string[]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* M.49 region code for a specific continent.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type RegionCode = (typeof RegionCodes)[number]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Continent codes to their full names.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export const RegionCodeToNameRecord = {
|
|
36
|
+
AF: "Africa",
|
|
37
|
+
AN: "Antarctica",
|
|
38
|
+
AS: "Asia",
|
|
39
|
+
EU: "Europe",
|
|
40
|
+
NA: "North America",
|
|
41
|
+
OC: "Oceania",
|
|
42
|
+
SA: "South America",
|
|
43
|
+
} as const satisfies Record<RegionCode, RegionName>
|
package/regions/index.ts
ADDED
package/regions/names.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A list of geographic regions, i.e. continents.
|
|
9
|
+
*
|
|
10
|
+
* @category Geographic
|
|
11
|
+
*/
|
|
12
|
+
export const RegionNames = [
|
|
13
|
+
"Africa",
|
|
14
|
+
"Antarctica",
|
|
15
|
+
"Asia",
|
|
16
|
+
"Europe",
|
|
17
|
+
"North America",
|
|
18
|
+
"Oceania",
|
|
19
|
+
"South America",
|
|
20
|
+
] as const satisfies readonly string[]
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A region of the world, i.e. a continent.
|
|
24
|
+
*
|
|
25
|
+
* @category Geographic
|
|
26
|
+
* @title Geographic Region
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export type RegionName = (typeof RegionNames)[number]
|