openskidata-format 11.1.0 → 12.1.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/dist/Lift.d.ts +2 -0
- package/dist/Lift.js +4 -4
- package/dist/Lift.js.map +1 -1
- package/dist/Run.d.ts +2 -0
- package/dist/Run.js.map +1 -1
- package/dist/SkiArea.d.ts +2 -0
- package/dist/SkiArea.js.map +1 -1
- package/dist/Spot.d.ts +2 -0
- package/dist/Spot.js.map +1 -1
- package/dist/ViewportHint.d.ts +33 -0
- package/dist/ViewportHint.js +153 -0
- package/dist/ViewportHint.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/testUtils.d.ts +2 -0
- package/dist/testUtils.js +7 -0
- package/dist/testUtils.js.map +1 -0
- package/package.json +1 -1
- package/src/ElevationProfile.test.ts +5 -0
- package/src/Lift.test.ts +3 -0
- package/src/Lift.ts +6 -4
- package/src/Run.ts +2 -0
- package/src/SkiArea.ts +2 -0
- package/src/SlopeGradingScale.test.ts +5 -0
- package/src/Spot.ts +2 -0
- package/src/ViewportHint.ts +208 -0
- package/src/index.ts +1 -0
- package/src/testUtils.ts +5 -0
package/dist/Lift.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { SkiAreaSummaryFeature } from './SkiArea';
|
|
|
5
5
|
import { Source } from './Source';
|
|
6
6
|
import { LiftStationSpotFeature } from './Spot';
|
|
7
7
|
import { Status } from './Status';
|
|
8
|
+
import { ViewportHint } from './ViewportHint';
|
|
8
9
|
export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>;
|
|
9
10
|
export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
|
|
10
11
|
/**
|
|
@@ -72,6 +73,7 @@ export type LiftProperties = {
|
|
|
72
73
|
websites: string[];
|
|
73
74
|
wikidataID: string | null;
|
|
74
75
|
places: Place[];
|
|
76
|
+
viewportHint: ViewportHint;
|
|
75
77
|
};
|
|
76
78
|
export declare enum LiftType {
|
|
77
79
|
CableCar = "cable_car",
|
package/dist/Lift.js
CHANGED
|
@@ -96,17 +96,17 @@ function getFormattedLiftType(liftType) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
function getLiftColor(status) {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
99
|
+
const brightRedColor = 'hsl(0, 82%, 42%)';
|
|
100
|
+
const dimRedColor = 'hsl(0, 53%, 42%)';
|
|
101
101
|
switch (status) {
|
|
102
102
|
case Status_1.Status.Disused:
|
|
103
103
|
case Status_1.Status.Abandoned:
|
|
104
|
-
return
|
|
104
|
+
return dimRedColor;
|
|
105
105
|
case Status_1.Status.Proposed:
|
|
106
106
|
case Status_1.Status.Planned:
|
|
107
107
|
case Status_1.Status.Construction:
|
|
108
108
|
case Status_1.Status.Operating:
|
|
109
|
-
return
|
|
109
|
+
return brightRedColor;
|
|
110
110
|
default:
|
|
111
111
|
return (0, exhaustiveMatchingGuard_1.exhaustiveMatchingGuard)(status);
|
|
112
112
|
}
|
package/dist/Lift.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lift.js","sourceRoot":"","sources":["../src/Lift.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Lift.js","sourceRoot":"","sources":["../src/Lift.ts"],"names":[],"mappings":"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"}
|
package/dist/Run.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { SkiAreaSummaryFeature } from './SkiArea';
|
|
|
7
7
|
import { SnowCoverHistory } from './SnowCoverHistory';
|
|
8
8
|
import { Source } from './Source';
|
|
9
9
|
import { Status } from './Status';
|
|
10
|
+
import { ViewportHint } from './ViewportHint';
|
|
10
11
|
export type RunGeometry = GeoJSON.LineString | GeoJSON.Polygon;
|
|
11
12
|
/**
|
|
12
13
|
* Represents a segment of a ski run/trail.
|
|
@@ -79,6 +80,7 @@ export type RunProperties = {
|
|
|
79
80
|
wikidataID: string | null;
|
|
80
81
|
places: Place[];
|
|
81
82
|
snowCoverHistory?: SnowCoverHistory;
|
|
83
|
+
viewportHint: ViewportHint;
|
|
82
84
|
};
|
|
83
85
|
export declare enum RunUse {
|
|
84
86
|
Downhill = "downhill",
|
package/dist/Run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Run.js","sourceRoot":"","sources":["../src/Run.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Run.js","sourceRoot":"","sources":["../src/Run.ts"],"names":[],"mappings":"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"}
|
package/dist/SkiArea.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { RunDifficultyConvention } from './RunDifficultyConvention';
|
|
|
6
6
|
import { SnowCoverHistory } from './SnowCoverHistory';
|
|
7
7
|
import { Source } from './Source';
|
|
8
8
|
import { Status } from './Status';
|
|
9
|
+
import { ViewportHint } from './ViewportHint';
|
|
9
10
|
/**
|
|
10
11
|
* A ski area feature is derived from several sources:
|
|
11
12
|
* - OpenStreetMap site=piste relations (must contain at least one lift or run of any kind)
|
|
@@ -48,6 +49,7 @@ export type SkiAreaProperties = SkiAreaSummaryProperties & {
|
|
|
48
49
|
websites: string[];
|
|
49
50
|
wikidataID: string | null;
|
|
50
51
|
places: Place[];
|
|
52
|
+
viewportHint: ViewportHint;
|
|
51
53
|
};
|
|
52
54
|
export type SkiAreaSummaryProperties = {
|
|
53
55
|
type: FeatureType.SkiArea;
|
package/dist/SkiArea.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SkiArea.js","sourceRoot":"","sources":["../src/SkiArea.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"SkiArea.js","sourceRoot":"","sources":["../src/SkiArea.ts"],"names":[],"mappings":";;;AAwEA,IAAY,eAGX;AAHD,WAAY,eAAe;IACzB,wCAAqB,CAAA;IACrB,oCAAiB,CAAA;AACnB,CAAC,EAHW,eAAe,+BAAf,eAAe,QAG1B"}
|
package/dist/Spot.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FeatureType } from './FeatureType';
|
|
|
3
3
|
import { Place } from './Place';
|
|
4
4
|
import { SkiAreaSummaryFeature } from './SkiArea';
|
|
5
5
|
import { Source } from './Source';
|
|
6
|
+
import { ViewportHint } from './ViewportHint';
|
|
6
7
|
/**
|
|
7
8
|
* A GeoJSON feature representing a spot (point of interest) in or around a ski area.
|
|
8
9
|
*
|
|
@@ -30,6 +31,7 @@ export type SpotBaseProperties = {
|
|
|
30
31
|
skiAreas: SkiAreaSummaryFeature[];
|
|
31
32
|
sources: Source[];
|
|
32
33
|
places: Place[];
|
|
34
|
+
viewportHint: ViewportHint;
|
|
33
35
|
};
|
|
34
36
|
/**
|
|
35
37
|
* Types of spots that can be found in or around ski areas.
|
package/dist/Spot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;AAiDA;;GAEG;AACH,IAAY,QAMX;AAND,WAAY,QAAQ;IAClB,iCAAqB,CAAA;IACrB,wCAA4B,CAAA;IAC5B,2EAA+D,CAAA;IAC/D,+EAAmE,CAAA;IACnE,iCAAqB,CAAA;AACvB,CAAC,EANW,QAAQ,wBAAR,QAAQ,QAMnB;AAaD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,gCAAS,CAAA;IACT,8CAAuB,CAAA;AACzB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B;AAuBD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,kCAAW,CAAA;IACX,wCAAiB,CAAA;AACnB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as GeoJSON from 'geojson';
|
|
2
|
+
/**
|
|
3
|
+
* Pre-computed viewport positioning data stored on every feature's properties.
|
|
4
|
+
* Camera space: Y axis points uphill (along bearing), X axis points screen-right.
|
|
5
|
+
* Metric values are in that rotated frame with elevation-adjusted Y positions
|
|
6
|
+
* (at 45° pitch, Δe metres above mean elevation shifts Y by Δe metres).
|
|
7
|
+
*/
|
|
8
|
+
export type ViewportHint = {
|
|
9
|
+
/** Geographic centre of the rotated bounding box as [lng, lat]. */
|
|
10
|
+
center: [number, number];
|
|
11
|
+
/**
|
|
12
|
+
* Uphill bearing in degrees clockwise from north. null when elevation variation is
|
|
13
|
+
* insufficient to determine orientation — client should use top-down view (pitch 0, bearing 0).
|
|
14
|
+
*/
|
|
15
|
+
bearing: number | null;
|
|
16
|
+
/** Bounding box width along the camera X axis, in metres. */
|
|
17
|
+
rotatedWidthMeters: number;
|
|
18
|
+
/** Bounding box height along the camera Y axis, in metres (elevation-adjusted). */
|
|
19
|
+
rotatedHeightMeters: number;
|
|
20
|
+
/**
|
|
21
|
+
* Most-downhill edge of the elevation-adjusted bounding box in camera-Y metres, relative
|
|
22
|
+
* to mean feature elevation. Used to compute the perspective centre shift at 45° pitch:
|
|
23
|
+
* look-at point = minCameraY + nearFraction * rotatedHeightMeters, where
|
|
24
|
+
* nearFraction = nearGround / (nearGround + farGround) from camera pitch and vFOV.
|
|
25
|
+
*/
|
|
26
|
+
minCameraY: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Computes a ViewportHint from one or more GeoJSON geometries.
|
|
30
|
+
* All geometry types are accepted; elevation is read from the Z coordinate (defaults to 0).
|
|
31
|
+
* Throws if no coordinates are found.
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeViewportHint(geometries: GeoJSON.Geometry[]): ViewportHint;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeViewportHint = computeViewportHint;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const distance_1 = tslib_1.__importDefault(require("@turf/distance"));
|
|
6
|
+
/**
|
|
7
|
+
* Computes a ViewportHint from one or more GeoJSON geometries.
|
|
8
|
+
* All geometry types are accepted; elevation is read from the Z coordinate (defaults to 0).
|
|
9
|
+
* Throws if no coordinates are found.
|
|
10
|
+
*/
|
|
11
|
+
function computeViewportHint(geometries) {
|
|
12
|
+
// Collect all coordinates (for bbox + elevation stats) and consecutive segment pairs
|
|
13
|
+
// (for the downhill bearing calculation).
|
|
14
|
+
const allCoords = [];
|
|
15
|
+
const bearingSegments = [];
|
|
16
|
+
for (const geom of geometries) {
|
|
17
|
+
collectGeometry(geom, allCoords, bearingSegments);
|
|
18
|
+
}
|
|
19
|
+
if (allCoords.length === 0)
|
|
20
|
+
throw new Error('computeViewportHint: no coordinates found in provided geometries');
|
|
21
|
+
let minLng = Infinity, maxLng = -Infinity;
|
|
22
|
+
let minLat = Infinity, maxLat = -Infinity;
|
|
23
|
+
for (const [lng, lat] of allCoords) {
|
|
24
|
+
if (lng < minLng)
|
|
25
|
+
minLng = lng;
|
|
26
|
+
if (lng > maxLng)
|
|
27
|
+
maxLng = lng;
|
|
28
|
+
if (lat < minLat)
|
|
29
|
+
minLat = lat;
|
|
30
|
+
if (lat > maxLat)
|
|
31
|
+
maxLat = lat;
|
|
32
|
+
}
|
|
33
|
+
const centerLng = (minLng + maxLng) / 2;
|
|
34
|
+
const centerLat = (minLat + maxLat) / 2;
|
|
35
|
+
// Weight each segment's downhill direction by length to find dominant bearing.
|
|
36
|
+
let sumSin = 0, sumCos = 0;
|
|
37
|
+
for (const [[lng0, lat0, elev0], [lng1, lat1, elev1]] of bearingSegments) {
|
|
38
|
+
if (elev0 === elev1)
|
|
39
|
+
continue;
|
|
40
|
+
const [fromLng, fromLat, toLng, toLat] = elev0 > elev1
|
|
41
|
+
? [lng0, lat0, lng1, lat1]
|
|
42
|
+
: [lng1, lat1, lng0, lat0];
|
|
43
|
+
const bear = greatCircleBearing(fromLat, fromLng, toLat, toLng);
|
|
44
|
+
const len = (0, distance_1.default)([lng0, lat0], [lng1, lat1], { units: 'meters' });
|
|
45
|
+
const bearingRad = (bear * Math.PI) / 180;
|
|
46
|
+
sumSin += Math.sin(bearingRad) * len;
|
|
47
|
+
sumCos += Math.cos(bearingRad) * len;
|
|
48
|
+
}
|
|
49
|
+
// Uphill bearing = downhill + 180°; null if no elevation variation found.
|
|
50
|
+
// A small offset is added so the camera is angled slightly to the side of the fall line,
|
|
51
|
+
// giving a better 3-D profile of the terrain rather than pointing straight uphill.
|
|
52
|
+
const bearingSideOffsetDeg = 30;
|
|
53
|
+
let mapBearing = null;
|
|
54
|
+
if (sumSin !== 0 || sumCos !== 0) {
|
|
55
|
+
const dominantDownhill = (Math.atan2(sumSin, sumCos) * 180) / Math.PI;
|
|
56
|
+
const normalizedDownhill = ((dominantDownhill % 360) + 360) % 360;
|
|
57
|
+
mapBearing = (normalizedDownhill + 180 + bearingSideOffsetDeg) % 360;
|
|
58
|
+
}
|
|
59
|
+
const metersPerDeg = 111320;
|
|
60
|
+
const cosLat = Math.cos(centerLat * (Math.PI / 180));
|
|
61
|
+
const bearingRad = ((mapBearing ?? 0) * Math.PI) / 180;
|
|
62
|
+
let elevationSum = 0;
|
|
63
|
+
for (const [, , elev] of allCoords)
|
|
64
|
+
elevationSum += elev;
|
|
65
|
+
const meanElevation = elevationSum / allCoords.length;
|
|
66
|
+
let minCameraX = Infinity, maxCameraX = -Infinity;
|
|
67
|
+
let minCameraY = Infinity, maxCameraY = -Infinity;
|
|
68
|
+
for (const [lng, lat, elev] of allCoords) {
|
|
69
|
+
const dx = (lng - centerLng) * cosLat * metersPerDeg;
|
|
70
|
+
const dy = (lat - centerLat) * metersPerDeg;
|
|
71
|
+
const cx = dx * Math.cos(bearingRad) - dy * Math.sin(bearingRad);
|
|
72
|
+
const cy = dx * Math.sin(bearingRad) + dy * Math.cos(bearingRad);
|
|
73
|
+
// At 45° pitch, elevation above mean shifts apparent Y by the same amount (tan 45° = 1).
|
|
74
|
+
const cyEffective = cy + (elev - meanElevation);
|
|
75
|
+
if (cx < minCameraX)
|
|
76
|
+
minCameraX = cx;
|
|
77
|
+
if (cx > maxCameraX)
|
|
78
|
+
maxCameraX = cx;
|
|
79
|
+
if (cyEffective < minCameraY)
|
|
80
|
+
minCameraY = cyEffective;
|
|
81
|
+
if (cyEffective > maxCameraY)
|
|
82
|
+
maxCameraY = cyEffective;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
center: [centerLng, centerLat],
|
|
86
|
+
bearing: mapBearing,
|
|
87
|
+
rotatedWidthMeters: maxCameraX - minCameraX,
|
|
88
|
+
rotatedHeightMeters: maxCameraY - minCameraY,
|
|
89
|
+
minCameraY,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Recursively extracts coordinates and segment pairs from a geometry. */
|
|
93
|
+
function collectGeometry(geom, allCoords, bearingSegments) {
|
|
94
|
+
switch (geom.type) {
|
|
95
|
+
case 'Point': {
|
|
96
|
+
const [lng, lat, elev = 0] = geom.coordinates;
|
|
97
|
+
allCoords.push([lng, lat, elev]);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case 'MultiPoint': {
|
|
101
|
+
for (const pos of geom.coordinates) {
|
|
102
|
+
const [lng, lat, elev = 0] = pos;
|
|
103
|
+
allCoords.push([lng, lat, elev]);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'LineString':
|
|
108
|
+
collectPositions(geom.coordinates, allCoords, bearingSegments);
|
|
109
|
+
break;
|
|
110
|
+
case 'MultiLineString':
|
|
111
|
+
for (const line of geom.coordinates) {
|
|
112
|
+
collectPositions(line, allCoords, bearingSegments);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
case 'Polygon':
|
|
116
|
+
for (const ring of geom.coordinates) {
|
|
117
|
+
collectPositions(ring, allCoords, bearingSegments);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'MultiPolygon':
|
|
121
|
+
for (const polygon of geom.coordinates) {
|
|
122
|
+
for (const ring of polygon) {
|
|
123
|
+
collectPositions(ring, allCoords, bearingSegments);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'GeometryCollection':
|
|
128
|
+
for (const g of geom.geometries) {
|
|
129
|
+
collectGeometry(g, allCoords, bearingSegments);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Adds positions to allCoords and consecutive pairs to bearingSegments. Z defaults to 0. */
|
|
135
|
+
function collectPositions(positions, allCoords, bearingSegments) {
|
|
136
|
+
const elevated = positions.map(([lng, lat, elev = 0]) => [lng, lat, elev]);
|
|
137
|
+
for (const coord of elevated)
|
|
138
|
+
allCoords.push(coord);
|
|
139
|
+
for (let i = 0; i < elevated.length - 1; i++) {
|
|
140
|
+
bearingSegments.push([elevated[i], elevated[i + 1]]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Great-circle bearing from (lat1, lng1) to (lat2, lng2), in degrees 0–360. */
|
|
144
|
+
function greatCircleBearing(lat1, lng1, lat2, lng2) {
|
|
145
|
+
const toRad = (d) => (d * Math.PI) / 180;
|
|
146
|
+
const φ1 = toRad(lat1);
|
|
147
|
+
const φ2 = toRad(lat2);
|
|
148
|
+
const Δλ = toRad(lng2 - lng1);
|
|
149
|
+
const y = Math.sin(Δλ) * Math.cos(φ2);
|
|
150
|
+
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
|
|
151
|
+
return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=ViewportHint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewportHint.js","sourceRoot":"","sources":["../src/ViewportHint.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,4 +14,5 @@ tslib_1.__exportStar(require("./SnowCoverHistory"), exports);
|
|
|
14
14
|
tslib_1.__exportStar(require("./Source"), exports);
|
|
15
15
|
tslib_1.__exportStar(require("./Spot"), exports);
|
|
16
16
|
tslib_1.__exportStar(require("./Status"), exports);
|
|
17
|
+
tslib_1.__exportStar(require("./ViewportHint"), exports);
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,wDAA6B;AAC7B,iDAAsB;AACtB,8DAAmC;AACnC,kDAAuB;AACvB,gDAAqB;AACrB,oEAAyC;AACzC,8DAAmC;AACnC,oDAAyB;AACzB,6DAAkC;AAClC,mDAAwB;AACxB,iDAAsB;AACtB,mDAAwB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,wDAA6B;AAC7B,iDAAsB;AACtB,8DAAmC;AACnC,kDAAuB;AACvB,gDAAqB;AACrB,oEAAyC;AACzC,8DAAmC;AACnC,oDAAyB;AACzB,6DAAkC;AAClC,mDAAwB;AACxB,iDAAsB;AACtB,mDAAwB;AACxB,yDAA8B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mockViewportHint = mockViewportHint;
|
|
4
|
+
function mockViewportHint() {
|
|
5
|
+
return { center: [0, 0], bearing: null, rotatedWidthMeters: 0, rotatedHeightMeters: 0, minCameraY: 0 };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=testUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.js","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":";;AAEA,4CAEC;AAFD,SAAgB,gBAAgB;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,mBAAmB,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;AACxG,CAAC"}
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { getLiftElevationData, LiftFeature, LiftType } from './Lift'
|
|
|
10
10
|
import { getRunElevationData, RunDifficulty, RunFeature, RunUse } from './Run'
|
|
11
11
|
import { RunDifficultyConvention } from './RunDifficultyConvention'
|
|
12
12
|
import { Status } from './Status'
|
|
13
|
+
import { mockViewportHint } from './testUtils'
|
|
13
14
|
|
|
14
15
|
describe('ElevationProfile', () => {
|
|
15
16
|
describe('getAscentAndDescent', () => {
|
|
@@ -277,6 +278,7 @@ describe('ElevationProfile', () => {
|
|
|
277
278
|
wikidataID: null,
|
|
278
279
|
tunnel: null,
|
|
279
280
|
places: [],
|
|
281
|
+
viewportHint: mockViewportHint(),
|
|
280
282
|
},
|
|
281
283
|
}
|
|
282
284
|
|
|
@@ -317,6 +319,7 @@ describe('ElevationProfile', () => {
|
|
|
317
319
|
wikidataID: null,
|
|
318
320
|
tunnel: null,
|
|
319
321
|
places: [],
|
|
322
|
+
viewportHint: mockViewportHint(),
|
|
320
323
|
},
|
|
321
324
|
}
|
|
322
325
|
|
|
@@ -361,6 +364,7 @@ describe('ElevationProfile', () => {
|
|
|
361
364
|
wikidataID: null,
|
|
362
365
|
tunnel: null,
|
|
363
366
|
places: [],
|
|
367
|
+
viewportHint: mockViewportHint(),
|
|
364
368
|
},
|
|
365
369
|
}
|
|
366
370
|
|
|
@@ -408,6 +412,7 @@ describe('ElevationProfile', () => {
|
|
|
408
412
|
wikidataID: null,
|
|
409
413
|
tunnel: null,
|
|
410
414
|
places: [],
|
|
415
|
+
viewportHint: mockViewportHint(),
|
|
411
416
|
},
|
|
412
417
|
geometry: {
|
|
413
418
|
type: 'LineString',
|
package/src/Lift.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FeatureType } from './FeatureType';
|
|
2
2
|
import { getLiftElevationData, LiftFeature, LiftType, getFormattedLiftType, getLiftColor } from './Lift';
|
|
3
|
+
import { mockViewportHint } from './testUtils';
|
|
3
4
|
import { LiftStationPosition, LiftStationSpotFeature, SpotType } from './Spot';
|
|
4
5
|
import { Status } from './Status';
|
|
5
6
|
|
|
@@ -41,6 +42,7 @@ function makeLiftFeature(overrides: {
|
|
|
41
42
|
websites: [],
|
|
42
43
|
wikidataID: null,
|
|
43
44
|
places: [],
|
|
45
|
+
viewportHint: mockViewportHint(),
|
|
44
46
|
},
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -64,6 +66,7 @@ function makeStation(
|
|
|
64
66
|
skiAreas: [],
|
|
65
67
|
sources: [],
|
|
66
68
|
places: [],
|
|
69
|
+
viewportHint: mockViewportHint(),
|
|
67
70
|
},
|
|
68
71
|
}
|
|
69
72
|
}
|
package/src/Lift.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Source } from './Source'
|
|
|
7
7
|
import { LiftStationPosition, LiftStationSpotFeature } from './Spot'
|
|
8
8
|
import { Status } from './Status'
|
|
9
9
|
import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
|
|
10
|
+
import { ViewportHint } from './ViewportHint'
|
|
10
11
|
|
|
11
12
|
export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>
|
|
12
13
|
|
|
@@ -78,6 +79,7 @@ export type LiftProperties = {
|
|
|
78
79
|
websites: string[]
|
|
79
80
|
wikidataID: string | null
|
|
80
81
|
places: Place[]
|
|
82
|
+
viewportHint: ViewportHint
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
export enum LiftType {
|
|
@@ -199,18 +201,18 @@ export function getFormattedLiftType(liftType: LiftType): string {
|
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
export function getLiftColor(status: Status): string {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
+
const brightRedColor = 'hsl(0, 82%, 42%)'
|
|
205
|
+
const dimRedColor = 'hsl(0, 53%, 42%)'
|
|
204
206
|
|
|
205
207
|
switch (status) {
|
|
206
208
|
case Status.Disused:
|
|
207
209
|
case Status.Abandoned:
|
|
208
|
-
return
|
|
210
|
+
return dimRedColor
|
|
209
211
|
case Status.Proposed:
|
|
210
212
|
case Status.Planned:
|
|
211
213
|
case Status.Construction:
|
|
212
214
|
case Status.Operating:
|
|
213
|
-
return
|
|
215
|
+
return brightRedColor
|
|
214
216
|
default:
|
|
215
217
|
return exhaustiveMatchingGuard(status)
|
|
216
218
|
}
|
package/src/Run.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { SnowCoverHistory } from './SnowCoverHistory'
|
|
|
13
13
|
import { Source } from './Source'
|
|
14
14
|
import { Status } from './Status'
|
|
15
15
|
import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
|
|
16
|
+
import { ViewportHint } from './ViewportHint'
|
|
16
17
|
|
|
17
18
|
export type RunGeometry = GeoJSON.LineString | GeoJSON.Polygon
|
|
18
19
|
|
|
@@ -88,6 +89,7 @@ export type RunProperties = {
|
|
|
88
89
|
wikidataID: string | null
|
|
89
90
|
places: Place[]
|
|
90
91
|
snowCoverHistory?: SnowCoverHistory
|
|
92
|
+
viewportHint: ViewportHint
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export enum RunUse {
|
package/src/SkiArea.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { RunDifficultyConvention } from './RunDifficultyConvention'
|
|
|
6
6
|
import { SnowCoverHistory } from './SnowCoverHistory'
|
|
7
7
|
import { Source } from './Source'
|
|
8
8
|
import { Status } from './Status'
|
|
9
|
+
import { ViewportHint } from './ViewportHint'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A ski area feature is derived from several sources:
|
|
@@ -58,6 +59,7 @@ export type SkiAreaProperties = SkiAreaSummaryProperties & {
|
|
|
58
59
|
websites: string[]
|
|
59
60
|
wikidataID: string | null
|
|
60
61
|
places: Place[]
|
|
62
|
+
viewportHint: ViewportHint
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export type SkiAreaSummaryProperties = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RunDifficulty, RunUse } from "./Run";
|
|
2
2
|
import { getEstimatedRunDifficulty, getSlopeGradingScale } from "./SlopeGradingScale";
|
|
3
|
+
import { mockViewportHint } from "./testUtils";
|
|
3
4
|
|
|
4
5
|
describe("getEstimatedRunDifficulty", () => {
|
|
5
6
|
it("should return null for steepness below minimum threshold", () => {
|
|
@@ -30,6 +31,7 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
30
31
|
wikidataID: null,
|
|
31
32
|
tunnel: null,
|
|
32
33
|
places: [],
|
|
34
|
+
viewportHint: mockViewportHint(),
|
|
33
35
|
},
|
|
34
36
|
});
|
|
35
37
|
|
|
@@ -64,6 +66,7 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
64
66
|
wikidataID: null,
|
|
65
67
|
tunnel: null,
|
|
66
68
|
places: [],
|
|
69
|
+
viewportHint: mockViewportHint(),
|
|
67
70
|
},
|
|
68
71
|
});
|
|
69
72
|
|
|
@@ -102,6 +105,7 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
102
105
|
wikidataID: null,
|
|
103
106
|
tunnel: null,
|
|
104
107
|
places: [],
|
|
108
|
+
viewportHint: mockViewportHint(),
|
|
105
109
|
},
|
|
106
110
|
});
|
|
107
111
|
|
|
@@ -138,6 +142,7 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
138
142
|
wikidataID: null,
|
|
139
143
|
tunnel: null,
|
|
140
144
|
places: [],
|
|
145
|
+
viewportHint: mockViewportHint(),
|
|
141
146
|
},
|
|
142
147
|
});
|
|
143
148
|
|
package/src/Spot.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FeatureType } from './FeatureType'
|
|
|
3
3
|
import { Place } from './Place'
|
|
4
4
|
import { SkiAreaSummaryFeature } from './SkiArea'
|
|
5
5
|
import { Source } from './Source'
|
|
6
|
+
import { ViewportHint } from './ViewportHint'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A GeoJSON feature representing a spot (point of interest) in or around a ski area.
|
|
@@ -43,6 +44,7 @@ export type SpotBaseProperties = {
|
|
|
43
44
|
skiAreas: SkiAreaSummaryFeature[]
|
|
44
45
|
sources: Source[]
|
|
45
46
|
places: Place[]
|
|
47
|
+
viewportHint: ViewportHint
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/**
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as GeoJSON from 'geojson'
|
|
2
|
+
import distance from '@turf/distance'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pre-computed viewport positioning data stored on every feature's properties.
|
|
6
|
+
* Camera space: Y axis points uphill (along bearing), X axis points screen-right.
|
|
7
|
+
* Metric values are in that rotated frame with elevation-adjusted Y positions
|
|
8
|
+
* (at 45° pitch, Δe metres above mean elevation shifts Y by Δe metres).
|
|
9
|
+
*/
|
|
10
|
+
export type ViewportHint = {
|
|
11
|
+
/** Geographic centre of the rotated bounding box as [lng, lat]. */
|
|
12
|
+
center: [number, number]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Uphill bearing in degrees clockwise from north. null when elevation variation is
|
|
16
|
+
* insufficient to determine orientation — client should use top-down view (pitch 0, bearing 0).
|
|
17
|
+
*/
|
|
18
|
+
bearing: number | null
|
|
19
|
+
|
|
20
|
+
/** Bounding box width along the camera X axis, in metres. */
|
|
21
|
+
rotatedWidthMeters: number
|
|
22
|
+
|
|
23
|
+
/** Bounding box height along the camera Y axis, in metres (elevation-adjusted). */
|
|
24
|
+
rotatedHeightMeters: number
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Most-downhill edge of the elevation-adjusted bounding box in camera-Y metres, relative
|
|
28
|
+
* to mean feature elevation. Used to compute the perspective centre shift at 45° pitch:
|
|
29
|
+
* look-at point = minCameraY + nearFraction * rotatedHeightMeters, where
|
|
30
|
+
* nearFraction = nearGround / (nearGround + farGround) from camera pitch and vFOV.
|
|
31
|
+
*/
|
|
32
|
+
minCameraY: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Computes a ViewportHint from one or more GeoJSON geometries.
|
|
37
|
+
* All geometry types are accepted; elevation is read from the Z coordinate (defaults to 0).
|
|
38
|
+
* Throws if no coordinates are found.
|
|
39
|
+
*/
|
|
40
|
+
export function computeViewportHint(
|
|
41
|
+
geometries: GeoJSON.Geometry[],
|
|
42
|
+
): ViewportHint {
|
|
43
|
+
// Collect all coordinates (for bbox + elevation stats) and consecutive segment pairs
|
|
44
|
+
// (for the downhill bearing calculation).
|
|
45
|
+
const allCoords: [number, number, number][] = []
|
|
46
|
+
const bearingSegments: [[number, number, number], [number, number, number]][] =
|
|
47
|
+
[]
|
|
48
|
+
|
|
49
|
+
for (const geom of geometries) {
|
|
50
|
+
collectGeometry(geom, allCoords, bearingSegments)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (allCoords.length === 0) throw new Error('computeViewportHint: no coordinates found in provided geometries')
|
|
54
|
+
|
|
55
|
+
let minLng = Infinity,
|
|
56
|
+
maxLng = -Infinity
|
|
57
|
+
let minLat = Infinity,
|
|
58
|
+
maxLat = -Infinity
|
|
59
|
+
for (const [lng, lat] of allCoords) {
|
|
60
|
+
if (lng < minLng) minLng = lng
|
|
61
|
+
if (lng > maxLng) maxLng = lng
|
|
62
|
+
if (lat < minLat) minLat = lat
|
|
63
|
+
if (lat > maxLat) maxLat = lat
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const centerLng = (minLng + maxLng) / 2
|
|
67
|
+
const centerLat = (minLat + maxLat) / 2
|
|
68
|
+
|
|
69
|
+
// Weight each segment's downhill direction by length to find dominant bearing.
|
|
70
|
+
let sumSin = 0,
|
|
71
|
+
sumCos = 0
|
|
72
|
+
for (const [[lng0, lat0, elev0], [lng1, lat1, elev1]] of bearingSegments) {
|
|
73
|
+
if (elev0 === elev1) continue
|
|
74
|
+
const [fromLng, fromLat, toLng, toLat] =
|
|
75
|
+
elev0 > elev1
|
|
76
|
+
? [lng0, lat0, lng1, lat1]
|
|
77
|
+
: [lng1, lat1, lng0, lat0]
|
|
78
|
+
const bear = greatCircleBearing(fromLat, fromLng, toLat, toLng)
|
|
79
|
+
const len = distance([lng0, lat0], [lng1, lat1], { units: 'meters' })
|
|
80
|
+
const bearingRad = (bear * Math.PI) / 180
|
|
81
|
+
sumSin += Math.sin(bearingRad) * len
|
|
82
|
+
sumCos += Math.cos(bearingRad) * len
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Uphill bearing = downhill + 180°; null if no elevation variation found.
|
|
86
|
+
// A small offset is added so the camera is angled slightly to the side of the fall line,
|
|
87
|
+
// giving a better 3-D profile of the terrain rather than pointing straight uphill.
|
|
88
|
+
const bearingSideOffsetDeg = 30
|
|
89
|
+
let mapBearing: number | null = null
|
|
90
|
+
if (sumSin !== 0 || sumCos !== 0) {
|
|
91
|
+
const dominantDownhill = (Math.atan2(sumSin, sumCos) * 180) / Math.PI
|
|
92
|
+
const normalizedDownhill = ((dominantDownhill % 360) + 360) % 360
|
|
93
|
+
mapBearing = (normalizedDownhill + 180 + bearingSideOffsetDeg) % 360
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const metersPerDeg = 111320
|
|
97
|
+
const cosLat = Math.cos(centerLat * (Math.PI / 180))
|
|
98
|
+
const bearingRad = ((mapBearing ?? 0) * Math.PI) / 180
|
|
99
|
+
|
|
100
|
+
let elevationSum = 0
|
|
101
|
+
for (const [, , elev] of allCoords) elevationSum += elev
|
|
102
|
+
const meanElevation = elevationSum / allCoords.length
|
|
103
|
+
|
|
104
|
+
let minCameraX = Infinity,
|
|
105
|
+
maxCameraX = -Infinity
|
|
106
|
+
let minCameraY = Infinity,
|
|
107
|
+
maxCameraY = -Infinity
|
|
108
|
+
|
|
109
|
+
for (const [lng, lat, elev] of allCoords) {
|
|
110
|
+
const dx = (lng - centerLng) * cosLat * metersPerDeg
|
|
111
|
+
const dy = (lat - centerLat) * metersPerDeg
|
|
112
|
+
const cx = dx * Math.cos(bearingRad) - dy * Math.sin(bearingRad)
|
|
113
|
+
const cy = dx * Math.sin(bearingRad) + dy * Math.cos(bearingRad)
|
|
114
|
+
// At 45° pitch, elevation above mean shifts apparent Y by the same amount (tan 45° = 1).
|
|
115
|
+
const cyEffective = cy + (elev - meanElevation)
|
|
116
|
+
if (cx < minCameraX) minCameraX = cx
|
|
117
|
+
if (cx > maxCameraX) maxCameraX = cx
|
|
118
|
+
if (cyEffective < minCameraY) minCameraY = cyEffective
|
|
119
|
+
if (cyEffective > maxCameraY) maxCameraY = cyEffective
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
center: [centerLng, centerLat],
|
|
124
|
+
bearing: mapBearing,
|
|
125
|
+
rotatedWidthMeters: maxCameraX - minCameraX,
|
|
126
|
+
rotatedHeightMeters: maxCameraY - minCameraY,
|
|
127
|
+
minCameraY,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Recursively extracts coordinates and segment pairs from a geometry. */
|
|
132
|
+
function collectGeometry(
|
|
133
|
+
geom: GeoJSON.Geometry,
|
|
134
|
+
allCoords: [number, number, number][],
|
|
135
|
+
bearingSegments: [[number, number, number], [number, number, number]][],
|
|
136
|
+
): void {
|
|
137
|
+
switch (geom.type) {
|
|
138
|
+
case 'Point': {
|
|
139
|
+
const [lng, lat, elev = 0] = geom.coordinates
|
|
140
|
+
allCoords.push([lng, lat, elev])
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
case 'MultiPoint': {
|
|
144
|
+
for (const pos of geom.coordinates) {
|
|
145
|
+
const [lng, lat, elev = 0] = pos
|
|
146
|
+
allCoords.push([lng, lat, elev])
|
|
147
|
+
}
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
case 'LineString':
|
|
151
|
+
collectPositions(geom.coordinates, allCoords, bearingSegments)
|
|
152
|
+
break
|
|
153
|
+
case 'MultiLineString':
|
|
154
|
+
for (const line of geom.coordinates) {
|
|
155
|
+
collectPositions(line, allCoords, bearingSegments)
|
|
156
|
+
}
|
|
157
|
+
break
|
|
158
|
+
case 'Polygon':
|
|
159
|
+
for (const ring of geom.coordinates) {
|
|
160
|
+
collectPositions(ring, allCoords, bearingSegments)
|
|
161
|
+
}
|
|
162
|
+
break
|
|
163
|
+
case 'MultiPolygon':
|
|
164
|
+
for (const polygon of geom.coordinates) {
|
|
165
|
+
for (const ring of polygon) {
|
|
166
|
+
collectPositions(ring, allCoords, bearingSegments)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break
|
|
170
|
+
case 'GeometryCollection':
|
|
171
|
+
for (const g of geom.geometries) {
|
|
172
|
+
collectGeometry(g, allCoords, bearingSegments)
|
|
173
|
+
}
|
|
174
|
+
break
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Adds positions to allCoords and consecutive pairs to bearingSegments. Z defaults to 0. */
|
|
179
|
+
function collectPositions(
|
|
180
|
+
positions: GeoJSON.Position[],
|
|
181
|
+
allCoords: [number, number, number][],
|
|
182
|
+
bearingSegments: [[number, number, number], [number, number, number]][],
|
|
183
|
+
): void {
|
|
184
|
+
const elevated: [number, number, number][] = positions.map(
|
|
185
|
+
([lng, lat, elev = 0]) => [lng, lat, elev],
|
|
186
|
+
)
|
|
187
|
+
for (const coord of elevated) allCoords.push(coord)
|
|
188
|
+
for (let i = 0; i < elevated.length - 1; i++) {
|
|
189
|
+
bearingSegments.push([elevated[i], elevated[i + 1]])
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Great-circle bearing from (lat1, lng1) to (lat2, lng2), in degrees 0–360. */
|
|
194
|
+
function greatCircleBearing(
|
|
195
|
+
lat1: number,
|
|
196
|
+
lng1: number,
|
|
197
|
+
lat2: number,
|
|
198
|
+
lng2: number,
|
|
199
|
+
): number {
|
|
200
|
+
const toRad = (d: number) => (d * Math.PI) / 180
|
|
201
|
+
const φ1 = toRad(lat1)
|
|
202
|
+
const φ2 = toRad(lat2)
|
|
203
|
+
const Δλ = toRad(lng2 - lng1)
|
|
204
|
+
const y = Math.sin(Δλ) * Math.cos(φ2)
|
|
205
|
+
const x =
|
|
206
|
+
Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ)
|
|
207
|
+
return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360
|
|
208
|
+
}
|
package/src/index.ts
CHANGED