openskidata-format 8.0.0 → 9.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.
- package/dist/ElevationProfile.d.ts +1 -0
- package/dist/ElevationProfile.js +21 -20
- package/dist/ElevationProfile.js.map +1 -1
- package/dist/Lift.d.ts +11 -1
- package/dist/Lift.js.map +1 -1
- package/dist/SkiArea.d.ts +2 -0
- package/dist/Spot.d.ts +6 -0
- package/dist/Spot.js.map +1 -1
- package/package.json +1 -1
- package/src/ElevationProfile.test.ts +102 -1
- package/src/ElevationProfile.ts +24 -22
- package/src/Lift.ts +12 -1
- package/src/SkiArea.ts +6 -1
- package/src/Spot.ts +10 -0
|
@@ -29,6 +29,7 @@ type PitchData = {
|
|
|
29
29
|
maxPitchInPercent: number | null;
|
|
30
30
|
inclinedLengthInMeters: number;
|
|
31
31
|
overallPitchInPercent: number | null;
|
|
32
|
+
pitchCalculationResolutionInMeters: number;
|
|
32
33
|
};
|
|
33
34
|
export declare function getElevationData(profileGeometry: GeoJSON.LineString): ElevationData;
|
|
34
35
|
export declare function getPitchData(profileGeometry: GeoJSON.LineString, resolutionInMeters?: number): PitchData;
|
package/dist/ElevationProfile.js
CHANGED
|
@@ -44,8 +44,23 @@ function getPitchData(profileGeometry, resolutionInMeters = 25) {
|
|
|
44
44
|
totalInclinedLength += Math.sqrt(Math.pow(lengthInMeters, 2) + Math.pow(elevation, 2));
|
|
45
45
|
totalElevationChange += Math.abs(elevation);
|
|
46
46
|
}
|
|
47
|
-
//
|
|
48
|
-
|
|
47
|
+
// If the line is too short relative to the resolution, pitch calculations are unreliable
|
|
48
|
+
// due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
|
|
49
|
+
// which can easily happen due to elevation data precision issues.
|
|
50
|
+
if (totalLength < resolutionInMeters / 2) {
|
|
51
|
+
return {
|
|
52
|
+
averagePitchInPercent: null,
|
|
53
|
+
maxPitchInPercent: null,
|
|
54
|
+
inclinedLengthInMeters: totalInclinedLength,
|
|
55
|
+
overallPitchInPercent: null,
|
|
56
|
+
pitchCalculationResolutionInMeters: totalLength,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Calculate a resolution that divides the distance evenly, with the constraint that it must be <= resolutionInMeters
|
|
60
|
+
const numSegments = Math.ceil(totalLength / resolutionInMeters);
|
|
61
|
+
const actualResolution = totalLength / numSegments;
|
|
62
|
+
// Chunk the line into segments using the calculated resolution for max pitch calculation
|
|
63
|
+
const chunkedGeometries = (0, line_chunk_1.lineChunk)(profileGeometry, actualResolution, {
|
|
49
64
|
units: 'meters',
|
|
50
65
|
}).features.map((feature) => feature.geometry);
|
|
51
66
|
// turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
|
|
@@ -55,41 +70,27 @@ function getPitchData(profileGeometry, resolutionInMeters = 25) {
|
|
|
55
70
|
stripNonOriginalElevations(chunkedGeometries, coordinates);
|
|
56
71
|
// Mutates the geometries in place to add elevation data
|
|
57
72
|
interpolateElevation(chunkedGeometries);
|
|
58
|
-
//
|
|
59
|
-
let maxPitchValue =
|
|
60
|
-
// Calculate max pitch over the fixed-length chunks
|
|
73
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
74
|
+
let maxPitchValue = 0;
|
|
61
75
|
for (const chunk of chunkedGeometries) {
|
|
62
76
|
const chunkCoords = chunk.coordinates;
|
|
63
77
|
const startPoint = chunkCoords[0];
|
|
64
78
|
const endPoint = chunkCoords[chunkCoords.length - 1];
|
|
65
79
|
const elevationChange = endPoint[2] - startPoint[2];
|
|
66
80
|
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
81
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters);
|
|
71
|
-
if (
|
|
82
|
+
if (chunkPitch > maxPitchValue) {
|
|
72
83
|
maxPitchValue = chunkPitch;
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
const averagePitch = totalElevationChange / totalLength;
|
|
76
87
|
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
88
|
return {
|
|
89
89
|
averagePitchInPercent: averagePitch,
|
|
90
90
|
maxPitchInPercent: maxPitchValue,
|
|
91
91
|
inclinedLengthInMeters: totalInclinedLength,
|
|
92
92
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
93
|
+
pitchCalculationResolutionInMeters: actualResolution,
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
function getProfileGeometry(geometry, elevationProfile) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElevationProfile.js","sourceRoot":"","sources":["../src/ElevationProfile.ts"],"names":[],"mappings":";;
|
|
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":";;;
|
|
1
|
+
{"version":3,"file":"Lift.js","sourceRoot":"","sources":["../src/Lift.ts"],"names":[],"mappings":"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"}
|
package/dist/SkiArea.d.ts
CHANGED
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":";;;
|
|
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
|
@@ -74,7 +74,11 @@ describe('ElevationProfile', () => {
|
|
|
74
74
|
|
|
75
75
|
const result = getPitchData(profileGeometry)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
// With the new resolution calculation, the actual resolution divides the distance evenly
|
|
78
|
+
// Total length is ~60.44m, with default resolution of 25m: numSegments = ceil(60.44/25) = 3, actualResolution = 60.44/3 ≈ 20.15m
|
|
79
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.15, 1)
|
|
80
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(25)
|
|
81
|
+
expect(result.maxPitchInPercent).toBeCloseTo(0.489, 2)
|
|
78
82
|
expect(result.averagePitchInPercent).toBeCloseTo(0.2482)
|
|
79
83
|
expect(result.inclinedLengthInMeters).toBeCloseTo(63.0597)
|
|
80
84
|
})
|
|
@@ -97,6 +101,101 @@ describe('ElevationProfile', () => {
|
|
|
97
101
|
// The overall change should be more reasonable when calculated over fixed distances
|
|
98
102
|
expect(result.maxPitchInPercent).toBeLessThan(0.5) // Should be much less than the individual segment pitch
|
|
99
103
|
})
|
|
104
|
+
|
|
105
|
+
it('calculates resolution that divides distance evenly', () => {
|
|
106
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
107
|
+
type: 'LineString',
|
|
108
|
+
coordinates: [
|
|
109
|
+
[0, 0, 100],
|
|
110
|
+
[0, 0.001, 110], // ~111m distance
|
|
111
|
+
[0, 0.002, 105], // ~111m distance
|
|
112
|
+
],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = getPitchData(profileGeometry, 50)
|
|
116
|
+
|
|
117
|
+
// Resolution should be <= 50
|
|
118
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
|
|
119
|
+
|
|
120
|
+
// With ~222m total length and 50m input resolution:
|
|
121
|
+
// numSegments = ceil(222/50) = 5
|
|
122
|
+
// actualResolution = 222/5 = 44.4m
|
|
123
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(44.4, 0)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('uses total length as resolution when line is shorter than input resolution', () => {
|
|
127
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
128
|
+
type: 'LineString',
|
|
129
|
+
coordinates: [
|
|
130
|
+
[0, 0, 100],
|
|
131
|
+
[0, 0.0001, 110], // ~11m distance
|
|
132
|
+
],
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = getPitchData(profileGeometry, 50)
|
|
136
|
+
|
|
137
|
+
// Resolution should equal the total length since numSegments = ceil(11/50) = 1
|
|
138
|
+
// actualResolution = 11/1 = 11m
|
|
139
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(11.1, 0)
|
|
140
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('ensures all chunks are processed without skipping short chunks', () => {
|
|
144
|
+
// Create a line that would previously have a short final chunk
|
|
145
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
146
|
+
type: 'LineString',
|
|
147
|
+
coordinates: [
|
|
148
|
+
[11.177425600412874, 47.31265682344346, 100],
|
|
149
|
+
[11.177224899122194, 47.312533118812354, 120], // ~20m, steep
|
|
150
|
+
[11.176823496540862, 47.31229807921545, 105], // ~40m, gentle descent
|
|
151
|
+
],
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const result = getPitchData(profileGeometry, 25)
|
|
155
|
+
|
|
156
|
+
// With evenly divided chunks, no chunks should be skipped
|
|
157
|
+
// The max pitch should be calculated from all segments
|
|
158
|
+
expect(result.maxPitchInPercent).toBeGreaterThan(0)
|
|
159
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.15, 1)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('returns null pitch data for very short lines', () => {
|
|
163
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
164
|
+
type: 'LineString',
|
|
165
|
+
coordinates: [
|
|
166
|
+
[0, 0, 100],
|
|
167
|
+
[0, 0.00001, 101], // ~1.11m distance
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const result = getPitchData(profileGeometry, 25)
|
|
172
|
+
|
|
173
|
+
// Lines shorter than half the resolution should return null pitch data
|
|
174
|
+
// because elevation data resolution makes pitch calculations unreliable
|
|
175
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(1.11, 1)
|
|
176
|
+
expect(result.maxPitchInPercent).toBeNull()
|
|
177
|
+
expect(result.averagePitchInPercent).toBeNull()
|
|
178
|
+
expect(result.overallPitchInPercent).toBeNull()
|
|
179
|
+
expect(result.inclinedLengthInMeters).toBeGreaterThan(0) // This should still be calculated
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('calculates pitch data for lines just above the minimum threshold', () => {
|
|
183
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
184
|
+
type: 'LineString',
|
|
185
|
+
coordinates: [
|
|
186
|
+
[0, 0, 100],
|
|
187
|
+
[0, 0.00015, 105], // ~16.7m distance, just above half of 25m
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = getPitchData(profileGeometry, 25)
|
|
192
|
+
|
|
193
|
+
// Lines at or above half the resolution should calculate pitch data
|
|
194
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(16.7, 0)
|
|
195
|
+
expect(result.maxPitchInPercent).not.toBeNull()
|
|
196
|
+
expect(result.averagePitchInPercent).not.toBeNull()
|
|
197
|
+
expect(result.overallPitchInPercent).not.toBeNull()
|
|
198
|
+
})
|
|
100
199
|
})
|
|
101
200
|
|
|
102
201
|
describe('extractPointsForElevationProfile', () => {
|
|
@@ -239,6 +338,7 @@ describe('ElevationProfile', () => {
|
|
|
239
338
|
id: '123',
|
|
240
339
|
liftType: LiftType.ChairLift,
|
|
241
340
|
status: Status.Operating,
|
|
341
|
+
access: null,
|
|
242
342
|
name: 'Test Lift',
|
|
243
343
|
skiAreas: [],
|
|
244
344
|
sources: [],
|
|
@@ -252,6 +352,7 @@ describe('ElevationProfile', () => {
|
|
|
252
352
|
detachable: null,
|
|
253
353
|
bubble: null,
|
|
254
354
|
heating: null,
|
|
355
|
+
stations: [],
|
|
255
356
|
websites: [],
|
|
256
357
|
wikidataID: null,
|
|
257
358
|
places: [],
|
package/src/ElevationProfile.ts
CHANGED
|
@@ -37,6 +37,7 @@ type PitchData = {
|
|
|
37
37
|
maxPitchInPercent: number | null
|
|
38
38
|
inclinedLengthInMeters: number
|
|
39
39
|
overallPitchInPercent: number | null
|
|
40
|
+
pitchCalculationResolutionInMeters: number
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function getElevationData(
|
|
@@ -86,8 +87,25 @@ export function getPitchData(
|
|
|
86
87
|
totalElevationChange += Math.abs(elevation)
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
-
|
|
90
|
+
// If the line is too short relative to the resolution, pitch calculations are unreliable
|
|
91
|
+
// due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
|
|
92
|
+
// which can easily happen due to elevation data precision issues.
|
|
93
|
+
if (totalLength < resolutionInMeters / 2) {
|
|
94
|
+
return {
|
|
95
|
+
averagePitchInPercent: null,
|
|
96
|
+
maxPitchInPercent: null,
|
|
97
|
+
inclinedLengthInMeters: totalInclinedLength,
|
|
98
|
+
overallPitchInPercent: null,
|
|
99
|
+
pitchCalculationResolutionInMeters: totalLength,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Calculate a resolution that divides the distance evenly, with the constraint that it must be <= resolutionInMeters
|
|
104
|
+
const numSegments = Math.ceil(totalLength / resolutionInMeters)
|
|
105
|
+
const actualResolution = totalLength / numSegments
|
|
106
|
+
|
|
107
|
+
// Chunk the line into segments using the calculated resolution for max pitch calculation
|
|
108
|
+
const chunkedGeometries = lineChunk(profileGeometry, actualResolution, {
|
|
91
109
|
units: 'meters',
|
|
92
110
|
}).features.map((feature) => feature.geometry)
|
|
93
111
|
|
|
@@ -100,10 +118,8 @@ export function getPitchData(
|
|
|
100
118
|
// Mutates the geometries in place to add elevation data
|
|
101
119
|
interpolateElevation(chunkedGeometries)
|
|
102
120
|
|
|
103
|
-
//
|
|
104
|
-
let maxPitchValue
|
|
105
|
-
|
|
106
|
-
// Calculate max pitch over the fixed-length chunks
|
|
121
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
122
|
+
let maxPitchValue = 0
|
|
107
123
|
for (const chunk of chunkedGeometries) {
|
|
108
124
|
const chunkCoords = chunk.coordinates
|
|
109
125
|
|
|
@@ -116,11 +132,8 @@ export function getPitchData(
|
|
|
116
132
|
{ units: 'meters' },
|
|
117
133
|
)
|
|
118
134
|
|
|
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
135
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters)
|
|
123
|
-
if (
|
|
136
|
+
if (chunkPitch > maxPitchValue) {
|
|
124
137
|
maxPitchValue = chunkPitch
|
|
125
138
|
}
|
|
126
139
|
}
|
|
@@ -130,23 +143,12 @@ export function getPitchData(
|
|
|
130
143
|
coordinates[coordinates.length - 1][2] - coordinates[0][2],
|
|
131
144
|
)
|
|
132
145
|
|
|
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
146
|
return {
|
|
146
147
|
averagePitchInPercent: averagePitch,
|
|
147
148
|
maxPitchInPercent: maxPitchValue,
|
|
148
149
|
inclinedLengthInMeters: totalInclinedLength,
|
|
149
150
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
151
|
+
pitchCalculationResolutionInMeters: actualResolution,
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
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']?: {
|
|
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
|
/**
|