openskidata-format 8.0.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,10 +6,12 @@ import { LineString } from 'geojson';
6
6
  * except for the final height which corresponds to the LineString endpoint and may have a different spacing.
7
7
  * Height values can be mapped to geographical coordinates using Turf.js: `turf.lineChunk(geometry, resolution, {units: 'meters'})`.
8
8
  * @property {number} resolution - The horizontal sampling distance in meters between consecutive height measurements.
9
+ * @property {number} targetResolution - The horizontal sampling distance in meters that was requested for the elevation profile. The actual resolution may be less to ensure an integer number of segments along the line. This is used for reconstructing the profile geometry from the feature geometry.
9
10
  */
10
11
  export type ElevationProfile = {
11
12
  heights: number[];
12
13
  resolution: number;
14
+ targetResolution: number;
13
15
  };
14
16
  /**
15
17
  * Elevation data that can be computed on demand fom a Run or Lift feature.
@@ -29,13 +31,36 @@ type PitchData = {
29
31
  maxPitchInPercent: number | null;
30
32
  inclinedLengthInMeters: number;
31
33
  overallPitchInPercent: number | null;
34
+ pitchCalculationResolutionInMeters: number;
32
35
  };
33
36
  export declare function getElevationData(profileGeometry: GeoJSON.LineString): ElevationData;
34
- export declare function getPitchData(profileGeometry: GeoJSON.LineString, resolutionInMeters?: number): PitchData;
37
+ export declare function getPitchData(profileGeometry: GeoJSON.LineString, minResolutionInMeters?: number): PitchData;
35
38
  export declare function getProfileGeometry(geometry: GeoJSON.LineString, elevationProfile: ElevationProfile): GeoJSON.LineString;
36
39
  /**
37
- * Determines the points to use for an elevation profile.
40
+ * Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
38
41
  */
39
- export declare function extractPointsForElevationProfile(geometry: LineString, resolution: number): GeoJSON.LineString;
42
+ export declare function extractPointsForElevationProfile(geometry: LineString, minResolutionInMeters: number): {
43
+ resolutionInMeters: number;
44
+ geometry: GeoJSON.LineString;
45
+ };
40
46
  export declare function getAscentAndDescent(profileGeometry: GeoJSON.LineString): AscentDescentData;
47
+ /**
48
+ * Turf's lineChunk has several issues:
49
+ * - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
50
+ * change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
51
+ * - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
52
+ * fix: strip Z coordinates from non-original points
53
+ * - it can produce a last point that is very close but not exactly the same as the original endpoint
54
+ * fix: set the last point to be exactly the original endpoint
55
+ * - it can produce two points very close to the end of line due to floating point precision issues
56
+ * fix: drop the second to last point if it's very close to the end
57
+ *
58
+ * @param geometry
59
+ * @param minResolutionInMeters
60
+ * @returns
61
+ */
62
+ export declare function lineChunkPatched(geometry: LineString, minResolutionInMeters: number): {
63
+ resolutionInMeters: number;
64
+ geometry: GeoJSON.LineString[];
65
+ };
41
66
  export {};
@@ -5,6 +5,7 @@ exports.getPitchData = getPitchData;
5
5
  exports.getProfileGeometry = getProfileGeometry;
6
6
  exports.extractPointsForElevationProfile = extractPointsForElevationProfile;
7
7
  exports.getAscentAndDescent = getAscentAndDescent;
8
+ exports.lineChunkPatched = lineChunkPatched;
8
9
  const tslib_1 = require("tslib");
9
10
  const distance_1 = tslib_1.__importDefault(require("@turf/distance"));
10
11
  const length_1 = tslib_1.__importDefault(require("@turf/length"));
@@ -16,7 +17,7 @@ function getElevationData(profileGeometry) {
16
17
  profileGeometry,
17
18
  };
18
19
  }
19
- function getPitchData(profileGeometry, resolutionInMeters = 25) {
20
+ function getPitchData(profileGeometry, minResolutionInMeters = 25) {
20
21
  const coordinates = profileGeometry.coordinates;
21
22
  if (coordinates[0].length < 3) {
22
23
  throw 'Elevation data is required for slope analysis';
@@ -44,59 +45,54 @@ function getPitchData(profileGeometry, resolutionInMeters = 25) {
44
45
  totalInclinedLength += Math.sqrt(Math.pow(lengthInMeters, 2) + Math.pow(elevation, 2));
45
46
  totalElevationChange += Math.abs(elevation);
46
47
  }
47
- // Chunk the line into fixed-length segments for max pitch calculation
48
- const chunkedGeometries = (0, line_chunk_1.lineChunk)(profileGeometry, resolutionInMeters, {
49
- units: 'meters',
50
- }).features.map((feature) => feature.geometry);
51
- // turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
52
- // but we need distance-based interpolation. Strip Z from non-original vertices so
53
- // interpolateElevation can add them correctly.
54
- // https://github.com/Turfjs/turf/issues/3007
55
- stripNonOriginalElevations(chunkedGeometries, coordinates);
48
+ // If the line is too short relative to the resolution, pitch calculations are unreliable
49
+ // due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
50
+ // which can easily happen due to elevation data precision issues.
51
+ if (totalLength < minResolutionInMeters / 2) {
52
+ return {
53
+ averagePitchInPercent: null,
54
+ maxPitchInPercent: null,
55
+ inclinedLengthInMeters: totalInclinedLength,
56
+ overallPitchInPercent: null,
57
+ pitchCalculationResolutionInMeters: totalLength,
58
+ };
59
+ }
60
+ // Chunk the line into segments using the calculated resolution for max pitch calculation
61
+ const { geometry: chunkedGeometries, resolutionInMeters: actualResolutionInMeters, } = lineChunkPatched(profileGeometry, minResolutionInMeters);
56
62
  // Mutates the geometries in place to add elevation data
57
63
  interpolateElevation(chunkedGeometries);
58
- // Initialize max pitch values
59
- let maxPitchValue = null;
60
- // Calculate max pitch over the fixed-length chunks
64
+ // Calculate max pitch over the evenly-divided chunks
65
+ let maxPitchValue = 0;
61
66
  for (const chunk of chunkedGeometries) {
62
67
  const chunkCoords = chunk.coordinates;
63
68
  const startPoint = chunkCoords[0];
64
69
  const endPoint = chunkCoords[chunkCoords.length - 1];
65
70
  const elevationChange = endPoint[2] - startPoint[2];
66
71
  const chunkLengthInMeters = (0, length_1.default)({ type: 'Feature', geometry: chunk, properties: {} }, { units: 'meters' });
67
- // Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
68
- if (chunkLengthInMeters < resolutionInMeters / 2)
69
- continue;
70
72
  const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters);
71
- if (maxPitchValue === null || chunkPitch > maxPitchValue) {
73
+ if (chunkPitch > maxPitchValue) {
72
74
  maxPitchValue = chunkPitch;
73
75
  }
74
76
  }
75
77
  const averagePitch = totalElevationChange / totalLength;
76
78
  const overallElevationChange = Math.abs(coordinates[coordinates.length - 1][2] - coordinates[0][2]);
77
- // null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
78
- // is questionable as a 1m change in elevation over
79
- // 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
80
- if (maxPitchValue === null) {
81
- return {
82
- averagePitchInPercent: null,
83
- maxPitchInPercent: null,
84
- inclinedLengthInMeters: totalInclinedLength,
85
- overallPitchInPercent: null,
86
- };
87
- }
88
79
  return {
89
80
  averagePitchInPercent: averagePitch,
90
81
  maxPitchInPercent: maxPitchValue,
91
82
  inclinedLengthInMeters: totalInclinedLength,
92
83
  overallPitchInPercent: overallElevationChange / totalLength,
84
+ pitchCalculationResolutionInMeters: actualResolutionInMeters,
93
85
  };
94
86
  }
95
87
  function getProfileGeometry(geometry, elevationProfile) {
96
- const profileLine = extractPointsForElevationProfile(geometry, elevationProfile.resolution);
88
+ const { geometry: profileLine, resolutionInMeters: actualResolution } = extractPointsForElevationProfile(geometry, elevationProfile.targetResolution);
97
89
  if (profileLine.coordinates.length !== elevationProfile.heights.length) {
98
90
  throw `Mismatch of points & elevation profile`;
99
91
  }
92
+ if (Math.abs(actualResolution - elevationProfile.resolution) >
93
+ elevationProfile.resolution * 0.001) {
94
+ throw `Resolution mismatch between profile geometry (${actualResolution}) and elevation profile (${elevationProfile.resolution})`;
95
+ }
100
96
  for (let i = 0; i < profileLine.coordinates.length; i++) {
101
97
  const point = profileLine.coordinates[i];
102
98
  const height = elevationProfile.heights[i];
@@ -105,13 +101,10 @@ function getProfileGeometry(geometry, elevationProfile) {
105
101
  return profileLine;
106
102
  }
107
103
  /**
108
- * Determines the points to use for an elevation profile.
104
+ * Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
109
105
  */
110
- function extractPointsForElevationProfile(geometry, resolution) {
111
- // Important: Z coordinates in the output of lineChunk may be incorrect, ignore them (https://github.com/Turfjs/turf/issues/3007)
112
- const lineChunks = (0, line_chunk_1.lineChunk)(geometry, resolution, {
113
- units: 'meters',
114
- }).features.map((feature) => feature.geometry);
106
+ function extractPointsForElevationProfile(geometry, minResolutionInMeters) {
107
+ const { geometry: lineChunks, resolutionInMeters } = lineChunkPatched(geometry, minResolutionInMeters);
115
108
  const points = [];
116
109
  for (let subline of lineChunks) {
117
110
  const point = subline.coordinates[0];
@@ -126,8 +119,11 @@ function extractPointsForElevationProfile(geometry, resolution) {
126
119
  }
127
120
  }
128
121
  return {
129
- type: 'LineString',
130
- coordinates: points,
122
+ resolutionInMeters,
123
+ geometry: {
124
+ type: 'LineString',
125
+ coordinates: points,
126
+ },
131
127
  };
132
128
  }
133
129
  function getAscentAndDescent(profileGeometry) {
@@ -168,6 +164,53 @@ function getAscentAndDescent(profileGeometry) {
168
164
  verticalInMeters: result.maxElevation - result.minElevation,
169
165
  };
170
166
  }
167
+ /**
168
+ * Turf's lineChunk has several issues:
169
+ * - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
170
+ * change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
171
+ * - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
172
+ * fix: strip Z coordinates from non-original points
173
+ * - it can produce a last point that is very close but not exactly the same as the original endpoint
174
+ * fix: set the last point to be exactly the original endpoint
175
+ * - it can produce two points very close to the end of line due to floating point precision issues
176
+ * fix: drop the second to last point if it's very close to the end
177
+ *
178
+ * @param geometry
179
+ * @param minResolutionInMeters
180
+ * @returns
181
+ */
182
+ function lineChunkPatched(geometry, minResolutionInMeters) {
183
+ const resolutionInMeters = horizontalResolutionFitting(geometry, minResolutionInMeters);
184
+ const lineChunks = (0, line_chunk_1.lineChunk)(geometry, resolutionInMeters, {
185
+ units: 'meters',
186
+ }).features.map((feature) => feature.geometry);
187
+ // Check the last segment has the expected length
188
+ const lastSegment = lineChunks[lineChunks.length - 1];
189
+ const lastSegmentLength = (0, length_1.default)({ type: 'Feature', geometry: lastSegment, properties: {} }, { units: 'meters' });
190
+ if (lastSegmentLength < resolutionInMeters * 0.01) {
191
+ // If the last segment is very short, which can happen due to floating point precision issues, drop it to avoid issues with elevation interpolation
192
+ lineChunks.pop();
193
+ }
194
+ // Set the last point from the original coordinates, turf line chunk can sometimes produce a last point that
195
+ // is very close but not exactly the same as the original endpoint, which can cause issues with elevation interpolation
196
+ const lastOriginalPoint = geometry.coordinates[geometry.coordinates.length - 1];
197
+ const lastChunkCoords = lineChunks[lineChunks.length - 1].coordinates;
198
+ lastChunkCoords[lastChunkCoords.length - 1] = lastOriginalPoint;
199
+ // turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
200
+ // but we need distance-based interpolation. Strip Z from non-original vertices so
201
+ // interpolateElevation can add them correctly.
202
+ // https://github.com/Turfjs/turf/issues/3007
203
+ stripNonOriginalElevations(lineChunks, geometry.coordinates);
204
+ return { resolutionInMeters, geometry: lineChunks };
205
+ }
206
+ function horizontalResolutionFitting(geometry, minResolution) {
207
+ const totalLength = (0, length_1.default)({ type: 'Feature', geometry, properties: {} }, { units: 'meters' });
208
+ if (totalLength === 0) {
209
+ return 0;
210
+ }
211
+ const numSegments = Math.ceil(totalLength / minResolution);
212
+ return totalLength / numSegments;
213
+ }
171
214
  /**
172
215
  * Strip Z coordinates from points that were created by lineChunk (not original vertices).
173
216
  * Original vertices are identified by matching lon/lat coordinates.
@@ -223,7 +266,7 @@ function interpolateElevation(geometries) {
223
266
  // Find elevation reference points
224
267
  const referencePoints = allPoints.filter((p) => p.hasElevation);
225
268
  if (referencePoints.length <= 1) {
226
- throw 'At least two points with elevation data are required';
269
+ throw `At least two points with elevation data are required ${JSON.stringify(allPoints)}`;
227
270
  }
228
271
  // Process segments between each pair of reference points
229
272
  for (let i = 0; i < referencePoints.length - 1; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"ElevationProfile.js","sourceRoot":"","sources":["../src/ElevationProfile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ElevationProfile.js","sourceRoot":"","sources":["../src/ElevationProfile.ts"],"names":[],"mappings":"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"}
package/dist/Lift.d.ts CHANGED
@@ -3,16 +3,22 @@ import { FeatureType } from './FeatureType';
3
3
  import { Place } from './Place';
4
4
  import { SkiAreaSummaryFeature } from './SkiArea';
5
5
  import { Source } from './Source';
6
+ import { LiftStationSpotFeature } from './Spot';
6
7
  import { Status } from './Status';
7
8
  export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>;
8
9
  export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
10
+ /**
11
+ * Access restriction for a lift feature.
12
+ * - 'private': The lift has restricted access (derived from OSM access=private tag)
13
+ * - null: No access restriction or access information not available
14
+ */
15
+ export type Access = 'private' | null;
9
16
  /**
10
17
  * A feature representing a ski lift.
11
18
  *
12
19
  * Lifts are derived from OpenStreetMap aerialway/railway features that are commonly used for winter sports.
13
20
  *
14
21
  * Note:
15
- * - Private lifts are not included in this dataset.
16
22
  * - Railways (except funiculars) are included only if they are part of a site=piste relation.
17
23
  * - Railway parts are not merged together so a single railway line may be represented as multiple features.
18
24
  * - Some lifts included in the dataset may be for other purposes (amusement parks, etc).
@@ -22,6 +28,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
22
28
  * @property {string} id - Unique identifier for the lift. The ID is just a hash of the feature, so will change if the feature changes in any way.
23
29
  * @property {LiftType} liftType - Type of lift (e.g. chair_lift, gondola). Derived from OpenStreetMap aerialway/railway tags.
24
30
  * @property {Status} status - Operational status of the lift. Derived from OpenStreetMap lifecycle tags.
31
+ * @property {Access} access - Access restriction for the lift. Set to "private" when derived from OpenStreetMap access=private tag, otherwise null.
25
32
  * @property {string | null} name - Name of the lift. Derived from the OpenStreetMap name tag.
26
33
  * @property {string | null} ref - Reference code/number for the lift. Derived from the OpenStreetMap ref tag.
27
34
  * @property {string | null} refFRCAIRN - French CAIRN reference identifier. Derived from the OpenStreetMap ref:FR:CAIRN tag.
@@ -33,6 +40,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
33
40
  * @property {boolean | null} detachable - Whether the lift has detachable grips. Derived from the OpenStreetMap aerialway:detachable tag.
34
41
  * @property {boolean | null} bubble - Whether the lift has bubbles/covers to protect from weather. Derived from the OpenStreetMap aerialway:bubble tag.
35
42
  * @property {boolean | null} heating - Whether the lift has heated carriers/seats. Derived from the OpenStreetMap aerialway:heating tag.
43
+ * @property {LiftStationSpotFeature[]} stations - Lift station spot features associated with this lift.
36
44
  * @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this lift is a part of.
37
45
  * @property {Source[]} sources - Data sources for the feature.
38
46
  * @property {string[]} websites - Websites associated with this lift. Derived from the OpenStreetMap website tag.
@@ -44,6 +52,7 @@ export type LiftProperties = {
44
52
  id: string;
45
53
  liftType: LiftType;
46
54
  status: Status;
55
+ access: Access;
47
56
  name: string | null;
48
57
  ref: string | null;
49
58
  refFRCAIRN: string | null;
@@ -55,6 +64,7 @@ export type LiftProperties = {
55
64
  detachable: boolean | null;
56
65
  bubble: boolean | null;
57
66
  heating: boolean | null;
67
+ stations: LiftStationSpotFeature[];
58
68
  skiAreas: SkiAreaSummaryFeature[];
59
69
  sources: Source[];
60
70
  websites: string[];
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/SkiArea.d.ts CHANGED
@@ -90,6 +90,8 @@ export type RunStatisticsByDifficulty = {
90
90
  [key in RunDifficulty | 'other']?: {
91
91
  count: number;
92
92
  lengthInKm: number;
93
+ snowmakingLengthInKm?: number;
94
+ snowfarmingLengthInKm?: number;
93
95
  };
94
96
  };
95
97
  export type LiftStatistics = {
package/dist/Spot.d.ts CHANGED
@@ -9,6 +9,10 @@ import { Source } from './Source';
9
9
  * Spots are used to represent lift stations, road crossings, or certain terrain features.
10
10
  */
11
11
  export type SpotFeature = GeoJSON.Feature<SpotGeometry, SpotProperties>;
12
+ /**
13
+ * A GeoJSON feature representing a lift station spot.
14
+ */
15
+ export type LiftStationSpotFeature = GeoJSON.Feature<SpotGeometry, LiftStationSpotProperties>;
12
16
  /**
13
17
  * Geometry type for spots - always a point.
14
18
  */
@@ -65,6 +69,7 @@ export declare enum DismountRequirement {
65
69
  * @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
66
70
  * @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
67
71
  * @property {boolean | null} exit - Whether passengers can alight here. From OpenStreetMap tag `aerialway:access=exit|both|no`.
72
+ * @property {string} liftId - ID of the lift feature associated with this station.
68
73
  */
69
74
  export type LiftStationSpotProperties = SpotBaseProperties & {
70
75
  spotType: SpotType.LiftStation;
@@ -72,6 +77,7 @@ export type LiftStationSpotProperties = SpotBaseProperties & {
72
77
  position: LiftStationPosition | null;
73
78
  entry: boolean | null;
74
79
  exit: boolean | null;
80
+ liftId: string;
75
81
  };
76
82
  /**
77
83
  * Position of a lift station along the lift line.
package/dist/Spot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;AAuCA;;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;AAqBD;;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":";;;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openskidata-format",
3
- "version": "8.0.0",
3
+ "version": "10.0.0",
4
4
  "description": "Data format for OpenSkiMap.org",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,16 +1,13 @@
1
+ import length from '@turf/length'
1
2
  import {
2
3
  extractPointsForElevationProfile,
3
4
  getAscentAndDescent,
4
5
  getPitchData,
6
+ lineChunkPatched,
5
7
  } from './ElevationProfile'
6
8
  import { FeatureType } from './FeatureType'
7
9
  import { getLiftElevationData, LiftFeature, LiftType } from './Lift'
8
- import {
9
- getRunElevationData,
10
- RunDifficulty,
11
- RunFeature,
12
- RunUse,
13
- } from './Run'
10
+ import { getRunElevationData, RunDifficulty, RunFeature, RunUse } from './Run'
14
11
  import { RunDifficultyConvention } from './RunDifficultyConvention'
15
12
  import { Status } from './Status'
16
13
 
@@ -74,7 +71,11 @@ describe('ElevationProfile', () => {
74
71
 
75
72
  const result = getPitchData(profileGeometry)
76
73
 
77
- expect(result.maxPitchInPercent).toBeCloseTo(0.377)
74
+ // With the new resolution calculation, the actual resolution divides the distance evenly
75
+ // Total length is ~60.44m, with default resolution of 25m: numSegments = ceil(60.44/25) = 3, actualResolution = 60.44/3 ≈ 20.14m
76
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.14)
77
+ expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(25)
78
+ expect(result.maxPitchInPercent).toBeCloseTo(0.489)
78
79
  expect(result.averagePitchInPercent).toBeCloseTo(0.2482)
79
80
  expect(result.inclinedLengthInMeters).toBeCloseTo(63.0597)
80
81
  })
@@ -97,6 +98,101 @@ describe('ElevationProfile', () => {
97
98
  // The overall change should be more reasonable when calculated over fixed distances
98
99
  expect(result.maxPitchInPercent).toBeLessThan(0.5) // Should be much less than the individual segment pitch
99
100
  })
101
+
102
+ it('calculates resolution that divides distance evenly', () => {
103
+ const profileGeometry: GeoJSON.LineString = {
104
+ type: 'LineString',
105
+ coordinates: [
106
+ [0, 0, 100],
107
+ [0, 0.001, 110], // ~111m distance
108
+ [0, 0.002, 105], // ~111m distance
109
+ ],
110
+ }
111
+
112
+ const result = getPitchData(profileGeometry, 50)
113
+
114
+ // Resolution should be <= 50
115
+ expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
116
+
117
+ // With ~222m total length and 50m input resolution:
118
+ // numSegments = ceil(222/50) = 5
119
+ // actualResolution = 222/5 ≈ 44.48m
120
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(44.48)
121
+ })
122
+
123
+ it('uses total length as resolution when line is shorter than input resolution', () => {
124
+ const profileGeometry: GeoJSON.LineString = {
125
+ type: 'LineString',
126
+ coordinates: [
127
+ [0, 0, 100],
128
+ [0, 0.0001, 110], // ~11m distance
129
+ ],
130
+ }
131
+
132
+ const result = getPitchData(profileGeometry, 50)
133
+
134
+ // Resolution should equal the total length since numSegments = ceil(11/50) = 1
135
+ // actualResolution = 11/1 ≈ 11.12m
136
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(11.12)
137
+ expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
138
+ })
139
+
140
+ it('ensures all chunks are processed without skipping short chunks', () => {
141
+ // Create a line that would previously have a short final chunk
142
+ const profileGeometry: GeoJSON.LineString = {
143
+ type: 'LineString',
144
+ coordinates: [
145
+ [11.177425600412874, 47.31265682344346, 100],
146
+ [11.177224899122194, 47.312533118812354, 120], // ~20m, steep
147
+ [11.176823496540862, 47.31229807921545, 105], // ~40m, gentle descent
148
+ ],
149
+ }
150
+
151
+ const result = getPitchData(profileGeometry, 25)
152
+
153
+ // With evenly divided chunks, no chunks should be skipped
154
+ // The max pitch should be calculated from all segments
155
+ expect(result.maxPitchInPercent).toBeGreaterThan(0)
156
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.14)
157
+ })
158
+
159
+ it('returns null pitch data for very short lines', () => {
160
+ const profileGeometry: GeoJSON.LineString = {
161
+ type: 'LineString',
162
+ coordinates: [
163
+ [0, 0, 100],
164
+ [0, 0.00001, 101], // ~1.11m distance
165
+ ],
166
+ }
167
+
168
+ const result = getPitchData(profileGeometry, 25)
169
+
170
+ // Lines shorter than half the resolution should return null pitch data
171
+ // because elevation data resolution makes pitch calculations unreliable
172
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(1.11)
173
+ expect(result.maxPitchInPercent).toBeNull()
174
+ expect(result.averagePitchInPercent).toBeNull()
175
+ expect(result.overallPitchInPercent).toBeNull()
176
+ expect(result.inclinedLengthInMeters).toBeGreaterThan(0) // This should still be calculated
177
+ })
178
+
179
+ it('calculates pitch data for lines just above the minimum threshold', () => {
180
+ const profileGeometry: GeoJSON.LineString = {
181
+ type: 'LineString',
182
+ coordinates: [
183
+ [0, 0, 100],
184
+ [0, 0.00015, 105], // ~16.7m distance, just above half of 25m
185
+ ],
186
+ }
187
+
188
+ const result = getPitchData(profileGeometry, 25)
189
+
190
+ // Lines at or above half the resolution should calculate pitch data
191
+ expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(16.68)
192
+ expect(result.maxPitchInPercent).not.toBeNull()
193
+ expect(result.averagePitchInPercent).not.toBeNull()
194
+ expect(result.overallPitchInPercent).not.toBeNull()
195
+ })
100
196
  })
101
197
 
102
198
  describe('extractPointsForElevationProfile', () => {
@@ -112,13 +208,13 @@ describe('ElevationProfile', () => {
112
208
  // With 20m resolution, there should be 12 points
113
209
  const result = extractPointsForElevationProfile(geometry, 20)
114
210
 
115
- expect(result.coordinates.length).toEqual(12)
116
- expect(result.coordinates[0]).toEqual([
211
+ expect(result.geometry.coordinates.length).toEqual(12)
212
+ expect(result.geometry.coordinates[0]).toEqual([
117
213
  11.177452968770694, 47.312650638218656,
118
214
  ])
119
- expect(result.coordinates[result.coordinates.length - 1]).toEqual([
120
- 11.175409464719593, 47.31138883724759,
121
- ])
215
+ expect(
216
+ result.geometry.coordinates[result.geometry.coordinates.length - 1],
217
+ ).toEqual([11.175409464719593, 47.31138883724759])
122
218
  })
123
219
 
124
220
  it('handles points that already have elevation', () => {
@@ -133,9 +229,9 @@ describe('ElevationProfile', () => {
133
229
  const result = extractPointsForElevationProfile(geometry, 20)
134
230
 
135
231
  // Should still work properly, returning 2D points regardless of input dimension
136
- expect(result.coordinates.length).toBeGreaterThan(1)
137
- expect(result.coordinates[0].length).toBe(2) // Should always return 2D points
138
- expect(result.coordinates[0]).toEqual([
232
+ expect(result.geometry.coordinates.length).toBeGreaterThan(1)
233
+ expect(result.geometry.coordinates[0].length).toBe(2) // Should always return 2D points
234
+ expect(result.geometry.coordinates[0]).toEqual([
139
235
  11.177452968770694, 47.312650638218656,
140
236
  ])
141
237
  })
@@ -232,13 +328,19 @@ describe('ElevationProfile', () => {
232
328
  type: 'Feature',
233
329
  geometry: {
234
330
  type: 'MultiLineString',
235
- coordinates: [[[0, 0, 100], [1, 1, 200]]],
331
+ coordinates: [
332
+ [
333
+ [0, 0, 100],
334
+ [1, 1, 200],
335
+ ],
336
+ ],
236
337
  },
237
338
  properties: {
238
339
  type: FeatureType.Lift,
239
340
  id: '123',
240
341
  liftType: LiftType.ChairLift,
241
342
  status: Status.Operating,
343
+ access: null,
242
344
  name: 'Test Lift',
243
345
  skiAreas: [],
244
346
  sources: [],
@@ -252,6 +354,7 @@ describe('ElevationProfile', () => {
252
354
  detachable: null,
253
355
  bubble: null,
254
356
  heating: null,
357
+ stations: [],
255
358
  websites: [],
256
359
  wikidataID: null,
257
360
  places: [],
@@ -294,7 +397,8 @@ describe('ElevationProfile', () => {
294
397
  1574, 1572, 1571, 1571, 1572, 1572, 1573, 1574, 1573, 1572, 1572,
295
398
  1573, 1575, 1576, 1577, 1580, 1583, 1584, 1586,
296
399
  ],
297
- resolution: 25,
400
+ resolution: 24.737575778032642,
401
+ targetResolution: 25,
298
402
  },
299
403
  sources: [],
300
404
  websites: [],
@@ -371,7 +475,255 @@ describe('ElevationProfile', () => {
371
475
  } as RunFeature
372
476
 
373
477
  const result = getRunElevationData(runFeature)
374
- expect(result?.maxPitchInPercent).toBeCloseTo(0.12, 2)
478
+ expect(result?.maxPitchInPercent).toBeCloseTo(0.12)
479
+ })
480
+ })
481
+
482
+ describe('lineChunkPatched', () => {
483
+ it('chunks a line into evenly spaced segments', () => {
484
+ const geometry: GeoJSON.LineString = {
485
+ type: 'LineString',
486
+ coordinates: [
487
+ [0, 0, 100],
488
+ [0, 0.001, 110], // ~111m distance
489
+ [0, 0.002, 105], // ~111m distance
490
+ ],
491
+ }
492
+
493
+ const result = lineChunkPatched(geometry, 50)
494
+
495
+ // Total length ~222m, with 50m input resolution:
496
+ // numSegments = ceil(222/50) = 5, actualResolution = 222/5 ≈ 44.48m
497
+ expect(result.resolutionInMeters).toBeCloseTo(44.48)
498
+ expect(result.geometry.length).toBe(5)
499
+ })
500
+
501
+ it('drops very short last segment caused by floating point precision issues', () => {
502
+ // This geometry used to produce a 0-length last segment
503
+ const geometry: GeoJSON.LineString = {
504
+ type: 'LineString',
505
+ coordinates: [
506
+ [11.0796655, 47.4527256, 1692.2],
507
+ [11.079720619903608, 47.45294351099136, 1681.8],
508
+ [11.079741829533312, 47.4531647479081, 1668.6],
509
+ [11.079727503541202, 47.453383892073894, 1656.7],
510
+ [11.07965005139028, 47.453599318647775, 1644.3],
511
+ [11.079572598604779, 47.4538147451695, 1630.9],
512
+ [11.07942834590789, 47.45401183160125, 1618.7],
513
+ [11.079261771477254, 47.45420278959679, 1607.3],
514
+ [11.079114389973567, 47.454399266146474, 1594.7],
515
+ [11.0790705, 47.45461691834462, 1583.1],
516
+ [11.079181898893397, 47.45481861566543, 1576.6],
517
+ [11.079441578857713, 47.45494597233321, 1569.5],
518
+ [11.079759061257176, 47.454999538815876, 1562],
519
+ [11.080078284221905, 47.4550501436389, 1556.1],
520
+ [11.080397507801036, 47.45510074757592, 1552.5],
521
+ [11.080713989344984, 47.45515789593482, 1548.1],
522
+ [11.081008027508169, 47.45525121278351, 1543.2],
523
+ [11.081212874246143, 47.455416412781105, 1540.9],
524
+ [11.081238270576348, 47.455636585144894, 1535.5],
525
+ [11.081208499760809, 47.45585723881769, 1526.8],
526
+ [11.081175991164459, 47.456077847750485, 1510.4],
527
+ [11.081099444790018, 47.456292146692356, 1497.9],
528
+ [11.080996819007606, 47.45650270883295, 1484.3],
529
+ [11.080897169545302, 47.45671391641284, 1472.4],
530
+ [11.080799550285963, 47.45692556428702, 1456.4],
531
+ [11.080701930240755, 47.457137212078344, 1443.9],
532
+ [11.08059047871202, 47.4573452579998, 1434.7],
533
+ [11.080452900000004, 47.45754649999998, 1427.4],
534
+ ],
535
+ }
536
+
537
+ const result = lineChunkPatched(geometry, 25)
538
+
539
+ // Verify that all segments have correct length
540
+ for (const chunk of result.geometry) {
541
+ const chunkLength = length(
542
+ { type: 'Feature', geometry: chunk, properties: {} },
543
+ { units: 'meters' },
544
+ )
545
+ expect(chunkLength).toBeCloseTo(result.resolutionInMeters)
546
+ }
547
+ })
548
+
549
+ it('preserves the exact last point from the original geometry', () => {
550
+ // This geometry had a split last point that was different from the input last point
551
+ const geometry: GeoJSON.LineString = {
552
+ type: 'LineString',
553
+ coordinates: [
554
+ [11.1215994, 47.466431, 886.6],
555
+ [11.118092699999998, 47.4702057, 768.5],
556
+ ],
557
+ }
558
+
559
+ const result = lineChunkPatched(geometry, 25)
560
+
561
+ // The last point of the last chunk should be exactly the same as the original last point
562
+ const lastChunk = result.geometry[result.geometry.length - 1]
563
+ const lastPoint = lastChunk.coordinates[lastChunk.coordinates.length - 1]
564
+ const originalLastPoint =
565
+ geometry.coordinates[geometry.coordinates.length - 1]
566
+
567
+ expect(lastPoint[0]).toBe(originalLastPoint[0])
568
+ expect(lastPoint[1]).toBe(originalLastPoint[1])
569
+ expect(lastPoint[2]).toBe(originalLastPoint[2])
570
+ })
571
+
572
+ it('strips elevation from non-original points', () => {
573
+ const geometry: GeoJSON.LineString = {
574
+ type: 'LineString',
575
+ coordinates: [
576
+ [0, 0, 100],
577
+ [0, 0.001, 110], // ~111m distance
578
+ [0, 0.002, 120], // ~111m distance
579
+ ],
580
+ }
581
+
582
+ const result = lineChunkPatched(geometry, 50)
583
+
584
+ // Check that interpolated points (not from original geometry) don't have elevation
585
+ for (const chunk of result.geometry) {
586
+ for (const point of chunk.coordinates) {
587
+ const key = `${point[0]},${point[1]}`
588
+ const originalKey = geometry.coordinates.some(
589
+ (c) => `${c[0]},${c[1]}` === key,
590
+ )
591
+
592
+ if (!originalKey) {
593
+ // Non-original points should not have elevation (should be 2D)
594
+ expect(point.length).toBe(2)
595
+ } else {
596
+ // Original points should still have elevation
597
+ expect(point.length).toBe(3)
598
+ }
599
+ }
600
+ }
601
+ })
602
+
603
+ it('handles very short lines', () => {
604
+ const geometry: GeoJSON.LineString = {
605
+ type: 'LineString',
606
+ coordinates: [
607
+ [0, 0, 100],
608
+ [0, 0.00001, 101], // ~1.11m distance
609
+ ],
610
+ }
611
+
612
+ const result = lineChunkPatched(geometry, 25)
613
+
614
+ // Should create a single chunk for very short lines
615
+ expect(result.geometry.length).toBe(1)
616
+ expect(result.resolutionInMeters).toBeCloseTo(1.11)
617
+ })
618
+
619
+ it('handles lines with exact multiples of resolution', () => {
620
+ const geometry: GeoJSON.LineString = {
621
+ type: 'LineString',
622
+ coordinates: [
623
+ [0, 0, 100],
624
+ [0, 0.0009, 110],
625
+ ],
626
+ }
627
+
628
+ const result = lineChunkPatched(geometry, 50)
629
+
630
+ // The actual distance is calculated and divided evenly
631
+ // Resolution should be <= 50m
632
+ expect(result.resolutionInMeters).toBeLessThanOrEqual(50)
633
+ expect(result.geometry.length).toBeGreaterThanOrEqual(1)
634
+ })
635
+
636
+ it('returns first and last points matching original geometry', () => {
637
+ const geometry: GeoJSON.LineString = {
638
+ type: 'LineString',
639
+ coordinates: [
640
+ [11.177425600412874, 47.31265682344346, 100],
641
+ [11.177224899122194, 47.312533118812354, 110],
642
+ [11.176823496540862, 47.31229807921545, 105],
643
+ ],
644
+ }
645
+
646
+ const result = lineChunkPatched(geometry, 25)
647
+
648
+ // First point of first chunk should match original first point
649
+ const firstPoint = result.geometry[0].coordinates[0]
650
+ expect(firstPoint[0]).toBe(geometry.coordinates[0][0])
651
+ expect(firstPoint[1]).toBe(geometry.coordinates[0][1])
652
+ expect(firstPoint[2]).toBe(geometry.coordinates[0][2])
653
+
654
+ // Last point of last chunk should match original last point
655
+ const lastChunk = result.geometry[result.geometry.length - 1]
656
+ const lastPoint = lastChunk.coordinates[lastChunk.coordinates.length - 1]
657
+ const originalLastPoint =
658
+ geometry.coordinates[geometry.coordinates.length - 1]
659
+ expect(lastPoint[0]).toBe(originalLastPoint[0])
660
+ expect(lastPoint[1]).toBe(originalLastPoint[1])
661
+ expect(lastPoint[2]).toBe(originalLastPoint[2])
662
+ })
663
+
664
+ it('calculates resolution that is less than or equal to requested resolution', () => {
665
+ const geometry: GeoJSON.LineString = {
666
+ type: 'LineString',
667
+ coordinates: [
668
+ [0, 0, 100],
669
+ [0, 0.0015, 110], // ~166.7m distance
670
+ ],
671
+ }
672
+
673
+ const result = lineChunkPatched(geometry, 50)
674
+
675
+ // Resolution should never exceed the requested minimum resolution
676
+ expect(result.resolutionInMeters).toBeLessThanOrEqual(50)
677
+ })
678
+
679
+ it('handles zero-length segments in original geometry', () => {
680
+ const geometry: GeoJSON.LineString = {
681
+ type: 'LineString',
682
+ coordinates: [
683
+ [0, 0, 100],
684
+ [0, 0, 100], // Duplicate point (0 length segment)
685
+ [0, 0.001, 110],
686
+ ],
687
+ }
688
+
689
+ const result = lineChunkPatched(geometry, 25)
690
+
691
+ // Should still work and produce valid chunks
692
+ expect(result.geometry.length).toBeGreaterThan(0)
693
+ expect(result.resolutionInMeters).toBeGreaterThan(0)
694
+ })
695
+
696
+ it('preserves all original points that fall on chunk boundaries', () => {
697
+ const geometry: GeoJSON.LineString = {
698
+ type: 'LineString',
699
+ coordinates: [
700
+ [0, 0, 100],
701
+ [0, 0.0005, 105], // ~55.6m - should be close to a chunk boundary with 50m resolution
702
+ [0, 0.001, 110],
703
+ ],
704
+ }
705
+
706
+ const result = lineChunkPatched(geometry, 50)
707
+
708
+ // Count how many original points are preserved in the chunks
709
+ let preservedOriginalPoints = 0
710
+ for (const chunk of result.geometry) {
711
+ for (const point of chunk.coordinates) {
712
+ if (
713
+ geometry.coordinates.some(
714
+ (c) => c[0] === point[0] && c[1] === point[1],
715
+ )
716
+ ) {
717
+ preservedOriginalPoints++
718
+ }
719
+ }
720
+ }
721
+
722
+ // All original points should be present somewhere in the chunks
723
+ // (Note: points may appear multiple times if they're at chunk boundaries)
724
+ expect(preservedOriginalPoints).toBeGreaterThanOrEqual(
725
+ geometry.coordinates.length,
726
+ )
375
727
  })
376
728
  })
377
729
  })
@@ -10,10 +10,12 @@ import { LineString } from 'geojson'
10
10
  * except for the final height which corresponds to the LineString endpoint and may have a different spacing.
11
11
  * Height values can be mapped to geographical coordinates using Turf.js: `turf.lineChunk(geometry, resolution, {units: 'meters'})`.
12
12
  * @property {number} resolution - The horizontal sampling distance in meters between consecutive height measurements.
13
+ * @property {number} targetResolution - The horizontal sampling distance in meters that was requested for the elevation profile. The actual resolution may be less to ensure an integer number of segments along the line. This is used for reconstructing the profile geometry from the feature geometry.
13
14
  */
14
15
  export type ElevationProfile = {
15
16
  heights: number[]
16
17
  resolution: number
18
+ targetResolution: number
17
19
  }
18
20
 
19
21
  /**
@@ -37,6 +39,7 @@ type PitchData = {
37
39
  maxPitchInPercent: number | null
38
40
  inclinedLengthInMeters: number
39
41
  overallPitchInPercent: number | null
42
+ pitchCalculationResolutionInMeters: number
40
43
  }
41
44
 
42
45
  export function getElevationData(
@@ -51,7 +54,7 @@ export function getElevationData(
51
54
 
52
55
  export function getPitchData(
53
56
  profileGeometry: GeoJSON.LineString,
54
- resolutionInMeters: number = 25, // Default resolution of 25 meters for pitch calculation
57
+ minResolutionInMeters: number = 25, // Default resolution of 25 meters for pitch calculation
55
58
  ): PitchData {
56
59
  const coordinates = profileGeometry.coordinates
57
60
  if (coordinates[0].length < 3) {
@@ -86,24 +89,30 @@ export function getPitchData(
86
89
  totalElevationChange += Math.abs(elevation)
87
90
  }
88
91
 
89
- // Chunk the line into fixed-length segments for max pitch calculation
90
- const chunkedGeometries = lineChunk(profileGeometry, resolutionInMeters, {
91
- units: 'meters',
92
- }).features.map((feature) => feature.geometry)
92
+ // If the line is too short relative to the resolution, pitch calculations are unreliable
93
+ // due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
94
+ // which can easily happen due to elevation data precision issues.
95
+ if (totalLength < minResolutionInMeters / 2) {
96
+ return {
97
+ averagePitchInPercent: null,
98
+ maxPitchInPercent: null,
99
+ inclinedLengthInMeters: totalInclinedLength,
100
+ overallPitchInPercent: null,
101
+ pitchCalculationResolutionInMeters: totalLength,
102
+ }
103
+ }
93
104
 
94
- // turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
95
- // but we need distance-based interpolation. Strip Z from non-original vertices so
96
- // interpolateElevation can add them correctly.
97
- // https://github.com/Turfjs/turf/issues/3007
98
- stripNonOriginalElevations(chunkedGeometries, coordinates)
105
+ // Chunk the line into segments using the calculated resolution for max pitch calculation
106
+ const {
107
+ geometry: chunkedGeometries,
108
+ resolutionInMeters: actualResolutionInMeters,
109
+ } = lineChunkPatched(profileGeometry, minResolutionInMeters)
99
110
 
100
111
  // Mutates the geometries in place to add elevation data
101
112
  interpolateElevation(chunkedGeometries)
102
113
 
103
- // Initialize max pitch values
104
- let maxPitchValue: number | null = null
105
-
106
- // Calculate max pitch over the fixed-length chunks
114
+ // Calculate max pitch over the evenly-divided chunks
115
+ let maxPitchValue = 0
107
116
  for (const chunk of chunkedGeometries) {
108
117
  const chunkCoords = chunk.coordinates
109
118
 
@@ -116,11 +125,8 @@ export function getPitchData(
116
125
  { units: 'meters' },
117
126
  )
118
127
 
119
- // Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
120
- if (chunkLengthInMeters < resolutionInMeters / 2) continue
121
-
122
128
  const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters)
123
- if (maxPitchValue === null || chunkPitch > maxPitchValue) {
129
+ if (chunkPitch > maxPitchValue) {
124
130
  maxPitchValue = chunkPitch
125
131
  }
126
132
  }
@@ -130,23 +136,12 @@ export function getPitchData(
130
136
  coordinates[coordinates.length - 1][2] - coordinates[0][2],
131
137
  )
132
138
 
133
- // null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
134
- // is questionable as a 1m change in elevation over
135
- // 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
136
- if (maxPitchValue === null) {
137
- return {
138
- averagePitchInPercent: null,
139
- maxPitchInPercent: null,
140
- inclinedLengthInMeters: totalInclinedLength,
141
- overallPitchInPercent: null,
142
- }
143
- }
144
-
145
139
  return {
146
140
  averagePitchInPercent: averagePitch,
147
141
  maxPitchInPercent: maxPitchValue,
148
142
  inclinedLengthInMeters: totalInclinedLength,
149
143
  overallPitchInPercent: overallElevationChange / totalLength,
144
+ pitchCalculationResolutionInMeters: actualResolutionInMeters,
150
145
  }
151
146
  }
152
147
 
@@ -154,14 +149,22 @@ export function getProfileGeometry(
154
149
  geometry: GeoJSON.LineString,
155
150
  elevationProfile: ElevationProfile,
156
151
  ): GeoJSON.LineString {
157
- const profileLine = extractPointsForElevationProfile(
158
- geometry,
159
- elevationProfile.resolution,
160
- )
152
+ const { geometry: profileLine, resolutionInMeters: actualResolution } =
153
+ extractPointsForElevationProfile(
154
+ geometry,
155
+ elevationProfile.targetResolution,
156
+ )
161
157
  if (profileLine.coordinates.length !== elevationProfile.heights.length) {
162
158
  throw `Mismatch of points & elevation profile`
163
159
  }
164
160
 
161
+ if (
162
+ Math.abs(actualResolution - elevationProfile.resolution) >
163
+ elevationProfile.resolution * 0.001
164
+ ) {
165
+ throw `Resolution mismatch between profile geometry (${actualResolution}) and elevation profile (${elevationProfile.resolution})`
166
+ }
167
+
165
168
  for (let i = 0; i < profileLine.coordinates.length; i++) {
166
169
  const point = profileLine.coordinates[i]
167
170
  const height = elevationProfile.heights[i]
@@ -172,16 +175,16 @@ export function getProfileGeometry(
172
175
  }
173
176
 
174
177
  /**
175
- * Determines the points to use for an elevation profile.
178
+ * Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
176
179
  */
177
180
  export function extractPointsForElevationProfile(
178
181
  geometry: LineString,
179
- resolution: number,
180
- ): GeoJSON.LineString {
181
- // Important: Z coordinates in the output of lineChunk may be incorrect, ignore them (https://github.com/Turfjs/turf/issues/3007)
182
- const lineChunks = lineChunk(geometry, resolution, {
183
- units: 'meters',
184
- }).features.map((feature) => feature.geometry)
182
+ minResolutionInMeters: number,
183
+ ): { resolutionInMeters: number; geometry: GeoJSON.LineString } {
184
+ const { geometry: lineChunks, resolutionInMeters } = lineChunkPatched(
185
+ geometry,
186
+ minResolutionInMeters,
187
+ )
185
188
 
186
189
  const points: GeoJSON.Position[] = []
187
190
  for (let subline of lineChunks) {
@@ -198,8 +201,11 @@ export function extractPointsForElevationProfile(
198
201
  }
199
202
 
200
203
  return {
201
- type: 'LineString',
202
- coordinates: points,
204
+ resolutionInMeters,
205
+ geometry: {
206
+ type: 'LineString',
207
+ coordinates: points,
208
+ },
203
209
  }
204
210
  }
205
211
 
@@ -254,6 +260,78 @@ export function getAscentAndDescent(
254
260
  }
255
261
  }
256
262
 
263
+ /**
264
+ * Turf's lineChunk has several issues:
265
+ * - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
266
+ * change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
267
+ * - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
268
+ * fix: strip Z coordinates from non-original points
269
+ * - it can produce a last point that is very close but not exactly the same as the original endpoint
270
+ * fix: set the last point to be exactly the original endpoint
271
+ * - it can produce two points very close to the end of line due to floating point precision issues
272
+ * fix: drop the second to last point if it's very close to the end
273
+ *
274
+ * @param geometry
275
+ * @param minResolutionInMeters
276
+ * @returns
277
+ */
278
+ export function lineChunkPatched(
279
+ geometry: LineString,
280
+ minResolutionInMeters: number,
281
+ ): { resolutionInMeters: number; geometry: GeoJSON.LineString[] } {
282
+ const resolutionInMeters = horizontalResolutionFitting(
283
+ geometry,
284
+ minResolutionInMeters,
285
+ )
286
+
287
+ const lineChunks = lineChunk(geometry, resolutionInMeters, {
288
+ units: 'meters',
289
+ }).features.map((feature) => feature.geometry)
290
+
291
+ // Check the last segment has the expected length
292
+ const lastSegment = lineChunks[lineChunks.length - 1]
293
+ const lastSegmentLength = length(
294
+ { type: 'Feature', geometry: lastSegment, properties: {} },
295
+ { units: 'meters' },
296
+ )
297
+ if (lastSegmentLength < resolutionInMeters * 0.01) {
298
+ // If the last segment is very short, which can happen due to floating point precision issues, drop it to avoid issues with elevation interpolation
299
+ lineChunks.pop()
300
+ }
301
+
302
+ // Set the last point from the original coordinates, turf line chunk can sometimes produce a last point that
303
+ // is very close but not exactly the same as the original endpoint, which can cause issues with elevation interpolation
304
+ const lastOriginalPoint =
305
+ geometry.coordinates[geometry.coordinates.length - 1]
306
+ const lastChunkCoords = lineChunks[lineChunks.length - 1].coordinates
307
+ lastChunkCoords[lastChunkCoords.length - 1] = lastOriginalPoint
308
+
309
+ // turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
310
+ // but we need distance-based interpolation. Strip Z from non-original vertices so
311
+ // interpolateElevation can add them correctly.
312
+ // https://github.com/Turfjs/turf/issues/3007
313
+ stripNonOriginalElevations(lineChunks, geometry.coordinates)
314
+
315
+ return { resolutionInMeters, geometry: lineChunks }
316
+ }
317
+
318
+ function horizontalResolutionFitting(
319
+ geometry: LineString,
320
+ minResolution: number,
321
+ ): number {
322
+ const totalLength = length(
323
+ { type: 'Feature', geometry, properties: {} },
324
+ { units: 'meters' },
325
+ )
326
+
327
+ if (totalLength === 0) {
328
+ return 0
329
+ }
330
+
331
+ const numSegments = Math.ceil(totalLength / minResolution)
332
+ return totalLength / numSegments
333
+ }
334
+
257
335
  /**
258
336
  * Strip Z coordinates from points that were created by lineChunk (not original vertices).
259
337
  * Original vertices are identified by matching lon/lat coordinates.
@@ -326,7 +404,7 @@ function interpolateElevation(geometries: GeoJSON.LineString[]) {
326
404
  const referencePoints = allPoints.filter((p) => p.hasElevation)
327
405
 
328
406
  if (referencePoints.length <= 1) {
329
- throw 'At least two points with elevation data are required'
407
+ throw `At least two points with elevation data are required ${JSON.stringify(allPoints)}`
330
408
  }
331
409
 
332
410
  // Process segments between each pair of reference points
package/src/Lift.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 { LiftStationSpotFeature } from './Spot'
6
7
  import { Status } from './Status'
7
8
  import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
8
9
 
@@ -10,13 +11,19 @@ export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>
10
11
 
11
12
  export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
12
13
 
14
+ /**
15
+ * Access restriction for a lift feature.
16
+ * - 'private': The lift has restricted access (derived from OSM access=private tag)
17
+ * - null: No access restriction or access information not available
18
+ */
19
+ export type Access = 'private' | null
20
+
13
21
  /**
14
22
  * A feature representing a ski lift.
15
23
  *
16
24
  * Lifts are derived from OpenStreetMap aerialway/railway features that are commonly used for winter sports.
17
25
  *
18
26
  * Note:
19
- * - Private lifts are not included in this dataset.
20
27
  * - Railways (except funiculars) are included only if they are part of a site=piste relation.
21
28
  * - Railway parts are not merged together so a single railway line may be represented as multiple features.
22
29
  * - Some lifts included in the dataset may be for other purposes (amusement parks, etc).
@@ -26,6 +33,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
26
33
  * @property {string} id - Unique identifier for the lift. The ID is just a hash of the feature, so will change if the feature changes in any way.
27
34
  * @property {LiftType} liftType - Type of lift (e.g. chair_lift, gondola). Derived from OpenStreetMap aerialway/railway tags.
28
35
  * @property {Status} status - Operational status of the lift. Derived from OpenStreetMap lifecycle tags.
36
+ * @property {Access} access - Access restriction for the lift. Set to "private" when derived from OpenStreetMap access=private tag, otherwise null.
29
37
  * @property {string | null} name - Name of the lift. Derived from the OpenStreetMap name tag.
30
38
  * @property {string | null} ref - Reference code/number for the lift. Derived from the OpenStreetMap ref tag.
31
39
  * @property {string | null} refFRCAIRN - French CAIRN reference identifier. Derived from the OpenStreetMap ref:FR:CAIRN tag.
@@ -37,6 +45,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
37
45
  * @property {boolean | null} detachable - Whether the lift has detachable grips. Derived from the OpenStreetMap aerialway:detachable tag.
38
46
  * @property {boolean | null} bubble - Whether the lift has bubbles/covers to protect from weather. Derived from the OpenStreetMap aerialway:bubble tag.
39
47
  * @property {boolean | null} heating - Whether the lift has heated carriers/seats. Derived from the OpenStreetMap aerialway:heating tag.
48
+ * @property {LiftStationSpotFeature[]} stations - Lift station spot features associated with this lift.
40
49
  * @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this lift is a part of.
41
50
  * @property {Source[]} sources - Data sources for the feature.
42
51
  * @property {string[]} websites - Websites associated with this lift. Derived from the OpenStreetMap website tag.
@@ -48,6 +57,7 @@ export type LiftProperties = {
48
57
  id: string
49
58
  liftType: LiftType
50
59
  status: Status
60
+ access: Access
51
61
  name: string | null
52
62
  ref: string | null
53
63
  refFRCAIRN: string | null
@@ -59,6 +69,7 @@ export type LiftProperties = {
59
69
  detachable: boolean | null
60
70
  bubble: boolean | null
61
71
  heating: boolean | null
72
+ stations: LiftStationSpotFeature[]
62
73
  skiAreas: SkiAreaSummaryFeature[]
63
74
  sources: Source[]
64
75
  websites: string[]
package/src/SkiArea.ts CHANGED
@@ -102,7 +102,12 @@ export type RunStatisticsByActivityAndDifficulty = {
102
102
  }
103
103
 
104
104
  export type RunStatisticsByDifficulty = {
105
- [key in RunDifficulty | 'other']?: { count: number; lengthInKm: number }
105
+ [key in RunDifficulty | 'other']?: {
106
+ count: number
107
+ lengthInKm: number
108
+ snowmakingLengthInKm?: number
109
+ snowfarmingLengthInKm?: number
110
+ }
106
111
  }
107
112
 
108
113
  export type LiftStatistics = {
package/src/Spot.ts CHANGED
@@ -11,6 +11,14 @@ import { Source } from './Source'
11
11
  */
12
12
  export type SpotFeature = GeoJSON.Feature<SpotGeometry, SpotProperties>
13
13
 
14
+ /**
15
+ * A GeoJSON feature representing a lift station spot.
16
+ */
17
+ export type LiftStationSpotFeature = GeoJSON.Feature<
18
+ SpotGeometry,
19
+ LiftStationSpotProperties
20
+ >
21
+
14
22
  /**
15
23
  * Geometry type for spots - always a point.
16
24
  */
@@ -78,6 +86,7 @@ export enum DismountRequirement {
78
86
  * @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
79
87
  * @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
80
88
  * @property {boolean | null} exit - Whether passengers can alight here. From OpenStreetMap tag `aerialway:access=exit|both|no`.
89
+ * @property {string} liftId - ID of the lift feature associated with this station.
81
90
  */
82
91
  export type LiftStationSpotProperties = SpotBaseProperties & {
83
92
  spotType: SpotType.LiftStation
@@ -85,6 +94,7 @@ export type LiftStationSpotProperties = SpotBaseProperties & {
85
94
  position: LiftStationPosition | null
86
95
  entry: boolean | null
87
96
  exit: boolean | null
97
+ liftId: string
88
98
  }
89
99
 
90
100
  /**