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 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 BRIGHT_RED_COLOR = 'hsl(0, 82%, 42%)';
100
- const DIM_RED_COLOR = 'hsl(0, 53%, 42%)';
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 DIM_RED_COLOR;
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 BRIGHT_RED_COLOR;
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":"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"}
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":"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"}
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;
@@ -1 +1 @@
1
- {"version":3,"file":"SkiArea.js","sourceRoot":"","sources":["../src/SkiArea.ts"],"names":[],"mappings":";;;AAsEA,IAAY,eAGX;AAHD,WAAY,eAAe;IACzB,wCAAqB,CAAA;IACrB,oCAAiB,CAAA;AACnB,CAAC,EAHW,eAAe,+BAAf,eAAe,QAG1B"}
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":";;;AA+CA;;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"}
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
@@ -11,3 +11,4 @@ export * from './SnowCoverHistory';
11
11
  export * from './Source';
12
12
  export * from './Spot';
13
13
  export * from './Status';
14
+ export * from './ViewportHint';
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,2 @@
1
+ import { ViewportHint } from './ViewportHint';
2
+ export declare function mockViewportHint(): ViewportHint;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openskidata-format",
3
- "version": "11.1.0",
3
+ "version": "12.1.0",
4
4
  "description": "Data format for OpenSkiMap.org",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 BRIGHT_RED_COLOR = 'hsl(0, 82%, 42%)'
203
- const DIM_RED_COLOR = 'hsl(0, 53%, 42%)'
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 DIM_RED_COLOR
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 BRIGHT_RED_COLOR
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
@@ -11,3 +11,4 @@ export * from './SnowCoverHistory'
11
11
  export * from './Source'
12
12
  export * from './Spot'
13
13
  export * from './Status'
14
+ export * from './ViewportHint'
@@ -0,0 +1,5 @@
1
+ import { ViewportHint } from './ViewportHint'
2
+
3
+ export function mockViewportHint(): ViewportHint {
4
+ return { center: [0, 0], bearing: null, rotatedWidthMeters: 0, rotatedHeightMeters: 0, minCameraY: 0 }
5
+ }