openskidata-format 7.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 +56 -57
- package/dist/ElevationProfile.js.map +1 -1
- package/dist/FeatureType.d.ts +2 -1
- package/dist/FeatureType.js +1 -0
- package/dist/FeatureType.js.map +1 -1
- package/dist/Lift.d.ts +11 -1
- package/dist/Lift.js.map +1 -1
- package/dist/Run.d.ts +4 -0
- package/dist/Run.js.map +1 -1
- package/dist/SkiArea.d.ts +2 -0
- package/dist/Spot.d.ts +116 -0
- package/dist/Spot.js +33 -0
- package/dist/Spot.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/ElevationProfile.test.ts +108 -1
- package/src/ElevationProfile.ts +66 -67
- package/src/FeatureType.ts +1 -0
- package/src/Lift.ts +12 -1
- package/src/Run.ts +4 -0
- package/src/SkiArea.ts +6 -1
- package/src/SlopeGradingScale.test.ts +8 -0
- package/src/Spot.ts +138 -0
- package/src/index.ts +1 -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,94 +44,82 @@ 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);
|
|
66
|
+
// turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
|
|
67
|
+
// but we need distance-based interpolation. Strip Z from non-original vertices so
|
|
68
|
+
// interpolateElevation can add them correctly.
|
|
69
|
+
// https://github.com/Turfjs/turf/issues/3007
|
|
70
|
+
stripNonOriginalElevations(chunkedGeometries, coordinates);
|
|
51
71
|
// Mutates the geometries in place to add elevation data
|
|
52
72
|
interpolateElevation(chunkedGeometries);
|
|
53
|
-
//
|
|
54
|
-
let maxPitchValue =
|
|
55
|
-
// Calculate max pitch over the fixed-length chunks
|
|
73
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
74
|
+
let maxPitchValue = 0;
|
|
56
75
|
for (const chunk of chunkedGeometries) {
|
|
57
76
|
const chunkCoords = chunk.coordinates;
|
|
58
77
|
const startPoint = chunkCoords[0];
|
|
59
78
|
const endPoint = chunkCoords[chunkCoords.length - 1];
|
|
60
79
|
const elevationChange = endPoint[2] - startPoint[2];
|
|
61
80
|
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
81
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters);
|
|
66
|
-
if (
|
|
82
|
+
if (chunkPitch > maxPitchValue) {
|
|
67
83
|
maxPitchValue = chunkPitch;
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
86
|
const averagePitch = totalElevationChange / totalLength;
|
|
71
87
|
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
|
-
}
|
|
83
88
|
return {
|
|
84
89
|
averagePitchInPercent: averagePitch,
|
|
85
90
|
maxPitchInPercent: maxPitchValue,
|
|
86
91
|
inclinedLengthInMeters: totalInclinedLength,
|
|
87
92
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
93
|
+
pitchCalculationResolutionInMeters: actualResolution,
|
|
88
94
|
};
|
|
89
95
|
}
|
|
90
96
|
function getProfileGeometry(geometry, elevationProfile) {
|
|
91
|
-
const
|
|
92
|
-
|
|
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) {
|
|
97
|
+
const profileLine = extractPointsForElevationProfile(geometry, elevationProfile.resolution);
|
|
98
|
+
if (profileLine.coordinates.length !== elevationProfile.heights.length) {
|
|
108
99
|
throw `Mismatch of points & elevation profile`;
|
|
109
100
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw 'All points should have an elevation at this point.';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
101
|
+
for (let i = 0; i < profileLine.coordinates.length; i++) {
|
|
102
|
+
const point = profileLine.coordinates[i];
|
|
103
|
+
const height = elevationProfile.heights[i];
|
|
104
|
+
point.push(height);
|
|
117
105
|
}
|
|
118
|
-
return
|
|
119
|
-
type: 'LineString',
|
|
120
|
-
coordinates: geometries.flatMap((geometry) => geometry.coordinates),
|
|
121
|
-
};
|
|
106
|
+
return profileLine;
|
|
122
107
|
}
|
|
123
108
|
/**
|
|
124
109
|
* Determines the points to use for an elevation profile.
|
|
125
110
|
*/
|
|
126
111
|
function extractPointsForElevationProfile(geometry, resolution) {
|
|
127
|
-
|
|
112
|
+
// Important: Z coordinates in the output of lineChunk may be incorrect, ignore them (https://github.com/Turfjs/turf/issues/3007)
|
|
113
|
+
const lineChunks = (0, line_chunk_1.lineChunk)(geometry, resolution, {
|
|
114
|
+
units: 'meters',
|
|
115
|
+
}).features.map((feature) => feature.geometry);
|
|
128
116
|
const points = [];
|
|
129
|
-
for (let subline of
|
|
117
|
+
for (let subline of lineChunks) {
|
|
130
118
|
const point = subline.coordinates[0];
|
|
131
119
|
points.push([point[0], point[1]]);
|
|
132
120
|
}
|
|
133
|
-
if (
|
|
134
|
-
const geometry =
|
|
121
|
+
if (lineChunks.length > 0) {
|
|
122
|
+
const geometry = lineChunks[lineChunks.length - 1];
|
|
135
123
|
const coords = geometry.coordinates;
|
|
136
124
|
if (coords.length > 1) {
|
|
137
125
|
const point = coords[coords.length - 1];
|
|
@@ -143,11 +131,6 @@ function extractPointsForElevationProfile(geometry, resolution) {
|
|
|
143
131
|
coordinates: points,
|
|
144
132
|
};
|
|
145
133
|
}
|
|
146
|
-
function lineChunksForElevationProfile(geometry, resolution) {
|
|
147
|
-
return (0, line_chunk_1.lineChunk)(geometry, resolution, {
|
|
148
|
-
units: 'meters',
|
|
149
|
-
}).features.map((feature) => feature.geometry);
|
|
150
|
-
}
|
|
151
134
|
function getAscentAndDescent(profileGeometry) {
|
|
152
135
|
const coordinates = profileGeometry.coordinates;
|
|
153
136
|
if (coordinates.length === 0) {
|
|
@@ -186,6 +169,22 @@ function getAscentAndDescent(profileGeometry) {
|
|
|
186
169
|
verticalInMeters: result.maxElevation - result.minElevation,
|
|
187
170
|
};
|
|
188
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Strip Z coordinates from points that were created by lineChunk (not original vertices).
|
|
174
|
+
* Original vertices are identified by matching lon/lat coordinates.
|
|
175
|
+
* Mutates the geometries in place.
|
|
176
|
+
*/
|
|
177
|
+
function stripNonOriginalElevations(geometries, originalCoordinates) {
|
|
178
|
+
const originalSet = new Set(originalCoordinates.map((c) => `${c[0]},${c[1]}`));
|
|
179
|
+
for (const geometry of geometries) {
|
|
180
|
+
for (const point of geometry.coordinates) {
|
|
181
|
+
const key = `${point[0]},${point[1]}`;
|
|
182
|
+
if (!originalSet.has(key) && point.length >= 3) {
|
|
183
|
+
point.length = 2;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
189
188
|
/**
|
|
190
189
|
* Interpolate elevation for points along a list of linestrings that don't already have elevation.
|
|
191
190
|
* Uses geographic distances for proper interpolation.
|
|
@@ -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/FeatureType.d.ts
CHANGED
package/dist/FeatureType.js
CHANGED
package/dist/FeatureType.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FeatureType.js","sourceRoot":"","sources":["../src/FeatureType.ts"],"names":[],"mappings":";;;AAAA,IAAY,
|
|
1
|
+
{"version":3,"file":"FeatureType.js","sourceRoot":"","sources":["../src/FeatureType.ts"],"names":[],"mappings":";;;AAAA,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,0BAAW,CAAA;IACX,4BAAa,CAAA;IACb,kCAAmB,CAAA;IACnB,4BAAa,CAAA;AACf,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB"}
|
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/Run.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type RunFeature = GeoJSON.Feature<RunGeometry, RunProperties>;
|
|
|
42
42
|
* @property {boolean | null} lit - Whether the run has lighting for night skiing, derived from the OpenStreetMap "piste:lit" or "lit" tag.
|
|
43
43
|
* @property {boolean | null} gladed - Whether the run is through gladed/tree terrain, derived from the OpenStreetMap "piste:gladed" or "gladed" tag.
|
|
44
44
|
* @property {boolean | null} patrolled - Whether the run is patrolled by ski patrol, derived from the OpenStreetMap "piste:patrolled" or "patrolled" tag.
|
|
45
|
+
* @property {boolean | null} snowmaking - Whether the run has its operation secured through snowmaking, derived from the OpenStreetMap "piste:snowmaking" tag.
|
|
46
|
+
* @property {boolean | null} snowfarming - Whether the run has its early season operation secured through snowfarming (storing snow from previous season), derived from the OpenStreetMap "piste:snowfarming" tag.
|
|
45
47
|
* @property {RunGrooming | null} grooming - Grooming status/type of the run, derived from the OpenStreetMap "piste:grooming" tag. If not specified explicitly, for difficulties "expert", "freeride", and "extreme", grooming is assumed to be "backcountry".
|
|
46
48
|
* @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this run belongs to. Derived from the OpenStreetMap site=piste relation or landuse=winter_sports area and proximity to ski area features. Runs with "backcountry" grooming are not associated with ski areas unless they have a "piste:patrolled=yes" or "patrolled=yes" OpenStreetMap tag, or are part of a "site=piste" ski area relation.
|
|
47
49
|
* @property {ElevationProfile | null} elevationProfile - Elevation profile of the run, only available for runs with LineString geometry.
|
|
@@ -65,6 +67,8 @@ export type RunProperties = {
|
|
|
65
67
|
lit: boolean | null;
|
|
66
68
|
gladed: boolean | null;
|
|
67
69
|
patrolled: boolean | null;
|
|
70
|
+
snowmaking: boolean | null;
|
|
71
|
+
snowfarming: boolean | null;
|
|
68
72
|
grooming: RunGrooming | null;
|
|
69
73
|
skiAreas: SkiAreaSummaryFeature[];
|
|
70
74
|
elevationProfile: ElevationProfile | null;
|
package/dist/Run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Run.js","sourceRoot":"","sources":["../src/Run.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Run.js","sourceRoot":"","sources":["../src/Run.ts"],"names":[],"mappings":"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"}
|
package/dist/SkiArea.d.ts
CHANGED
package/dist/Spot.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as GeoJSON from 'geojson';
|
|
2
|
+
import { FeatureType } from './FeatureType';
|
|
3
|
+
import { Place } from './Place';
|
|
4
|
+
import { SkiAreaSummaryFeature } from './SkiArea';
|
|
5
|
+
import { Source } from './Source';
|
|
6
|
+
/**
|
|
7
|
+
* A GeoJSON feature representing a spot (point of interest) in or around a ski area.
|
|
8
|
+
*
|
|
9
|
+
* Spots are used to represent lift stations, road crossings, or certain terrain features.
|
|
10
|
+
*/
|
|
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>;
|
|
16
|
+
/**
|
|
17
|
+
* Geometry type for spots - always a point.
|
|
18
|
+
*/
|
|
19
|
+
export type SpotGeometry = GeoJSON.Point;
|
|
20
|
+
/**
|
|
21
|
+
* Union of all spot property types.
|
|
22
|
+
*/
|
|
23
|
+
export type SpotProperties = CrossingSpotProperties | LiftStationSpotProperties | AvalancheTransceiverTrainingSpotProperties | AvalancheTransceiverCheckpointSpotProperties | HalfpipeSpotProperties;
|
|
24
|
+
/**
|
|
25
|
+
* Base properties shared by all spot types.
|
|
26
|
+
*/
|
|
27
|
+
export type SpotBaseProperties = {
|
|
28
|
+
type: FeatureType.Spot;
|
|
29
|
+
id: string;
|
|
30
|
+
skiAreas: SkiAreaSummaryFeature[];
|
|
31
|
+
sources: Source[];
|
|
32
|
+
places: Place[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Types of spots that can be found in or around ski areas.
|
|
36
|
+
*/
|
|
37
|
+
export declare enum SpotType {
|
|
38
|
+
Crossing = "crossing",
|
|
39
|
+
LiftStation = "lift_station",
|
|
40
|
+
AvalancheTransceiverTraining = "avalanche_transceiver_training",
|
|
41
|
+
AvalancheTransceiverCheckpoint = "avalanche_transceiver_checkpoint",
|
|
42
|
+
Halfpipe = "halfpipe"
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A crossing is a point where a ski run intersects with a road or path,
|
|
46
|
+
* potentially requiring skiers to remove their skis.
|
|
47
|
+
*
|
|
48
|
+
* From OpenStreetMap node with tag `piste:dismount=yes|no|sometimes`.
|
|
49
|
+
*/
|
|
50
|
+
export type CrossingSpotProperties = SpotBaseProperties & {
|
|
51
|
+
spotType: SpotType.Crossing;
|
|
52
|
+
dismount: DismountRequirement;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Whether dismounting from skis is required at a crossing.
|
|
56
|
+
*/
|
|
57
|
+
export declare enum DismountRequirement {
|
|
58
|
+
Yes = "yes",
|
|
59
|
+
No = "no",
|
|
60
|
+
Sometimes = "sometimes"
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A lift station is a boarding or alighting point for a ski lift.
|
|
64
|
+
*
|
|
65
|
+
* From OpenStreetMap node or area with tag `aerialway=station`.
|
|
66
|
+
* Areas are represented as a point (centroid).
|
|
67
|
+
*
|
|
68
|
+
* @property {string | null} name - Name of the station, from OpenStreetMap tag `name`.
|
|
69
|
+
* @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
|
|
70
|
+
* @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
|
|
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.
|
|
73
|
+
*/
|
|
74
|
+
export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
75
|
+
spotType: SpotType.LiftStation;
|
|
76
|
+
name: string | null;
|
|
77
|
+
position: LiftStationPosition | null;
|
|
78
|
+
entry: boolean | null;
|
|
79
|
+
exit: boolean | null;
|
|
80
|
+
liftId: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Position of a lift station along the lift line.
|
|
84
|
+
*/
|
|
85
|
+
export declare enum LiftStationPosition {
|
|
86
|
+
Top = "top",
|
|
87
|
+
Mid = "mid",
|
|
88
|
+
Bottom = "bottom"
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A designated area where skiers can practice using avalanche transceivers.
|
|
92
|
+
*
|
|
93
|
+
* From OpenStreetMap node or area with tags `amenity=avalanche_transceiver` and `avalanche_transceiver=training`.
|
|
94
|
+
* Areas are represented as a point (centroid).
|
|
95
|
+
*/
|
|
96
|
+
export type AvalancheTransceiverTrainingSpotProperties = SpotBaseProperties & {
|
|
97
|
+
spotType: SpotType.AvalancheTransceiverTraining;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* A checkpoint where skiers can verify their avalanche transceiver is functioning.
|
|
101
|
+
*
|
|
102
|
+
* From OpenStreetMap node or area with tags `amenity=avalanche_transceiver` and `avalanche_transceiver=checkpoint`.
|
|
103
|
+
* Areas are represented as a point (centroid).
|
|
104
|
+
*/
|
|
105
|
+
export type AvalancheTransceiverCheckpointSpotProperties = SpotBaseProperties & {
|
|
106
|
+
spotType: SpotType.AvalancheTransceiverCheckpoint;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* A halfpipe structure for freestyle skiing or snowboarding.
|
|
110
|
+
*
|
|
111
|
+
* From OpenStreetMap node/way/area with tag `man_made=piste:halfpipe`.
|
|
112
|
+
* Areas are represented as a point (centroid).
|
|
113
|
+
*/
|
|
114
|
+
export type HalfpipeSpotProperties = SpotBaseProperties & {
|
|
115
|
+
spotType: SpotType.Halfpipe;
|
|
116
|
+
};
|
package/dist/Spot.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LiftStationPosition = exports.DismountRequirement = exports.SpotType = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Types of spots that can be found in or around ski areas.
|
|
6
|
+
*/
|
|
7
|
+
var SpotType;
|
|
8
|
+
(function (SpotType) {
|
|
9
|
+
SpotType["Crossing"] = "crossing";
|
|
10
|
+
SpotType["LiftStation"] = "lift_station";
|
|
11
|
+
SpotType["AvalancheTransceiverTraining"] = "avalanche_transceiver_training";
|
|
12
|
+
SpotType["AvalancheTransceiverCheckpoint"] = "avalanche_transceiver_checkpoint";
|
|
13
|
+
SpotType["Halfpipe"] = "halfpipe";
|
|
14
|
+
})(SpotType || (exports.SpotType = SpotType = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Whether dismounting from skis is required at a crossing.
|
|
17
|
+
*/
|
|
18
|
+
var DismountRequirement;
|
|
19
|
+
(function (DismountRequirement) {
|
|
20
|
+
DismountRequirement["Yes"] = "yes";
|
|
21
|
+
DismountRequirement["No"] = "no";
|
|
22
|
+
DismountRequirement["Sometimes"] = "sometimes";
|
|
23
|
+
})(DismountRequirement || (exports.DismountRequirement = DismountRequirement = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Position of a lift station along the lift line.
|
|
26
|
+
*/
|
|
27
|
+
var LiftStationPosition;
|
|
28
|
+
(function (LiftStationPosition) {
|
|
29
|
+
LiftStationPosition["Top"] = "top";
|
|
30
|
+
LiftStationPosition["Mid"] = "mid";
|
|
31
|
+
LiftStationPosition["Bottom"] = "bottom";
|
|
32
|
+
})(LiftStationPosition || (exports.LiftStationPosition = LiftStationPosition = {}));
|
|
33
|
+
//# sourceMappingURL=Spot.js.map
|
package/dist/Spot.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;AA+CA;;GAEG;AACH,IAAY,QAMX;AAND,WAAY,QAAQ;IAClB,iCAAqB,CAAA;IACrB,wCAA4B,CAAA;IAC5B,2EAA+D,CAAA;IAC/D,+EAAmE,CAAA;IACnE,iCAAqB,CAAA;AACvB,CAAC,EANW,QAAQ,wBAAR,QAAQ,QAMnB;AAaD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,gCAAS,CAAA;IACT,8CAAuB,CAAA;AACzB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B;AAuBD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,kCAAW,CAAA;IACX,wCAAiB,CAAA;AACnB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -12,5 +12,6 @@ tslib_1.__exportStar(require("./SlopeGradingScale"), exports);
|
|
|
12
12
|
tslib_1.__exportStar(require("./SkiArea"), exports);
|
|
13
13
|
tslib_1.__exportStar(require("./SnowCoverHistory"), exports);
|
|
14
14
|
tslib_1.__exportStar(require("./Source"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./Spot"), exports);
|
|
15
16
|
tslib_1.__exportStar(require("./Status"), exports);
|
|
16
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,wDAA6B;AAC7B,iDAAsB;AACtB,8DAAmC;AACnC,kDAAuB;AACvB,gDAAqB;AACrB,oEAAyC;AACzC,8DAAmC;AACnC,oDAAyB;AACzB,6DAAkC;AAClC,mDAAwB;AACxB,mDAAwB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,wDAA6B;AAC7B,iDAAsB;AACtB,8DAAmC;AACnC,kDAAuB;AACvB,gDAAqB;AACrB,oEAAyC;AACzC,8DAAmC;AACnC,oDAAyB;AACzB,6DAAkC;AAClC,mDAAwB;AACxB,iDAAsB;AACtB,mDAAwB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openskidata-format",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Data format for OpenSkiMap.org",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/russellporter/openskidata-format#readme",
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/geojson": "^7946.0.
|
|
30
|
-
"@types/jest": "^
|
|
31
|
-
"jest": "^
|
|
32
|
-
"ts-jest": "^29.
|
|
33
|
-
"typescript": "^5"
|
|
29
|
+
"@types/geojson": "^7946.0.16",
|
|
30
|
+
"@types/jest": "^30.0.0",
|
|
31
|
+
"jest": "^30.2.0",
|
|
32
|
+
"ts-jest": "^29.4.6",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@turf/boolean-point-in-polygon": "^7.2
|
|
37
|
-
"@turf/center": "^7.2
|
|
38
|
-
"@turf/distance": "^7.2
|
|
39
|
-
"@turf/helpers": "^7.2
|
|
40
|
-
"@turf/length": "^7.2
|
|
41
|
-
"@turf/line-chunk": "^7.2
|
|
42
|
-
"tslib": "^2"
|
|
36
|
+
"@turf/boolean-point-in-polygon": "^7.3.2",
|
|
37
|
+
"@turf/center": "^7.3.2",
|
|
38
|
+
"@turf/distance": "^7.3.2",
|
|
39
|
+
"@turf/helpers": "^7.3.2",
|
|
40
|
+
"@turf/length": "^7.3.2",
|
|
41
|
+
"@turf/line-chunk": "^7.3.2",
|
|
42
|
+
"tslib": "^2.8.1"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -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', () => {
|
|
@@ -174,6 +273,8 @@ describe('ElevationProfile', () => {
|
|
|
174
273
|
lit: null,
|
|
175
274
|
gladed: null,
|
|
176
275
|
patrolled: null,
|
|
276
|
+
snowmaking: null,
|
|
277
|
+
snowfarming: null,
|
|
177
278
|
grooming: null,
|
|
178
279
|
websites: [],
|
|
179
280
|
wikidataID: null,
|
|
@@ -211,6 +312,8 @@ describe('ElevationProfile', () => {
|
|
|
211
312
|
lit: null,
|
|
212
313
|
gladed: null,
|
|
213
314
|
patrolled: null,
|
|
315
|
+
snowmaking: null,
|
|
316
|
+
snowfarming: null,
|
|
214
317
|
grooming: null,
|
|
215
318
|
websites: [],
|
|
216
319
|
wikidataID: null,
|
|
@@ -235,6 +338,7 @@ describe('ElevationProfile', () => {
|
|
|
235
338
|
id: '123',
|
|
236
339
|
liftType: LiftType.ChairLift,
|
|
237
340
|
status: Status.Operating,
|
|
341
|
+
access: null,
|
|
238
342
|
name: 'Test Lift',
|
|
239
343
|
skiAreas: [],
|
|
240
344
|
sources: [],
|
|
@@ -248,6 +352,7 @@ describe('ElevationProfile', () => {
|
|
|
248
352
|
detachable: null,
|
|
249
353
|
bubble: null,
|
|
250
354
|
heating: null,
|
|
355
|
+
stations: [],
|
|
251
356
|
websites: [],
|
|
252
357
|
wikidataID: null,
|
|
253
358
|
places: [],
|
|
@@ -274,6 +379,8 @@ describe('ElevationProfile', () => {
|
|
|
274
379
|
lit: null,
|
|
275
380
|
gladed: null,
|
|
276
381
|
patrolled: null,
|
|
382
|
+
snowmaking: null,
|
|
383
|
+
snowfarming: null,
|
|
277
384
|
grooming: 'classic+skating',
|
|
278
385
|
skiAreas: [],
|
|
279
386
|
elevationProfile: {
|
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,18 +87,39 @@ 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
|
|
|
112
|
+
// turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
|
|
113
|
+
// but we need distance-based interpolation. Strip Z from non-original vertices so
|
|
114
|
+
// interpolateElevation can add them correctly.
|
|
115
|
+
// https://github.com/Turfjs/turf/issues/3007
|
|
116
|
+
stripNonOriginalElevations(chunkedGeometries, coordinates)
|
|
117
|
+
|
|
94
118
|
// Mutates the geometries in place to add elevation data
|
|
95
119
|
interpolateElevation(chunkedGeometries)
|
|
96
120
|
|
|
97
|
-
//
|
|
98
|
-
let maxPitchValue
|
|
99
|
-
|
|
100
|
-
// Calculate max pitch over the fixed-length chunks
|
|
121
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
122
|
+
let maxPitchValue = 0
|
|
101
123
|
for (const chunk of chunkedGeometries) {
|
|
102
124
|
const chunkCoords = chunk.coordinates
|
|
103
125
|
|
|
@@ -110,11 +132,8 @@ export function getPitchData(
|
|
|
110
132
|
{ units: 'meters' },
|
|
111
133
|
)
|
|
112
134
|
|
|
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
135
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters)
|
|
117
|
-
if (
|
|
136
|
+
if (chunkPitch > maxPitchValue) {
|
|
118
137
|
maxPitchValue = chunkPitch
|
|
119
138
|
}
|
|
120
139
|
}
|
|
@@ -124,23 +143,12 @@ export function getPitchData(
|
|
|
124
143
|
coordinates[coordinates.length - 1][2] - coordinates[0][2],
|
|
125
144
|
)
|
|
126
145
|
|
|
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
|
-
|
|
139
146
|
return {
|
|
140
147
|
averagePitchInPercent: averagePitch,
|
|
141
148
|
maxPitchInPercent: maxPitchValue,
|
|
142
149
|
inclinedLengthInMeters: totalInclinedLength,
|
|
143
150
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
151
|
+
pitchCalculationResolutionInMeters: actualResolution,
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
|
|
@@ -148,46 +156,21 @@ export function getProfileGeometry(
|
|
|
148
156
|
geometry: GeoJSON.LineString,
|
|
149
157
|
elevationProfile: ElevationProfile,
|
|
150
158
|
): GeoJSON.LineString {
|
|
151
|
-
const
|
|
159
|
+
const profileLine = extractPointsForElevationProfile(
|
|
152
160
|
geometry,
|
|
153
161
|
elevationProfile.resolution,
|
|
154
162
|
)
|
|
155
|
-
|
|
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) {
|
|
163
|
+
if (profileLine.coordinates.length !== elevationProfile.heights.length) {
|
|
175
164
|
throw `Mismatch of points & elevation profile`
|
|
176
165
|
}
|
|
177
166
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
throw 'All points should have an elevation at this point.'
|
|
183
|
-
}
|
|
184
|
-
}
|
|
167
|
+
for (let i = 0; i < profileLine.coordinates.length; i++) {
|
|
168
|
+
const point = profileLine.coordinates[i]
|
|
169
|
+
const height = elevationProfile.heights[i]
|
|
170
|
+
point.push(height)
|
|
185
171
|
}
|
|
186
172
|
|
|
187
|
-
return
|
|
188
|
-
type: 'LineString',
|
|
189
|
-
coordinates: geometries.flatMap((geometry) => geometry.coordinates),
|
|
190
|
-
}
|
|
173
|
+
return profileLine
|
|
191
174
|
}
|
|
192
175
|
|
|
193
176
|
/**
|
|
@@ -197,14 +180,18 @@ export function extractPointsForElevationProfile(
|
|
|
197
180
|
geometry: LineString,
|
|
198
181
|
resolution: number,
|
|
199
182
|
): GeoJSON.LineString {
|
|
200
|
-
|
|
183
|
+
// Important: Z coordinates in the output of lineChunk may be incorrect, ignore them (https://github.com/Turfjs/turf/issues/3007)
|
|
184
|
+
const lineChunks = lineChunk(geometry, resolution, {
|
|
185
|
+
units: 'meters',
|
|
186
|
+
}).features.map((feature) => feature.geometry)
|
|
187
|
+
|
|
201
188
|
const points: GeoJSON.Position[] = []
|
|
202
|
-
for (let subline of
|
|
189
|
+
for (let subline of lineChunks) {
|
|
203
190
|
const point = subline.coordinates[0]
|
|
204
191
|
points.push([point[0], point[1]])
|
|
205
192
|
}
|
|
206
|
-
if (
|
|
207
|
-
const geometry =
|
|
193
|
+
if (lineChunks.length > 0) {
|
|
194
|
+
const geometry = lineChunks[lineChunks.length - 1]
|
|
208
195
|
const coords = geometry.coordinates
|
|
209
196
|
if (coords.length > 1) {
|
|
210
197
|
const point = coords[coords.length - 1]
|
|
@@ -218,15 +205,6 @@ export function extractPointsForElevationProfile(
|
|
|
218
205
|
}
|
|
219
206
|
}
|
|
220
207
|
|
|
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
|
-
|
|
230
208
|
export function getAscentAndDescent(
|
|
231
209
|
profileGeometry: GeoJSON.LineString,
|
|
232
210
|
): AscentDescentData {
|
|
@@ -278,6 +256,27 @@ export function getAscentAndDescent(
|
|
|
278
256
|
}
|
|
279
257
|
}
|
|
280
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Strip Z coordinates from points that were created by lineChunk (not original vertices).
|
|
261
|
+
* Original vertices are identified by matching lon/lat coordinates.
|
|
262
|
+
* Mutates the geometries in place.
|
|
263
|
+
*/
|
|
264
|
+
function stripNonOriginalElevations(
|
|
265
|
+
geometries: GeoJSON.LineString[],
|
|
266
|
+
originalCoordinates: GeoJSON.Position[],
|
|
267
|
+
) {
|
|
268
|
+
const originalSet = new Set(originalCoordinates.map((c) => `${c[0]},${c[1]}`))
|
|
269
|
+
|
|
270
|
+
for (const geometry of geometries) {
|
|
271
|
+
for (const point of geometry.coordinates) {
|
|
272
|
+
const key = `${point[0]},${point[1]}`
|
|
273
|
+
if (!originalSet.has(key) && point.length >= 3) {
|
|
274
|
+
point.length = 2
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
281
280
|
/**
|
|
282
281
|
* Interpolate elevation for points along a list of linestrings that don't already have elevation.
|
|
283
282
|
* Uses geographic distances for proper interpolation.
|
package/src/FeatureType.ts
CHANGED
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/Run.ts
CHANGED
|
@@ -51,6 +51,8 @@ export type RunFeature = GeoJSON.Feature<RunGeometry, RunProperties>
|
|
|
51
51
|
* @property {boolean | null} lit - Whether the run has lighting for night skiing, derived from the OpenStreetMap "piste:lit" or "lit" tag.
|
|
52
52
|
* @property {boolean | null} gladed - Whether the run is through gladed/tree terrain, derived from the OpenStreetMap "piste:gladed" or "gladed" tag.
|
|
53
53
|
* @property {boolean | null} patrolled - Whether the run is patrolled by ski patrol, derived from the OpenStreetMap "piste:patrolled" or "patrolled" tag.
|
|
54
|
+
* @property {boolean | null} snowmaking - Whether the run has its operation secured through snowmaking, derived from the OpenStreetMap "piste:snowmaking" tag.
|
|
55
|
+
* @property {boolean | null} snowfarming - Whether the run has its early season operation secured through snowfarming (storing snow from previous season), derived from the OpenStreetMap "piste:snowfarming" tag.
|
|
54
56
|
* @property {RunGrooming | null} grooming - Grooming status/type of the run, derived from the OpenStreetMap "piste:grooming" tag. If not specified explicitly, for difficulties "expert", "freeride", and "extreme", grooming is assumed to be "backcountry".
|
|
55
57
|
* @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this run belongs to. Derived from the OpenStreetMap site=piste relation or landuse=winter_sports area and proximity to ski area features. Runs with "backcountry" grooming are not associated with ski areas unless they have a "piste:patrolled=yes" or "patrolled=yes" OpenStreetMap tag, or are part of a "site=piste" ski area relation.
|
|
56
58
|
* @property {ElevationProfile | null} elevationProfile - Elevation profile of the run, only available for runs with LineString geometry.
|
|
@@ -74,6 +76,8 @@ export type RunProperties = {
|
|
|
74
76
|
lit: boolean | null
|
|
75
77
|
gladed: boolean | null
|
|
76
78
|
patrolled: boolean | null
|
|
79
|
+
snowmaking: boolean | null
|
|
80
|
+
snowfarming: boolean | null
|
|
77
81
|
grooming: RunGrooming | null
|
|
78
82
|
skiAreas: SkiAreaSummaryFeature[]
|
|
79
83
|
elevationProfile: ElevationProfile | null
|
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 = {
|
|
@@ -20,6 +20,8 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
20
20
|
lit: null,
|
|
21
21
|
gladed: null,
|
|
22
22
|
patrolled: null,
|
|
23
|
+
snowmaking: null,
|
|
24
|
+
snowfarming: null,
|
|
23
25
|
grooming: null,
|
|
24
26
|
skiAreas: [],
|
|
25
27
|
elevationProfile: null,
|
|
@@ -51,6 +53,8 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
51
53
|
lit: null,
|
|
52
54
|
gladed: null,
|
|
53
55
|
patrolled: null,
|
|
56
|
+
snowmaking: null,
|
|
57
|
+
snowfarming: null,
|
|
54
58
|
grooming: null,
|
|
55
59
|
skiAreas: [],
|
|
56
60
|
elevationProfile: null,
|
|
@@ -86,6 +90,8 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
86
90
|
lit: null,
|
|
87
91
|
gladed: null,
|
|
88
92
|
patrolled: null,
|
|
93
|
+
snowmaking: null,
|
|
94
|
+
snowfarming: null,
|
|
89
95
|
grooming: null,
|
|
90
96
|
skiAreas: [],
|
|
91
97
|
elevationProfile: null,
|
|
@@ -119,6 +125,8 @@ describe("getEstimatedRunDifficulty", () => {
|
|
|
119
125
|
lit: null,
|
|
120
126
|
gladed: null,
|
|
121
127
|
patrolled: null,
|
|
128
|
+
snowmaking: null,
|
|
129
|
+
snowfarming: null,
|
|
122
130
|
grooming: null,
|
|
123
131
|
skiAreas: [],
|
|
124
132
|
elevationProfile: null,
|
package/src/Spot.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as GeoJSON from 'geojson'
|
|
2
|
+
import { FeatureType } from './FeatureType'
|
|
3
|
+
import { Place } from './Place'
|
|
4
|
+
import { SkiAreaSummaryFeature } from './SkiArea'
|
|
5
|
+
import { Source } from './Source'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A GeoJSON feature representing a spot (point of interest) in or around a ski area.
|
|
9
|
+
*
|
|
10
|
+
* Spots are used to represent lift stations, road crossings, or certain terrain features.
|
|
11
|
+
*/
|
|
12
|
+
export type SpotFeature = GeoJSON.Feature<SpotGeometry, SpotProperties>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A GeoJSON feature representing a lift station spot.
|
|
16
|
+
*/
|
|
17
|
+
export type LiftStationSpotFeature = GeoJSON.Feature<
|
|
18
|
+
SpotGeometry,
|
|
19
|
+
LiftStationSpotProperties
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Geometry type for spots - always a point.
|
|
24
|
+
*/
|
|
25
|
+
export type SpotGeometry = GeoJSON.Point
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Union of all spot property types.
|
|
29
|
+
*/
|
|
30
|
+
export type SpotProperties =
|
|
31
|
+
| CrossingSpotProperties
|
|
32
|
+
| LiftStationSpotProperties
|
|
33
|
+
| AvalancheTransceiverTrainingSpotProperties
|
|
34
|
+
| AvalancheTransceiverCheckpointSpotProperties
|
|
35
|
+
| HalfpipeSpotProperties
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Base properties shared by all spot types.
|
|
39
|
+
*/
|
|
40
|
+
export type SpotBaseProperties = {
|
|
41
|
+
type: FeatureType.Spot
|
|
42
|
+
id: string
|
|
43
|
+
skiAreas: SkiAreaSummaryFeature[]
|
|
44
|
+
sources: Source[]
|
|
45
|
+
places: Place[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Types of spots that can be found in or around ski areas.
|
|
50
|
+
*/
|
|
51
|
+
export enum SpotType {
|
|
52
|
+
Crossing = 'crossing',
|
|
53
|
+
LiftStation = 'lift_station',
|
|
54
|
+
AvalancheTransceiverTraining = 'avalanche_transceiver_training',
|
|
55
|
+
AvalancheTransceiverCheckpoint = 'avalanche_transceiver_checkpoint',
|
|
56
|
+
Halfpipe = 'halfpipe',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A crossing is a point where a ski run intersects with a road or path,
|
|
61
|
+
* potentially requiring skiers to remove their skis.
|
|
62
|
+
*
|
|
63
|
+
* From OpenStreetMap node with tag `piste:dismount=yes|no|sometimes`.
|
|
64
|
+
*/
|
|
65
|
+
export type CrossingSpotProperties = SpotBaseProperties & {
|
|
66
|
+
spotType: SpotType.Crossing
|
|
67
|
+
dismount: DismountRequirement
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether dismounting from skis is required at a crossing.
|
|
72
|
+
*/
|
|
73
|
+
export enum DismountRequirement {
|
|
74
|
+
Yes = 'yes',
|
|
75
|
+
No = 'no',
|
|
76
|
+
Sometimes = 'sometimes',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A lift station is a boarding or alighting point for a ski lift.
|
|
81
|
+
*
|
|
82
|
+
* From OpenStreetMap node or area with tag `aerialway=station`.
|
|
83
|
+
* Areas are represented as a point (centroid).
|
|
84
|
+
*
|
|
85
|
+
* @property {string | null} name - Name of the station, from OpenStreetMap tag `name`.
|
|
86
|
+
* @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
|
|
87
|
+
* @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
|
|
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.
|
|
90
|
+
*/
|
|
91
|
+
export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
92
|
+
spotType: SpotType.LiftStation
|
|
93
|
+
name: string | null
|
|
94
|
+
position: LiftStationPosition | null
|
|
95
|
+
entry: boolean | null
|
|
96
|
+
exit: boolean | null
|
|
97
|
+
liftId: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Position of a lift station along the lift line.
|
|
102
|
+
*/
|
|
103
|
+
export enum LiftStationPosition {
|
|
104
|
+
Top = 'top',
|
|
105
|
+
Mid = 'mid',
|
|
106
|
+
Bottom = 'bottom',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A designated area where skiers can practice using avalanche transceivers.
|
|
111
|
+
*
|
|
112
|
+
* From OpenStreetMap node or area with tags `amenity=avalanche_transceiver` and `avalanche_transceiver=training`.
|
|
113
|
+
* Areas are represented as a point (centroid).
|
|
114
|
+
*/
|
|
115
|
+
export type AvalancheTransceiverTrainingSpotProperties = SpotBaseProperties & {
|
|
116
|
+
spotType: SpotType.AvalancheTransceiverTraining
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A checkpoint where skiers can verify their avalanche transceiver is functioning.
|
|
121
|
+
*
|
|
122
|
+
* From OpenStreetMap node or area with tags `amenity=avalanche_transceiver` and `avalanche_transceiver=checkpoint`.
|
|
123
|
+
* Areas are represented as a point (centroid).
|
|
124
|
+
*/
|
|
125
|
+
export type AvalancheTransceiverCheckpointSpotProperties =
|
|
126
|
+
SpotBaseProperties & {
|
|
127
|
+
spotType: SpotType.AvalancheTransceiverCheckpoint
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A halfpipe structure for freestyle skiing or snowboarding.
|
|
132
|
+
*
|
|
133
|
+
* From OpenStreetMap node/way/area with tag `man_made=piste:halfpipe`.
|
|
134
|
+
* Areas are represented as a point (centroid).
|
|
135
|
+
*/
|
|
136
|
+
export type HalfpipeSpotProperties = SpotBaseProperties & {
|
|
137
|
+
spotType: SpotType.Halfpipe
|
|
138
|
+
}
|