openskidata-format 3.2.0 → 5.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.
@@ -25,13 +25,13 @@ type AscentDescentData = {
25
25
  verticalInMeters: number;
26
26
  };
27
27
  type PitchData = {
28
- averagePitchInPercent: number;
29
- maxPitchInPercent: number;
28
+ averagePitchInPercent: number | null;
29
+ maxPitchInPercent: number | null;
30
30
  inclinedLengthInMeters: number;
31
- overallPitchInPercent: number;
31
+ overallPitchInPercent: number | null;
32
32
  };
33
33
  export declare function getElevationData(profileGeometry: GeoJSON.LineString): ElevationData;
34
- export declare function getPitchData(profileGeometry: GeoJSON.LineString): PitchData;
34
+ export declare function getPitchData(profileGeometry: GeoJSON.LineString, resolutionInMeters?: number): PitchData;
35
35
  export declare function getProfileGeometry(geometry: GeoJSON.LineString, elevationProfile: ElevationProfile): GeoJSON.LineString;
36
36
  /**
37
37
  * Determines the points to use for an elevation profile.
@@ -6,6 +6,7 @@ exports.getProfileGeometry = getProfileGeometry;
6
6
  exports.extractPointsForElevationProfile = extractPointsForElevationProfile;
7
7
  exports.getAscentAndDescent = getAscentAndDescent;
8
8
  const tslib_1 = require("tslib");
9
+ const distance_1 = tslib_1.__importDefault(require("@turf/distance"));
9
10
  const length_1 = tslib_1.__importDefault(require("@turf/length"));
10
11
  const line_chunk_1 = require("@turf/line-chunk");
11
12
  function getElevationData(profileGeometry) {
@@ -15,16 +16,16 @@ function getElevationData(profileGeometry) {
15
16
  profileGeometry,
16
17
  };
17
18
  }
18
- function getPitchData(profileGeometry) {
19
+ function getPitchData(profileGeometry, resolutionInMeters = 25) {
19
20
  const coordinates = profileGeometry.coordinates;
20
21
  if (coordinates[0].length < 3) {
21
22
  throw 'Elevation data is required for slope analysis';
22
23
  }
24
+ // For overall calculations (average pitch, total length, etc.)
23
25
  let totalLength = 0;
24
26
  let totalInclinedLength = 0;
25
27
  let totalElevationChange = 0;
26
- let maxUphillPitch = Number.MIN_VALUE;
27
- let maxDownhillPitch = Number.MAX_VALUE;
28
+ // First calculate the values needed for overall statistics
28
29
  for (let i = 0; i < coordinates.length - 1; i++) {
29
30
  const before = coordinates[i];
30
31
  const after = coordinates[i + 1];
@@ -39,61 +40,102 @@ function getPitchData(profileGeometry) {
39
40
  properties: {},
40
41
  };
41
42
  const lengthInMeters = (0, length_1.default)(feature, { units: 'meters' });
42
- const percentSlope = elevation / lengthInMeters;
43
- maxUphillPitch = Math.max(maxUphillPitch, percentSlope);
44
- maxDownhillPitch = Math.min(maxDownhillPitch, percentSlope);
45
43
  totalLength += lengthInMeters;
46
44
  totalInclinedLength += Math.sqrt(Math.pow(lengthInMeters, 2) + Math.pow(elevation, 2));
47
45
  totalElevationChange += Math.abs(elevation);
48
46
  }
49
- const maxPitch = Math.abs(maxUphillPitch) > Math.abs(maxDownhillPitch)
50
- ? maxUphillPitch
51
- : maxDownhillPitch;
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
+ // Mutates the geometries in place to add elevation data
52
+ interpolateElevation(chunkedGeometries);
53
+ // Initialize max pitch values
54
+ let maxPitchValue = null;
55
+ // Calculate max pitch over the fixed-length chunks
56
+ for (const chunk of chunkedGeometries) {
57
+ const chunkCoords = chunk.coordinates;
58
+ const startPoint = chunkCoords[0];
59
+ const endPoint = chunkCoords[chunkCoords.length - 1];
60
+ const elevationChange = endPoint[2] - startPoint[2];
61
+ const chunkLengthInMeters = (0, length_1.default)({ type: 'Feature', geometry: chunk, properties: {} }, { units: 'meters' });
62
+ // Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
63
+ if (chunkLengthInMeters < resolutionInMeters / 2)
64
+ continue;
65
+ const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters);
66
+ if (maxPitchValue === null || chunkPitch > maxPitchValue) {
67
+ maxPitchValue = chunkPitch;
68
+ }
69
+ }
52
70
  const averagePitch = totalElevationChange / totalLength;
53
71
  const overallElevationChange = Math.abs(coordinates[coordinates.length - 1][2] - coordinates[0][2]);
72
+ // null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
73
+ // is questionable as a 1m change in elevation over
74
+ // 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
75
+ if (maxPitchValue === null) {
76
+ return {
77
+ averagePitchInPercent: null,
78
+ maxPitchInPercent: null,
79
+ inclinedLengthInMeters: totalInclinedLength,
80
+ overallPitchInPercent: null,
81
+ };
82
+ }
54
83
  return {
55
84
  averagePitchInPercent: averagePitch,
56
- maxPitchInPercent: maxPitch,
85
+ maxPitchInPercent: maxPitchValue,
57
86
  inclinedLengthInMeters: totalInclinedLength,
58
87
  overallPitchInPercent: overallElevationChange / totalLength,
59
88
  };
60
89
  }
61
90
  function getProfileGeometry(geometry, elevationProfile) {
62
- const points = extractPointsForElevationProfile(geometry, elevationProfile.resolution).coordinates;
63
- const heights = elevationProfile.heights;
64
- if (points.length !== heights.length) {
65
- throw 'Mismatch of points & elevation profile.';
91
+ const geometries = lineChunksForElevationProfile(geometry, elevationProfile.resolution);
92
+ var index = 0;
93
+ for (let subline of geometries) {
94
+ const firstPoint = subline.coordinates[0];
95
+ if (firstPoint.length === 2) {
96
+ firstPoint.push(elevationProfile.heights[index]);
97
+ }
98
+ index++;
99
+ if (index === elevationProfile.heights.length) {
100
+ throw 'Mismatch of points & elevation profile.';
101
+ }
102
+ const lastPoint = subline.coordinates[subline.coordinates.length - 1];
103
+ if (lastPoint.length === 2) {
104
+ lastPoint.push(elevationProfile.heights[index]);
105
+ }
106
+ }
107
+ if (index !== elevationProfile.heights.length - 1) {
108
+ throw `Mismatch of points & elevation profile`;
109
+ }
110
+ // Check all coordinates have a height
111
+ for (let subline of geometries) {
112
+ for (let point of subline.coordinates) {
113
+ if (point.length < 3) {
114
+ throw 'All points should have an elevation at this point.';
115
+ }
116
+ }
66
117
  }
67
118
  return {
68
119
  type: 'LineString',
69
- coordinates: points.map((point, index) => {
70
- return [point[0], point[1], heights[index]];
71
- }),
120
+ coordinates: geometries.flatMap((geometry) => geometry.coordinates),
72
121
  };
73
122
  }
74
123
  /**
75
124
  * Determines the points to use for an elevation profile.
76
125
  */
77
126
  function extractPointsForElevationProfile(geometry, resolution) {
78
- const subfeatures = (0, line_chunk_1.lineChunk)(geometry, resolution, {
79
- units: 'meters',
80
- }).features;
127
+ const geometries = lineChunksForElevationProfile(geometry, resolution);
81
128
  const points = [];
82
- for (let subline of subfeatures) {
83
- const geometry = subline.geometry;
84
- if (geometry) {
85
- const point = geometry.coordinates[0];
86
- points.push([point[0], point[1]]);
87
- }
129
+ for (let subline of geometries) {
130
+ const point = subline.coordinates[0];
131
+ points.push([point[0], point[1]]);
88
132
  }
89
- if (subfeatures.length > 0) {
90
- const geometry = subfeatures[subfeatures.length - 1].geometry;
91
- if (geometry) {
92
- const coords = geometry.coordinates;
93
- if (coords.length > 1) {
94
- const point = coords[coords.length - 1];
95
- points.push([point[0], point[1]]);
96
- }
133
+ if (geometries.length > 0) {
134
+ const geometry = geometries[geometries.length - 1];
135
+ const coords = geometry.coordinates;
136
+ if (coords.length > 1) {
137
+ const point = coords[coords.length - 1];
138
+ points.push([point[0], point[1]]);
97
139
  }
98
140
  }
99
141
  return {
@@ -101,6 +143,11 @@ function extractPointsForElevationProfile(geometry, resolution) {
101
143
  coordinates: points,
102
144
  };
103
145
  }
146
+ function lineChunksForElevationProfile(geometry, resolution) {
147
+ return (0, line_chunk_1.lineChunk)(geometry, resolution, {
148
+ units: 'meters',
149
+ }).features.map((feature) => feature.geometry);
150
+ }
104
151
  function getAscentAndDescent(profileGeometry) {
105
152
  const coordinates = profileGeometry.coordinates;
106
153
  if (coordinates.length === 0) {
@@ -139,4 +186,79 @@ function getAscentAndDescent(profileGeometry) {
139
186
  verticalInMeters: result.maxElevation - result.minElevation,
140
187
  };
141
188
  }
189
+ /**
190
+ * Interpolate elevation for points along a list of linestrings that don't already have elevation.
191
+ * Uses geographic distances for proper interpolation.
192
+ * Mutates the geometries in place.
193
+ *
194
+ * @throws Error if no points have elevation data or if segments can't be properly formed
195
+ */
196
+ function interpolateElevation(geometries) {
197
+ if (geometries.length === 0) {
198
+ return;
199
+ }
200
+ // Flatten all coordinates into a single array for easier processing
201
+ const allPoints = [];
202
+ // Collect all points while tracking which ones have elevation
203
+ let pointIndex = 0;
204
+ let totalDistance = 0;
205
+ let previousPoint = null;
206
+ for (const geometry of geometries) {
207
+ for (const point of geometry.coordinates) {
208
+ // Calculate distance from previous point
209
+ if (previousPoint) {
210
+ const segmentLength = (0, distance_1.default)(previousPoint, point, {
211
+ units: 'meters',
212
+ });
213
+ totalDistance += segmentLength;
214
+ }
215
+ const hasElevation = point.length >= 3;
216
+ allPoints.push({
217
+ point,
218
+ hasElevation,
219
+ index: pointIndex++,
220
+ distanceFromStart: totalDistance,
221
+ });
222
+ previousPoint = point;
223
+ }
224
+ }
225
+ // Find elevation reference points
226
+ const referencePoints = allPoints.filter((p) => p.hasElevation);
227
+ if (referencePoints.length <= 1) {
228
+ throw 'At least two points with elevation data are required';
229
+ }
230
+ // Process segments between each pair of reference points
231
+ for (let i = 0; i < referencePoints.length - 1; i++) {
232
+ const startPoint = referencePoints[i];
233
+ const endPoint = referencePoints[i + 1];
234
+ // Points must be in sequence
235
+ if (endPoint.index <= startPoint.index) {
236
+ throw 'Reference points must be in sequence';
237
+ }
238
+ const startElevation = startPoint.point[2];
239
+ const endElevation = endPoint.point[2];
240
+ const elevationDelta = endElevation - startElevation;
241
+ // Calculate the geographic distance between reference points
242
+ const distanceBetweenRefs = endPoint.distanceFromStart - startPoint.distanceFromStart;
243
+ // Interpolate elevations for points between references
244
+ for (let j = startPoint.index + 1; j < endPoint.index; j++) {
245
+ const pointData = allPoints[j];
246
+ // Only interpolate if point doesn't already have elevation
247
+ if (pointData.hasElevation) {
248
+ throw "Can't interpolate elevation for points that already have it";
249
+ }
250
+ // Calculate how far along this point is between the reference points
251
+ const distanceFromStart = pointData.distanceFromStart - startPoint.distanceFromStart;
252
+ // Its possible both references are the same point, in which case we use the ref elevation
253
+ let interpolatedElevation = startElevation;
254
+ if (distanceBetweenRefs > 0) {
255
+ const interpolationFactor = distanceFromStart / distanceBetweenRefs;
256
+ interpolatedElevation =
257
+ startElevation + elevationDelta * interpolationFactor;
258
+ }
259
+ // Add elevation to the existing point array (using reference semantics)
260
+ pointData.point.push(interpolatedElevation);
261
+ }
262
+ }
263
+ }
142
264
  //# sourceMappingURL=ElevationProfile.js.map
@@ -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":";;AAyCA,4CAQC;AAED,oCA6FC;AAED,gDA4CC;AAKD,4EAuBC;AAWD,kDAiDC;;AAtRD,sEAAqC;AACrC,kEAAiC;AACjC,iDAA4C;AAuC5C,SAAgB,gBAAgB,CAC9B,eAAmC;IAEnC,OAAO;QACL,GAAG,mBAAmB,CAAC,eAAe,CAAC;QACvC,GAAG,YAAY,CAAC,eAAe,CAAC;QAChC,eAAe;KAChB,CAAA;AACH,CAAC;AAED,SAAgB,YAAY,CAC1B,eAAmC,EACnC,qBAA6B,EAAE;IAE/B,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAA;IAC/C,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,+CAA+C,CAAA;IACvD,CAAC;IAED,+DAA+D;IAC/D,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,mBAAmB,GAAG,CAAC,CAAA;IAC3B,IAAI,oBAAoB,GAAG,CAAC,CAAA;IAE5B,2DAA2D;IAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QAC7B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAChC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;QACtC,MAAM,QAAQ,GAAuB;YACnC,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;SAC7B,CAAA;QACD,MAAM,OAAO,GAAoB;YAC/B,IAAI,EAAE,SAAS;YACf,QAAQ;YACR,UAAU,EAAE,EAAE;SACf,CAAA;QACD,MAAM,cAAc,GAAG,IAAA,gBAAM,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE3D,WAAW,IAAI,cAAc,CAAA;QAC7B,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAC9B,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CACrD,CAAA;QACD,oBAAoB,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC7C,CAAC;IAED,sEAAsE;IACtE,MAAM,iBAAiB,GAAG,IAAA,sBAAS,EAAC,eAAe,EAAE,kBAAkB,EAAE;QACvE,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE9C,wDAAwD;IACxD,oBAAoB,CAAC,iBAAiB,CAAC,CAAA;IAEvC,8BAA8B;IAC9B,IAAI,aAAa,GAAkB,IAAI,CAAA;IAEvC,mDAAmD;IACnD,KAAK,MAAM,KAAK,IAAI,iBAAiB,EAAE,CAAC;QACtC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAA;QAErC,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAEpD,MAAM,eAAe,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QACnD,MAAM,mBAAmB,GAAG,IAAA,gBAAM,EAChC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,EACpD,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpB,CAAA;QAED,sGAAsG;QACtG,IAAI,mBAAmB,GAAG,kBAAkB,GAAG,CAAC;YAAE,SAAQ;QAE1D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,mBAAmB,CAAC,CAAA;QAClE,IAAI,aAAa,KAAK,IAAI,IAAI,UAAU,GAAG,aAAa,EAAE,CAAC;YACzD,aAAa,GAAG,UAAU,CAAA;QAC5B,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,oBAAoB,GAAG,WAAW,CAAA;IACvD,MAAM,sBAAsB,GAAG,IAAI,CAAC,GAAG,CACrC,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3D,CAAA;IAED,8HAA8H;IAC9H,mDAAmD;IACnD,sGAAsG;IACtG,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO;YACL,qBAAqB,EAAE,IAAI;YAC3B,iBAAiB,EAAE,IAAI;YACvB,sBAAsB,EAAE,mBAAmB;YAC3C,qBAAqB,EAAE,IAAI;SAC5B,CAAA;IACH,CAAC;IAED,OAAO;QACL,qBAAqB,EAAE,YAAY;QACnC,iBAAiB,EAAE,aAAa;QAChC,sBAAsB,EAAE,mBAAmB;QAC3C,qBAAqB,EAAE,sBAAsB,GAAG,WAAW;KAC5D,CAAA;AACH,CAAC;AAED,SAAgB,kBAAkB,CAChC,QAA4B,EAC5B,gBAAkC;IAElC,MAAM,UAAU,GAAG,6BAA6B,CAC9C,QAAQ,EACR,gBAAgB,CAAC,UAAU,CAC5B,CAAA;IACD,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;QACzC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAClD,CAAC;QAED,KAAK,EAAE,CAAA;QAEP,IAAI,KAAK,KAAK,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC9C,MAAM,yCAAyC,CAAA;QACjD,CAAC;QAED,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACrE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IAED,IAAI,KAAK,KAAK,gBAAgB,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,wCAAwC,CAAA;IAChD,CAAC;IAED,sCAAsC;IACtC,KAAK,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC/B,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACtC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,oDAAoD,CAAA;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;KACpE,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,gCAAgC,CAC9C,QAAoB,EACpB,UAAkB;IAElB,MAAM,UAAU,GAAG,6BAA6B,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IACtE,MAAM,MAAM,GAAuB,EAAE,CAAA;IACrC,KAAK,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnC,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAA;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YACvC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,MAAM;KACpB,CAAA;AACH,CAAC;AAED,SAAS,6BAA6B,CACpC,QAAoB,EACpB,UAAkB;IAElB,OAAO,IAAA,sBAAS,EAAC,QAAQ,EAAE,UAAU,EAAE;QACrC,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;AAChD,CAAC;AAED,SAAgB,mBAAmB,CACjC,eAAmC;IAEnC,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAA;IAC/C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,iEAAiE,CAAA;IACzE,CAAC;IACD,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,wDAAwD,CAAA;IAChE,CAAC;IACD,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,WAAW;SACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACxB,MAAM,CACL,CAAC,WAAW,EAAE,gBAAgB,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,gBAAgB,GAAG,WAAW,CAAC,aAAa,CAAA;QAC3D,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,WAAW,CAAC,MAAM,IAAI,MAAM,CAAA;QAC9B,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,OAAO,IAAI,MAAM,CAAA;QAC/B,CAAC;QACD,WAAW,CAAC,aAAa,GAAG,gBAAgB,CAAA;QAC5C,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CACjC,gBAAgB,EAChB,WAAW,CAAC,YAAY,CACzB,CAAA;QACD,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CACjC,gBAAgB,EAChB,WAAW,CAAC,YAAY,CACzB,CAAA;QAED,OAAO,WAAW,CAAA;IACpB,CAAC,EACD;QACE,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;QACV,YAAY,EAAE,gBAAgB;QAC9B,YAAY,EAAE,gBAAgB;QAC9B,aAAa,EAAE,gBAAgB;KAChC,CACF,CAAA;IAEH,OAAO;QACL,cAAc,EAAE,MAAM,CAAC,MAAM;QAC7B,eAAe,EAAE,MAAM,CAAC,OAAO;QAC/B,oBAAoB,EAAE,MAAM,CAAC,YAAY;QACzC,oBAAoB,EAAE,MAAM,CAAC,YAAY;QACzC,gBAAgB,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY;KAC5D,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,UAAgC;IAC5D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAM;IACR,CAAC;IAED,oEAAoE;IACpE,MAAM,SAAS,GAKT,EAAE,CAAA;IAER,8DAA8D;IAC9D,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,IAAI,aAAa,GAA4B,IAAI,CAAA;IAEjD,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YACzC,yCAAyC;YACzC,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,aAAa,GAAG,IAAA,kBAAQ,EAAC,aAAa,EAAE,KAAK,EAAE;oBACnD,KAAK,EAAE,QAAQ;iBAChB,CAAC,CAAA;gBACF,aAAa,IAAI,aAAa,CAAA;YAChC,CAAC;YAED,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAA;YACtC,SAAS,CAAC,IAAI,CAAC;gBACb,KAAK;gBACL,YAAY;gBACZ,KAAK,EAAE,UAAU,EAAE;gBACnB,iBAAiB,EAAE,aAAa;aACjC,CAAC,CAAA;YAEF,aAAa,GAAG,KAAK,CAAA;QACvB,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;IAE/D,IAAI,eAAe,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,sDAAsD,CAAA;IAC9D,CAAC;IAED,yDAAyD;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;QACrC,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAEvC,6BAA6B;QAC7B,IAAI,QAAQ,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;YACvC,MAAM,sCAAsC,CAAA;QAC9C,CAAC;QAED,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC1C,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACtC,MAAM,cAAc,GAAG,YAAY,GAAG,cAAc,CAAA;QAEpD,6DAA6D;QAC7D,MAAM,mBAAmB,GACvB,QAAQ,CAAC,iBAAiB,GAAG,UAAU,CAAC,iBAAiB,CAAA;QAE3D,uDAAuD;QACvD,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3D,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;YAE9B,2DAA2D;YAC3D,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC;gBAC3B,MAAM,6DAA6D,CAAA;YACrE,CAAC;YACD,qEAAqE;YACrE,MAAM,iBAAiB,GACrB,SAAS,CAAC,iBAAiB,GAAG,UAAU,CAAC,iBAAiB,CAAA;YAE5D,0FAA0F;YAC1F,IAAI,qBAAqB,GAAG,cAAc,CAAA;YAC1C,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,mBAAmB,GAAG,iBAAiB,GAAG,mBAAmB,CAAA;gBACnE,qBAAqB;oBACnB,cAAc,GAAG,cAAc,GAAG,mBAAmB,CAAA;YACzD,CAAC;YAED,wEAAwE;YACxE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC;AACH,CAAC"}
package/dist/Lift.d.ts CHANGED
@@ -4,7 +4,7 @@ import { SkiAreaSummaryFeature } from './SkiArea';
4
4
  import { Source } from './Source';
5
5
  import { Status } from './Status';
6
6
  export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>;
7
- export type LiftGeometry = GeoJSON.Point | GeoJSON.MultiPoint | GeoJSON.LineString | GeoJSON.MultiLineString | GeoJSON.Polygon | GeoJSON.MultiPolygon;
7
+ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
8
8
  /**
9
9
  * A feature representing a ski lift.
10
10
  *
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
@@ -6,8 +6,8 @@ import { Source } from './Source';
6
6
  import { Status } from './Status';
7
7
  /**
8
8
  * A ski area feature is derived from several sources:
9
- * - OpenStreetMap site=piste relations
10
- * - OpenStreetMap landuse=winter_sports areas that contains ski runs
9
+ * - OpenStreetMap site=piste relations (must contain at least one lift or run of any kind)
10
+ * - OpenStreetMap landuse=winter_sports areas (must contain at least one downhill/nordic (non-backcountry) run or operational lift)
11
11
  * - Skimap.org ski areas
12
12
  *
13
13
  * The processor attempts to merge the data of all sources to create a single ski area feature.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openskidata-format",
3
- "version": "3.2.0",
3
+ "version": "5.0.0",
4
4
  "description": "Data format for OpenSkiMap.org",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,6 +33,7 @@
33
33
  "typescript": "^5"
34
34
  },
35
35
  "dependencies": {
36
+ "@turf/distance": "^7.2.0",
36
37
  "@turf/length": "^7.2.0",
37
38
  "@turf/line-chunk": "^7.2.0",
38
39
  "@turf/meta": "^7.2.0",
@@ -74,23 +74,28 @@ describe('ElevationProfile', () => {
74
74
 
75
75
  const result = getPitchData(profileGeometry)
76
76
 
77
- expect(result.maxPitchInPercent).toBeCloseTo(0.489)
77
+ expect(result.maxPitchInPercent).toBeCloseTo(0.377)
78
78
  expect(result.averagePitchInPercent).toBeCloseTo(0.2482)
79
79
  expect(result.inclinedLengthInMeters).toBeCloseTo(63.0597)
80
80
  })
81
81
 
82
- it('throws error when elevation data is missing', () => {
82
+ it('calculates pitch correctly on a profile with problematic short segments', () => {
83
+ // Create a geometry with artificially short segments and unrealistic elevation changes
83
84
  const profileGeometry: GeoJSON.LineString = {
84
85
  type: 'LineString',
85
86
  coordinates: [
86
- [0, 0],
87
- [1, 1],
87
+ [0, 0, 100],
88
+ [0, 0.00000001, 101], // Very short distance with 1m elevation gain (unrealistically steep)
89
+ [0, 0.001, 102], // Normal segment
90
+ [0, 0.002, 103], // Normal segment
88
91
  ],
89
92
  }
90
93
 
91
- expect(() => getPitchData(profileGeometry)).toThrow(
92
- 'Elevation data is required for slope analysis',
93
- )
94
+ // Using fixed-length segments should smooth out the unrealistic spike
95
+ const result = getPitchData(profileGeometry, 50)
96
+
97
+ // The overall change should be more reasonable when calculated over fixed distances
98
+ expect(result.maxPitchInPercent).toBeLessThan(0.5) // Should be much less than the individual segment pitch
94
99
  })
95
100
  })
96
101
 
@@ -115,6 +120,25 @@ describe('ElevationProfile', () => {
115
120
  11.175409464719593, 47.31138883724759,
116
121
  ])
117
122
  })
123
+
124
+ it('handles points that already have elevation', () => {
125
+ const geometry: GeoJSON.LineString = {
126
+ type: 'LineString',
127
+ coordinates: [
128
+ [11.177452968770694, 47.312650638218656, 1500], // With elevation
129
+ [11.175409464719593, 47.31138883724759, 1450], // With elevation
130
+ ],
131
+ }
132
+
133
+ const result = extractPointsForElevationProfile(geometry, 20)
134
+
135
+ // 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([
139
+ 11.177452968770694, 47.312650638218656,
140
+ ])
141
+ })
118
142
  })
119
143
 
120
144
  describe('getRunElevationData', () => {
@@ -201,8 +225,8 @@ describe('ElevationProfile', () => {
201
225
  const liftFeature: LiftFeature = {
202
226
  type: 'Feature',
203
227
  geometry: {
204
- type: 'Point',
205
- coordinates: [0, 0, 100],
228
+ type: 'MultiLineString',
229
+ coordinates: [[[0, 0, 100], [1, 1, 200]]],
206
230
  },
207
231
  properties: {
208
232
  type: FeatureType.Lift,
@@ -228,5 +252,115 @@ describe('ElevationProfile', () => {
228
252
 
229
253
  expect(getLiftElevationData(liftFeature)).toBeNull()
230
254
  })
255
+
256
+ it('get max aspect', () => {
257
+ const runFeature = {
258
+ type: 'Feature',
259
+ properties: {
260
+ type: 'run',
261
+ id: 'c8c91edb337438a2cc8b96f3e3cc42887384de3b',
262
+ uses: ['nordic'],
263
+ name: 'City-Trail Davos',
264
+ ref: '3',
265
+ description: 'Golfplatz – Bünda',
266
+ difficulty: 'novice',
267
+ difficultyConvention: 'europe',
268
+ status: 'operating',
269
+ oneway: false,
270
+ lit: null,
271
+ gladed: null,
272
+ patrolled: null,
273
+ grooming: 'classic+skating',
274
+ skiAreas: [],
275
+ elevationProfile: {
276
+ heights: [
277
+ 1546, 1546, 1546, 1546, 1546, 1545, 1546, 1546, 1546, 1546, 1546,
278
+ 1545, 1544, 1543, 1543, 1543, 1543, 1543, 1543, 1543, 1544, 1544,
279
+ 1545, 1546, 1547, 1547, 1547, 1547, 1547, 1547, 1547, 1547, 1547,
280
+ 1547, 1547, 1547, 1547, 1547, 1548, 1549, 1549, 1549, 1549, 1549,
281
+ 1550, 1550, 1551, 1552, 1552, 1552, 1552, 1552, 1552, 1552, 1553,
282
+ 1554, 1554, 1554, 1555, 1556, 1557, 1557, 1557, 1557, 1558, 1560,
283
+ 1563, 1564, 1567, 1569, 1570, 1570, 1572, 1572, 1571, 1570, 1572,
284
+ 1574, 1572, 1571, 1571, 1572, 1572, 1573, 1574, 1573, 1572, 1572,
285
+ 1573, 1575, 1576, 1577, 1580, 1583, 1584, 1586,
286
+ ],
287
+ resolution: 25,
288
+ },
289
+ sources: [],
290
+ websites: [],
291
+ wikidata_id: null,
292
+ },
293
+ geometry: {
294
+ type: 'LineString',
295
+ coordinates: [
296
+ [9.8353072, 46.797782200000015, 1546],
297
+ [9.8353827, 46.797835800000016, 1546],
298
+ [9.8356987, 46.798060000000014, 1546],
299
+ [9.8360226, 46.79806910000001, 1546],
300
+ [9.8380422, 46.79899450000001, 1546],
301
+ [9.837935, 46.79945900000001, 1544],
302
+ [9.8376614, 46.79963530000002, 1543],
303
+ [9.8372698, 46.7996886, 1543],
304
+ [9.8370767, 46.79981160000001, 1543],
305
+ [9.8370096, 46.79993550000002, 1544],
306
+ [9.8371799, 46.8000539, 1543],
307
+ [9.837685500000003, 46.8002724, 1543],
308
+ [9.8379698, 46.8005066, 1543],
309
+ [9.8380396, 46.8006763, 1544],
310
+ [9.8379765, 46.8008132, 1544],
311
+ [9.837833, 46.80095, 1545],
312
+ [9.8378196, 46.8011363, 1546],
313
+ [9.8378488, 46.8013207, 1547],
314
+ [9.837881, 46.8014058, 1547],
315
+ [9.837895, 46.8014252, 1547],
316
+ [9.8382166, 46.801688099999986, 1547],
317
+ [9.8383936, 46.8019258, 1547],
318
+ [9.8386216, 46.8029164, 1547],
319
+ [9.8387771, 46.8031256, 1547],
320
+ [9.8394209, 46.8034763, 1547],
321
+ [9.8397186, 46.8037939, 1547],
322
+ [9.8398018, 46.804254799999995, 1548],
323
+ [9.8399613, 46.8044383, 1549],
324
+ [9.8401469, 46.80448349999998, 1549],
325
+ [9.8402551, 46.80451270000001, 1549],
326
+ [9.8405166, 46.80462749999998, 1549],
327
+ [9.8422614, 46.804981799999986, 1552],
328
+ [9.842446900000004, 46.805034, 1552],
329
+ [9.8425913, 46.805118600000014, 1552],
330
+ [9.8426637, 46.8053545, 1552],
331
+ [9.842754900000003, 46.80561510000001, 1552],
332
+ [9.8429278, 46.805750300000014, 1552],
333
+ [9.8434727, 46.80615580000001, 1553],
334
+ [9.8437722, 46.806829199999996, 1554],
335
+ [9.8437728, 46.807147199999996, 1555],
336
+ [9.8442519, 46.8075484, 1557],
337
+ [9.8446648, 46.807873199999996, 1557],
338
+ [9.8448358, 46.80802230000001, 1557],
339
+ [9.8453494, 46.80833250000001, 1562],
340
+ [9.8457157, 46.808526, 1564],
341
+ [9.8463504, 46.8087692, 1569],
342
+ [9.8467433, 46.8090115, 1570],
343
+ [9.8471886, 46.8092391, 1571],
344
+ [9.8474304, 46.8093297, 1572],
345
+ [9.847923499999998, 46.8095145, 1570],
346
+ [9.849087, 46.80979280000001, 1573],
347
+ [9.8495311, 46.809877599999986, 1571],
348
+ [9.850192000000003, 46.8099654, 1572],
349
+ [9.8513658, 46.81011090000001, 1572],
350
+ [9.852096400000004, 46.810099599999994, 1573],
351
+ [9.852562200000007, 46.810143499999995, 1575],
352
+ [9.852670200000004, 46.8103734, 1576],
353
+ [9.852746100000006, 46.8105425, 1577],
354
+ [9.852914200000006, 46.81067139999999, 1580],
355
+ [9.853248800000003, 46.810729599999995, 1583],
356
+ [9.853487800000007, 46.810662799999996, 1584],
357
+ [9.853535100000004, 46.81066409999998, 1586],
358
+ ],
359
+ },
360
+ } as RunFeature
361
+
362
+ const result = getRunElevationData(runFeature)
363
+ expect(result?.maxPitchInPercent).toBeCloseTo(0.12, 2)
364
+ })
231
365
  })
232
366
  })
@@ -1,3 +1,4 @@
1
+ import distance from '@turf/distance'
1
2
  import length from '@turf/length'
2
3
  import { lineChunk } from '@turf/line-chunk'
3
4
  import { LineString } from 'geojson'
@@ -32,10 +33,10 @@ type AscentDescentData = {
32
33
  }
33
34
 
34
35
  type PitchData = {
35
- averagePitchInPercent: number
36
- maxPitchInPercent: number
36
+ averagePitchInPercent: number | null
37
+ maxPitchInPercent: number | null
37
38
  inclinedLengthInMeters: number
38
- overallPitchInPercent: number
39
+ overallPitchInPercent: number | null
39
40
  }
40
41
 
41
42
  export function getElevationData(
@@ -48,17 +49,21 @@ export function getElevationData(
48
49
  }
49
50
  }
50
51
 
51
- export function getPitchData(profileGeometry: GeoJSON.LineString): PitchData {
52
+ export function getPitchData(
53
+ profileGeometry: GeoJSON.LineString,
54
+ resolutionInMeters: number = 25, // Default resolution of 25 meters for pitch calculation
55
+ ): PitchData {
52
56
  const coordinates = profileGeometry.coordinates
53
57
  if (coordinates[0].length < 3) {
54
58
  throw 'Elevation data is required for slope analysis'
55
59
  }
56
60
 
61
+ // For overall calculations (average pitch, total length, etc.)
57
62
  let totalLength = 0
58
63
  let totalInclinedLength = 0
59
64
  let totalElevationChange = 0
60
- let maxUphillPitch = Number.MIN_VALUE
61
- let maxDownhillPitch = Number.MAX_VALUE
65
+
66
+ // First calculate the values needed for overall statistics
62
67
  for (let i = 0; i < coordinates.length - 1; i++) {
63
68
  const before = coordinates[i]
64
69
  const after = coordinates[i + 1]
@@ -74,10 +79,6 @@ export function getPitchData(profileGeometry: GeoJSON.LineString): PitchData {
74
79
  }
75
80
  const lengthInMeters = length(feature, { units: 'meters' })
76
81
 
77
- const percentSlope = elevation / lengthInMeters
78
- maxUphillPitch = Math.max(maxUphillPitch, percentSlope)
79
- maxDownhillPitch = Math.min(maxDownhillPitch, percentSlope)
80
-
81
82
  totalLength += lengthInMeters
82
83
  totalInclinedLength += Math.sqrt(
83
84
  Math.pow(lengthInMeters, 2) + Math.pow(elevation, 2),
@@ -85,17 +86,59 @@ export function getPitchData(profileGeometry: GeoJSON.LineString): PitchData {
85
86
  totalElevationChange += Math.abs(elevation)
86
87
  }
87
88
 
88
- const maxPitch =
89
- Math.abs(maxUphillPitch) > Math.abs(maxDownhillPitch)
90
- ? maxUphillPitch
91
- : maxDownhillPitch
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)
93
+
94
+ // Mutates the geometries in place to add elevation data
95
+ interpolateElevation(chunkedGeometries)
96
+
97
+ // Initialize max pitch values
98
+ let maxPitchValue: number | null = null
99
+
100
+ // Calculate max pitch over the fixed-length chunks
101
+ for (const chunk of chunkedGeometries) {
102
+ const chunkCoords = chunk.coordinates
103
+
104
+ const startPoint = chunkCoords[0]
105
+ const endPoint = chunkCoords[chunkCoords.length - 1]
106
+
107
+ const elevationChange = endPoint[2] - startPoint[2]
108
+ const chunkLengthInMeters = length(
109
+ { type: 'Feature', geometry: chunk, properties: {} },
110
+ { units: 'meters' },
111
+ )
112
+
113
+ // Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
114
+ if (chunkLengthInMeters < resolutionInMeters / 2) continue
115
+
116
+ const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters)
117
+ if (maxPitchValue === null || chunkPitch > maxPitchValue) {
118
+ maxPitchValue = chunkPitch
119
+ }
120
+ }
121
+
92
122
  const averagePitch = totalElevationChange / totalLength
93
123
  const overallElevationChange = Math.abs(
94
124
  coordinates[coordinates.length - 1][2] - coordinates[0][2],
95
125
  )
126
+
127
+ // null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
128
+ // is questionable as a 1m change in elevation over
129
+ // 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
130
+ if (maxPitchValue === null) {
131
+ return {
132
+ averagePitchInPercent: null,
133
+ maxPitchInPercent: null,
134
+ inclinedLengthInMeters: totalInclinedLength,
135
+ overallPitchInPercent: null,
136
+ }
137
+ }
138
+
96
139
  return {
97
140
  averagePitchInPercent: averagePitch,
98
- maxPitchInPercent: maxPitch,
141
+ maxPitchInPercent: maxPitchValue,
99
142
  inclinedLengthInMeters: totalInclinedLength,
100
143
  overallPitchInPercent: overallElevationChange / totalLength,
101
144
  }
@@ -105,20 +148,45 @@ export function getProfileGeometry(
105
148
  geometry: GeoJSON.LineString,
106
149
  elevationProfile: ElevationProfile,
107
150
  ): GeoJSON.LineString {
108
- const points = extractPointsForElevationProfile(
151
+ const geometries = lineChunksForElevationProfile(
109
152
  geometry,
110
153
  elevationProfile.resolution,
111
- ).coordinates
112
- const heights = elevationProfile.heights
113
- if (points.length !== heights.length) {
114
- throw 'Mismatch of points & elevation profile.'
154
+ )
155
+ var index = 0
156
+ for (let subline of geometries) {
157
+ const firstPoint = subline.coordinates[0]
158
+ if (firstPoint.length === 2) {
159
+ firstPoint.push(elevationProfile.heights[index])
160
+ }
161
+
162
+ index++
163
+
164
+ if (index === elevationProfile.heights.length) {
165
+ throw 'Mismatch of points & elevation profile.'
166
+ }
167
+
168
+ const lastPoint = subline.coordinates[subline.coordinates.length - 1]
169
+ if (lastPoint.length === 2) {
170
+ lastPoint.push(elevationProfile.heights[index])
171
+ }
172
+ }
173
+
174
+ if (index !== elevationProfile.heights.length - 1) {
175
+ throw `Mismatch of points & elevation profile`
176
+ }
177
+
178
+ // Check all coordinates have a height
179
+ for (let subline of geometries) {
180
+ for (let point of subline.coordinates) {
181
+ if (point.length < 3) {
182
+ throw 'All points should have an elevation at this point.'
183
+ }
184
+ }
115
185
  }
116
186
 
117
187
  return {
118
188
  type: 'LineString',
119
- coordinates: points.map((point, index) => {
120
- return [point[0], point[1], heights[index]]
121
- }),
189
+ coordinates: geometries.flatMap((geometry) => geometry.coordinates),
122
190
  }
123
191
  }
124
192
 
@@ -129,25 +197,18 @@ export function extractPointsForElevationProfile(
129
197
  geometry: LineString,
130
198
  resolution: number,
131
199
  ): GeoJSON.LineString {
132
- const subfeatures = lineChunk(geometry, resolution, {
133
- units: 'meters',
134
- }).features
200
+ const geometries = lineChunksForElevationProfile(geometry, resolution)
135
201
  const points: GeoJSON.Position[] = []
136
- for (let subline of subfeatures) {
137
- const geometry = subline.geometry
138
- if (geometry) {
139
- const point = geometry.coordinates[0]
140
- points.push([point[0], point[1]])
141
- }
202
+ for (let subline of geometries) {
203
+ const point = subline.coordinates[0]
204
+ points.push([point[0], point[1]])
142
205
  }
143
- if (subfeatures.length > 0) {
144
- const geometry = subfeatures[subfeatures.length - 1].geometry
145
- if (geometry) {
146
- const coords = geometry.coordinates
147
- if (coords.length > 1) {
148
- const point = coords[coords.length - 1]
149
- points.push([point[0], point[1]])
150
- }
206
+ if (geometries.length > 0) {
207
+ const geometry = geometries[geometries.length - 1]
208
+ const coords = geometry.coordinates
209
+ if (coords.length > 1) {
210
+ const point = coords[coords.length - 1]
211
+ points.push([point[0], point[1]])
151
212
  }
152
213
  }
153
214
 
@@ -157,6 +218,15 @@ export function extractPointsForElevationProfile(
157
218
  }
158
219
  }
159
220
 
221
+ function lineChunksForElevationProfile(
222
+ geometry: LineString,
223
+ resolution: number,
224
+ ): GeoJSON.LineString[] {
225
+ return lineChunk(geometry, resolution, {
226
+ units: 'meters',
227
+ }).features.map((feature) => feature.geometry)
228
+ }
229
+
160
230
  export function getAscentAndDescent(
161
231
  profileGeometry: GeoJSON.LineString,
162
232
  ): AscentDescentData {
@@ -207,3 +277,101 @@ export function getAscentAndDescent(
207
277
  verticalInMeters: result.maxElevation - result.minElevation,
208
278
  }
209
279
  }
280
+
281
+ /**
282
+ * Interpolate elevation for points along a list of linestrings that don't already have elevation.
283
+ * Uses geographic distances for proper interpolation.
284
+ * Mutates the geometries in place.
285
+ *
286
+ * @throws Error if no points have elevation data or if segments can't be properly formed
287
+ */
288
+ function interpolateElevation(geometries: GeoJSON.LineString[]) {
289
+ if (geometries.length === 0) {
290
+ return
291
+ }
292
+
293
+ // Flatten all coordinates into a single array for easier processing
294
+ const allPoints: {
295
+ point: GeoJSON.Position
296
+ hasElevation: boolean
297
+ index: number
298
+ distanceFromStart: number // Distance from the beginning of the line
299
+ }[] = []
300
+
301
+ // Collect all points while tracking which ones have elevation
302
+ let pointIndex = 0
303
+ let totalDistance = 0
304
+ let previousPoint: GeoJSON.Position | null = null
305
+
306
+ for (const geometry of geometries) {
307
+ for (const point of geometry.coordinates) {
308
+ // Calculate distance from previous point
309
+ if (previousPoint) {
310
+ const segmentLength = distance(previousPoint, point, {
311
+ units: 'meters',
312
+ })
313
+ totalDistance += segmentLength
314
+ }
315
+
316
+ const hasElevation = point.length >= 3
317
+ allPoints.push({
318
+ point,
319
+ hasElevation,
320
+ index: pointIndex++,
321
+ distanceFromStart: totalDistance,
322
+ })
323
+
324
+ previousPoint = point
325
+ }
326
+ }
327
+
328
+ // Find elevation reference points
329
+ const referencePoints = allPoints.filter((p) => p.hasElevation)
330
+
331
+ if (referencePoints.length <= 1) {
332
+ throw 'At least two points with elevation data are required'
333
+ }
334
+
335
+ // Process segments between each pair of reference points
336
+ for (let i = 0; i < referencePoints.length - 1; i++) {
337
+ const startPoint = referencePoints[i]
338
+ const endPoint = referencePoints[i + 1]
339
+
340
+ // Points must be in sequence
341
+ if (endPoint.index <= startPoint.index) {
342
+ throw 'Reference points must be in sequence'
343
+ }
344
+
345
+ const startElevation = startPoint.point[2]
346
+ const endElevation = endPoint.point[2]
347
+ const elevationDelta = endElevation - startElevation
348
+
349
+ // Calculate the geographic distance between reference points
350
+ const distanceBetweenRefs =
351
+ endPoint.distanceFromStart - startPoint.distanceFromStart
352
+
353
+ // Interpolate elevations for points between references
354
+ for (let j = startPoint.index + 1; j < endPoint.index; j++) {
355
+ const pointData = allPoints[j]
356
+
357
+ // Only interpolate if point doesn't already have elevation
358
+ if (pointData.hasElevation) {
359
+ throw "Can't interpolate elevation for points that already have it"
360
+ }
361
+ // Calculate how far along this point is between the reference points
362
+ const distanceFromStart =
363
+ pointData.distanceFromStart - startPoint.distanceFromStart
364
+
365
+ // Its possible both references are the same point, in which case we use the ref elevation
366
+ let interpolatedElevation = startElevation
367
+ if (distanceBetweenRefs > 0) {
368
+ const interpolationFactor = distanceFromStart / distanceBetweenRefs
369
+ interpolatedElevation =
370
+ startElevation + elevationDelta * interpolationFactor
371
+ }
372
+
373
+ // Add elevation to the existing point array (using reference semantics)
374
+ pointData.point.push(interpolatedElevation)
375
+ }
376
+ }
377
+ }
package/src/Lift.ts CHANGED
@@ -7,13 +7,7 @@ import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
7
7
 
8
8
  export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>
9
9
 
10
- export type LiftGeometry =
11
- | GeoJSON.Point
12
- | GeoJSON.MultiPoint
13
- | GeoJSON.LineString
14
- | GeoJSON.MultiLineString
15
- | GeoJSON.Polygon
16
- | GeoJSON.MultiPolygon
10
+ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
17
11
 
18
12
  /**
19
13
  * A feature representing a ski lift.
package/src/SkiArea.ts CHANGED
@@ -7,8 +7,8 @@ import { Status } from './Status'
7
7
 
8
8
  /**
9
9
  * A ski area feature is derived from several sources:
10
- * - OpenStreetMap site=piste relations
11
- * - OpenStreetMap landuse=winter_sports areas that contains ski runs
10
+ * - OpenStreetMap site=piste relations (must contain at least one lift or run of any kind)
11
+ * - OpenStreetMap landuse=winter_sports areas (must contain at least one downhill/nordic (non-backcountry) run or operational lift)
12
12
  * - Skimap.org ski areas
13
13
  *
14
14
  * The processor attempts to merge the data of all sources to create a single ski area feature.