openskidata-format 14.0.0 → 16.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.d.ts.map +1 -0
- package/dist/ElevationProfile.js +15 -24
- package/dist/ElevationProfile.js.map +1 -1
- package/dist/FeatureType.d.ts +1 -0
- package/dist/FeatureType.d.ts.map +1 -0
- package/dist/FeatureType.js +2 -5
- package/dist/FeatureType.js.map +1 -1
- package/dist/Lift.d.ts +10 -8
- package/dist/Lift.d.ts.map +1 -0
- package/dist/Lift.js +22 -29
- package/dist/Lift.js.map +1 -1
- package/dist/LiftNameFormatter.d.ts +2 -1
- package/dist/LiftNameFormatter.d.ts.map +1 -0
- package/dist/LiftNameFormatter.js +11 -14
- package/dist/LiftNameFormatter.js.map +1 -1
- package/dist/Place.d.ts +1 -0
- package/dist/Place.d.ts.map +1 -0
- package/dist/Place.js +1 -2
- package/dist/Run.d.ts +10 -9
- package/dist/Run.d.ts.map +1 -0
- package/dist/Run.js +23 -30
- package/dist/Run.js.map +1 -1
- package/dist/RunDifficultyConvention.d.ts +2 -1
- package/dist/RunDifficultyConvention.d.ts.map +1 -0
- package/dist/RunDifficultyConvention.js +10 -15
- package/dist/RunDifficultyConvention.js.map +1 -1
- package/dist/SkiArea.d.ts +11 -12
- package/dist/SkiArea.d.ts.map +1 -0
- package/dist/SkiArea.js +2 -5
- package/dist/SkiArea.js.map +1 -1
- package/dist/SlopeGradingScale.d.ts +3 -2
- package/dist/SlopeGradingScale.d.ts.map +1 -0
- package/dist/SlopeGradingScale.js +21 -26
- package/dist/SlopeGradingScale.js.map +1 -1
- package/dist/SnowCoverHistory.d.ts +1 -0
- package/dist/SnowCoverHistory.d.ts.map +1 -0
- package/dist/SnowCoverHistory.js +9 -8
- package/dist/SnowCoverHistory.js.map +1 -1
- package/dist/Source.d.ts +2 -14
- package/dist/Source.d.ts.map +1 -0
- package/dist/Source.js +7 -43
- package/dist/Source.js.map +1 -1
- package/dist/Spot.d.ts +6 -5
- package/dist/Spot.d.ts.map +1 -0
- package/dist/Spot.js +6 -9
- package/dist/Spot.js.map +1 -1
- package/dist/Status.d.ts +1 -0
- package/dist/Status.d.ts.map +1 -0
- package/dist/Status.js +2 -5
- package/dist/Status.js.map +1 -1
- package/dist/ViewportHint.d.ts +1 -0
- package/dist/ViewportHint.d.ts.map +1 -0
- package/dist/ViewportHint.js +4 -10
- package/dist/ViewportHint.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -18
- package/dist/index.js.map +1 -1
- package/dist/util/exhaustiveMatchingGuard.d.ts +1 -0
- package/dist/util/exhaustiveMatchingGuard.d.ts.map +1 -0
- package/dist/util/exhaustiveMatchingGuard.js +1 -5
- package/dist/util/exhaustiveMatchingGuard.js.map +1 -1
- package/package.json +35 -23
- package/dist/SkiPass.d.ts +0 -82
- package/dist/SkiPass.js +0 -3
- package/dist/SkiPass.js.map +0 -1
- package/dist/testUtils.d.ts +0 -2
- package/dist/testUtils.js +0 -7
- package/dist/testUtils.js.map +0 -1
- package/src/ElevationProfile.test.ts +0 -738
- package/src/ElevationProfile.ts +0 -452
- package/src/FeatureType.ts +0 -6
- package/src/Lift.test.ts +0 -195
- package/src/Lift.ts +0 -219
- package/src/LiftNameFormatter.ts +0 -116
- package/src/Place.ts +0 -11
- package/src/Run.ts +0 -242
- package/src/RunDifficultyConvention.test.ts +0 -79
- package/src/RunDifficultyConvention.ts +0 -335
- package/src/SkiArea.ts +0 -128
- package/src/SkiPass.ts +0 -89
- package/src/SlopeGradingScale.test.ts +0 -151
- package/src/SlopeGradingScale.ts +0 -96
- package/src/SnowCoverHistory.test.ts +0 -103
- package/src/SnowCoverHistory.ts +0 -107
- package/src/Source.test.ts +0 -50
- package/src/Source.ts +0 -72
- package/src/Spot.ts +0 -140
- package/src/Status.ts +0 -20
- package/src/ViewportHint.ts +0 -208
- package/src/index.ts +0 -15
- package/src/testUtils.ts +0 -5
- package/src/util/exhaustiveMatchingGuard.ts +0 -3
package/src/Lift.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import distance from '@turf/distance'
|
|
2
|
-
import { ElevationData, getElevationData } from './ElevationProfile'
|
|
3
|
-
import { FeatureType } from './FeatureType'
|
|
4
|
-
import { Place } from './Place'
|
|
5
|
-
import { SkiAreaSummaryFeature } from './SkiArea'
|
|
6
|
-
import { Source } from './Source'
|
|
7
|
-
import { LiftStationPosition, LiftStationSpotFeature } from './Spot'
|
|
8
|
-
import { Status } from './Status'
|
|
9
|
-
import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
|
|
10
|
-
import { ViewportHint } from './ViewportHint'
|
|
11
|
-
|
|
12
|
-
export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>
|
|
13
|
-
|
|
14
|
-
export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Access restriction for a lift feature.
|
|
18
|
-
* - 'private': The lift has restricted access (derived from OSM access=private tag)
|
|
19
|
-
* - null: No access restriction or access information not available
|
|
20
|
-
*/
|
|
21
|
-
export type Access = 'private' | null
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A feature representing a ski lift.
|
|
25
|
-
*
|
|
26
|
-
* Lifts are derived from OpenStreetMap aerialway/railway features that are commonly used for winter sports.
|
|
27
|
-
*
|
|
28
|
-
* Note:
|
|
29
|
-
* - Railways (except funiculars) are included only if they are part of a site=piste relation.
|
|
30
|
-
* - Railway parts are not merged together so a single railway line may be represented as multiple features.
|
|
31
|
-
* - Some lifts included in the dataset may be for other purposes (amusement parks, etc).
|
|
32
|
-
*
|
|
33
|
-
* Properties:
|
|
34
|
-
* @property {FeatureType.Lift} type - The feature type, which is always 'Lift'.
|
|
35
|
-
* @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.
|
|
36
|
-
* @property {LiftType} liftType - Type of lift (e.g. chair_lift, gondola). Derived from OpenStreetMap aerialway/railway tags.
|
|
37
|
-
* @property {Status} status - Operational status of the lift. Derived from OpenStreetMap lifecycle tags.
|
|
38
|
-
* @property {Access} access - Access restriction for the lift. Set to "private" when derived from OpenStreetMap access=private tag, otherwise null.
|
|
39
|
-
* @property {string | null} name - Name of the lift. Derived from the OpenStreetMap name tag.
|
|
40
|
-
* @property {string | null} ref - Reference code/number for the lift. Derived from the OpenStreetMap ref tag.
|
|
41
|
-
* @property {string | null} refFRCAIRN - French CAIRN reference identifier. Derived from the OpenStreetMap ref:FR:CAIRN tag.
|
|
42
|
-
* @property {string | null} description - Description of the lift. Derived from the OpenStreetMap description tag.
|
|
43
|
-
* @property {boolean | null} oneway - Whether the lift allows riding only in one direction. Derived from the OpenStreetMap oneway tag.
|
|
44
|
-
* @property {number | null} occupancy - Number of people each carrier can transport. Derived from the OpenStreetMap aerialway:occupancy tag.
|
|
45
|
-
* @property {number | null} capacity - Transport capacity in persons/hour. Derived from the OpenStreetMap aerialway:capacity tag.
|
|
46
|
-
* @property {number | null} duration - Duration of lift ride in seconds. Derived from the OpenStreetMap aerialway:duration tag.
|
|
47
|
-
* @property {boolean | null} detachable - Whether the lift has detachable grips. Derived from the OpenStreetMap aerialway:detachable tag.
|
|
48
|
-
* @property {boolean | null} bubble - Whether the lift has bubbles/covers to protect from weather. Derived from the OpenStreetMap aerialway:bubble tag.
|
|
49
|
-
* @property {boolean | null} heating - Whether the lift has heated carriers/seats. Derived from the OpenStreetMap aerialway:heating tag.
|
|
50
|
-
* @property {boolean | null} tunnel - Whether the lift passes through a tunnel, derived from the OpenStreetMap "tunnel" tag. True for any tunnel value (e.g. "yes", "avalanche_protector", "building_passage"), null if untagged.
|
|
51
|
-
* @property {LiftStationSpotFeature[]} stations - Lift station spot features associated with this lift.
|
|
52
|
-
* @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this lift is a part of.
|
|
53
|
-
* @property {Source[]} sources - Data sources for the feature.
|
|
54
|
-
* @property {string[]} websites - Websites associated with this lift. Derived from the OpenStreetMap website tag.
|
|
55
|
-
* @property {string | null} wikidataID - Wikidata identifier. Derived from the OpenStreetMap wikidata tag.
|
|
56
|
-
* @property {Place[]} places - Geographic places this lift is within (e.g., city, region, country). Derived from reverse geocoding.
|
|
57
|
-
*/
|
|
58
|
-
export type LiftProperties = {
|
|
59
|
-
type: FeatureType.Lift
|
|
60
|
-
id: string
|
|
61
|
-
liftType: LiftType
|
|
62
|
-
status: Status
|
|
63
|
-
access: Access
|
|
64
|
-
name: string | null
|
|
65
|
-
ref: string | null
|
|
66
|
-
refFRCAIRN: string | null
|
|
67
|
-
description: string | null
|
|
68
|
-
oneway: boolean | null
|
|
69
|
-
occupancy: number | null
|
|
70
|
-
capacity: number | null
|
|
71
|
-
duration: number | null
|
|
72
|
-
detachable: boolean | null
|
|
73
|
-
bubble: boolean | null
|
|
74
|
-
heating: boolean | null
|
|
75
|
-
tunnel: boolean | null
|
|
76
|
-
stations: LiftStationSpotFeature[]
|
|
77
|
-
skiAreas: SkiAreaSummaryFeature[]
|
|
78
|
-
sources: Source[]
|
|
79
|
-
websites: string[]
|
|
80
|
-
wikidataID: string | null
|
|
81
|
-
places: Place[]
|
|
82
|
-
viewportHint: ViewportHint
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export enum LiftType {
|
|
86
|
-
CableCar = 'cable_car',
|
|
87
|
-
Gondola = 'gondola',
|
|
88
|
-
ChairLift = 'chair_lift',
|
|
89
|
-
MixedLift = 'mixed_lift',
|
|
90
|
-
DragLift = 'drag_lift',
|
|
91
|
-
TBar = 't-bar',
|
|
92
|
-
JBar = 'j-bar',
|
|
93
|
-
Platter = 'platter',
|
|
94
|
-
RopeTow = 'rope_tow',
|
|
95
|
-
MagicCarpet = 'magic_carpet',
|
|
96
|
-
Funicular = 'funicular',
|
|
97
|
-
Railway = 'railway',
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export type LiftElevationData = ElevationData & {
|
|
101
|
-
speedInMetersPerSecond: number | null
|
|
102
|
-
verticalSpeedInMetersPerSecond: number | null
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function getLiftElevationData(
|
|
106
|
-
feature: LiftFeature,
|
|
107
|
-
): LiftElevationData | null {
|
|
108
|
-
const geometry = feature.geometry
|
|
109
|
-
if (
|
|
110
|
-
!geometry ||
|
|
111
|
-
geometry.type !== 'LineString' ||
|
|
112
|
-
geometry.coordinates[0].length < 3
|
|
113
|
-
) {
|
|
114
|
-
return null
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const elevationData = getElevationData(geometry)
|
|
118
|
-
const durationInSeconds = feature.properties.duration
|
|
119
|
-
const terminalDistances = getTerminalStationDistances(feature)
|
|
120
|
-
const inclinedLength =
|
|
121
|
-
terminalDistances?.inclinedLengthInMeters ??
|
|
122
|
-
elevationData.inclinedLengthInMeters
|
|
123
|
-
const verticalLength =
|
|
124
|
-
terminalDistances?.verticalInMeters ?? elevationData.verticalInMeters
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
...elevationData,
|
|
128
|
-
speedInMetersPerSecond: durationInSeconds
|
|
129
|
-
? inclinedLength / durationInSeconds
|
|
130
|
-
: null,
|
|
131
|
-
verticalSpeedInMetersPerSecond: durationInSeconds
|
|
132
|
-
? verticalLength / durationInSeconds
|
|
133
|
-
: null,
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function getTerminalStationDistances(
|
|
138
|
-
feature: LiftFeature,
|
|
139
|
-
): { inclinedLengthInMeters: number; verticalInMeters: number } | null {
|
|
140
|
-
const stations = feature.properties.stations
|
|
141
|
-
const topStation = stations.find(
|
|
142
|
-
(s) => s.properties.position === LiftStationPosition.Top,
|
|
143
|
-
)
|
|
144
|
-
const bottomStation = stations.find(
|
|
145
|
-
(s) => s.properties.position === LiftStationPosition.Bottom,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
if (!topStation || !bottomStation) {
|
|
149
|
-
return null
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const topCoords = topStation.geometry.coordinates
|
|
153
|
-
const bottomCoords = bottomStation.geometry.coordinates
|
|
154
|
-
|
|
155
|
-
if (topCoords.length < 3 || bottomCoords.length < 3) {
|
|
156
|
-
return null
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const horizontalDistanceInMeters = distance(
|
|
160
|
-
topStation.geometry,
|
|
161
|
-
bottomStation.geometry,
|
|
162
|
-
{ units: 'meters' },
|
|
163
|
-
)
|
|
164
|
-
const verticalInMeters = Math.abs(topCoords[2] - bottomCoords[2])
|
|
165
|
-
const inclinedLengthInMeters = Math.sqrt(
|
|
166
|
-
Math.pow(horizontalDistanceInMeters, 2) + Math.pow(verticalInMeters, 2),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
return { inclinedLengthInMeters, verticalInMeters }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function getFormattedLiftType(liftType: LiftType): string {
|
|
173
|
-
switch (liftType) {
|
|
174
|
-
case LiftType.CableCar:
|
|
175
|
-
return 'Cable Car'
|
|
176
|
-
case LiftType.Gondola:
|
|
177
|
-
return 'Gondola'
|
|
178
|
-
case LiftType.ChairLift:
|
|
179
|
-
return 'Chairlift'
|
|
180
|
-
case LiftType.MixedLift:
|
|
181
|
-
return 'Hybrid'
|
|
182
|
-
case LiftType.DragLift:
|
|
183
|
-
return 'Drag lift'
|
|
184
|
-
case LiftType.TBar:
|
|
185
|
-
return 'T-bar'
|
|
186
|
-
case LiftType.JBar:
|
|
187
|
-
return 'J-bar'
|
|
188
|
-
case LiftType.Platter:
|
|
189
|
-
return 'Platter'
|
|
190
|
-
case LiftType.RopeTow:
|
|
191
|
-
return 'Ropetow'
|
|
192
|
-
case LiftType.MagicCarpet:
|
|
193
|
-
return 'Magic Carpet'
|
|
194
|
-
case LiftType.Funicular:
|
|
195
|
-
return 'Funicular'
|
|
196
|
-
case LiftType.Railway:
|
|
197
|
-
return 'Railway'
|
|
198
|
-
default:
|
|
199
|
-
return exhaustiveMatchingGuard(liftType)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function getLiftColor(status: Status): string {
|
|
204
|
-
const brightRedColor = 'hsl(0, 82%, 42%)'
|
|
205
|
-
const dimRedColor = 'hsl(0, 53%, 42%)'
|
|
206
|
-
|
|
207
|
-
switch (status) {
|
|
208
|
-
case Status.Disused:
|
|
209
|
-
case Status.Abandoned:
|
|
210
|
-
return dimRedColor
|
|
211
|
-
case Status.Proposed:
|
|
212
|
-
case Status.Planned:
|
|
213
|
-
case Status.Construction:
|
|
214
|
-
case Status.Operating:
|
|
215
|
-
return brightRedColor
|
|
216
|
-
default:
|
|
217
|
-
return exhaustiveMatchingGuard(status)
|
|
218
|
-
}
|
|
219
|
-
}
|
package/src/LiftNameFormatter.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { LiftProperties, LiftType, getFormattedLiftType } from './Lift'
|
|
2
|
-
|
|
3
|
-
export function getLiftNameAndType(properties: LiftProperties) {
|
|
4
|
-
const name = properties.name
|
|
5
|
-
|
|
6
|
-
const liftType = getAugmentedLiftType(properties)
|
|
7
|
-
|
|
8
|
-
if (name && liftType) {
|
|
9
|
-
return `${name} (${liftType})`
|
|
10
|
-
} else if (name) {
|
|
11
|
-
return name
|
|
12
|
-
} else {
|
|
13
|
-
return liftType
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getAugmentedLiftType(properties: LiftProperties): string | null {
|
|
18
|
-
const components = []
|
|
19
|
-
|
|
20
|
-
const heated = properties.heating
|
|
21
|
-
const bubble = properties.bubble
|
|
22
|
-
if (heated === true) {
|
|
23
|
-
components.push('Heated')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
bubble === true &&
|
|
28
|
-
properties.liftType !== LiftType.CableCar &&
|
|
29
|
-
properties.liftType !== LiftType.Gondola
|
|
30
|
-
) {
|
|
31
|
-
components.push('Bubble')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!isOccupancyImplicit(properties) && properties.occupancy) {
|
|
35
|
-
components.push(properties.occupancy + 'p')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const liftType =
|
|
39
|
-
implicitOccupancyLiftType(properties) ||
|
|
40
|
-
getFormattedLiftType(properties.liftType)
|
|
41
|
-
if (liftType !== null) {
|
|
42
|
-
components.push(liftType)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return components.join(' ')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function implicitOccupancyLiftType(properties: LiftProperties): string | null {
|
|
49
|
-
const occupancy = properties.occupancy
|
|
50
|
-
if (properties.liftType === LiftType.ChairLift) {
|
|
51
|
-
switch (occupancy) {
|
|
52
|
-
case 1:
|
|
53
|
-
return 'Single'
|
|
54
|
-
case 2:
|
|
55
|
-
return 'Double'
|
|
56
|
-
case 3:
|
|
57
|
-
return 'Triple'
|
|
58
|
-
case 4:
|
|
59
|
-
return 'Quad'
|
|
60
|
-
default:
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
switch (properties.liftType) {
|
|
66
|
-
case LiftType.TBar:
|
|
67
|
-
return 'T-bar'
|
|
68
|
-
case LiftType.JBar:
|
|
69
|
-
return 'J-bar'
|
|
70
|
-
case LiftType.Platter:
|
|
71
|
-
return 'Platter'
|
|
72
|
-
case LiftType.RopeTow:
|
|
73
|
-
return 'Ropetow'
|
|
74
|
-
case LiftType.MagicCarpet:
|
|
75
|
-
return 'Magic Carpet'
|
|
76
|
-
}
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isOccupancyImplicit(properties: LiftProperties) {
|
|
81
|
-
const implicitLiftType = implicitOccupancyLiftType(properties)
|
|
82
|
-
if (implicitLiftType) {
|
|
83
|
-
return true
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return containsAny(properties.name, [
|
|
87
|
-
't-bar',
|
|
88
|
-
'j-bar',
|
|
89
|
-
'platter',
|
|
90
|
-
'rope tow',
|
|
91
|
-
'ropetow',
|
|
92
|
-
'rope-tow',
|
|
93
|
-
'carpet',
|
|
94
|
-
'single',
|
|
95
|
-
'double',
|
|
96
|
-
'triple',
|
|
97
|
-
'quad',
|
|
98
|
-
'1er',
|
|
99
|
-
'2er',
|
|
100
|
-
'3er',
|
|
101
|
-
'4er',
|
|
102
|
-
'6er',
|
|
103
|
-
'8er',
|
|
104
|
-
])
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function containsAny(input: string | null, lowerCaseSearchList: string[]) {
|
|
108
|
-
if (input === null) {
|
|
109
|
-
return false
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const lowerCaseInput = input.toLowerCase()
|
|
113
|
-
return lowerCaseSearchList.some((searchItem) => {
|
|
114
|
-
return lowerCaseInput.includes(searchItem)
|
|
115
|
-
})
|
|
116
|
-
}
|
package/src/Place.ts
DELETED
package/src/Run.ts
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import * as GeoJSON from 'geojson'
|
|
2
|
-
import {
|
|
3
|
-
ElevationData,
|
|
4
|
-
ElevationProfile,
|
|
5
|
-
getElevationData,
|
|
6
|
-
getProfileGeometry,
|
|
7
|
-
} from './ElevationProfile'
|
|
8
|
-
import { FeatureType } from './FeatureType'
|
|
9
|
-
import { Place } from './Place'
|
|
10
|
-
import { RunDifficultyConvention } from './RunDifficultyConvention'
|
|
11
|
-
import { SkiAreaSummaryFeature } from './SkiArea'
|
|
12
|
-
import { SnowCoverHistory } from './SnowCoverHistory'
|
|
13
|
-
import { Source } from './Source'
|
|
14
|
-
import { Status } from './Status'
|
|
15
|
-
import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
|
|
16
|
-
import { ViewportHint } from './ViewportHint'
|
|
17
|
-
|
|
18
|
-
export type RunGeometry = GeoJSON.LineString | GeoJSON.Polygon
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Represents a segment of a ski run/trail.
|
|
22
|
-
*
|
|
23
|
-
* Runs are derived from OpenStreetMap pistes, which are defined as all ways/areas/relations containing a "piste:type" tag.
|
|
24
|
-
* Piste relations support is limited, it is only used to enhance the data of its member ways. There is no way to access the relation itself in the output data.
|
|
25
|
-
*
|
|
26
|
-
* A single run as marked on the mountain may be broken up into several run features, especially if the run has branches, or changes in tags (e.g. difficulty, use).
|
|
27
|
-
*
|
|
28
|
-
* In the future, there might be a "Route" entity introduced to represent a whole run, which would be composed of multiple run features.
|
|
29
|
-
*
|
|
30
|
-
* Note: winter hiking trails and trails of other winter sports disciplines are also included in this dataset.
|
|
31
|
-
*
|
|
32
|
-
* In postprocessing:
|
|
33
|
-
* - overlapping ways are merged into a single run feature with multiple uses.
|
|
34
|
-
* - connected run segments with the same properties are merged into a single run feature.
|
|
35
|
-
*/
|
|
36
|
-
export type RunFeature = GeoJSON.Feature<RunGeometry, RunProperties>
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Represents the properties of a ski run.
|
|
40
|
-
*
|
|
41
|
-
* Properties:
|
|
42
|
-
* @property {FeatureType.Run} type - The feature type, which is always 'Run'.
|
|
43
|
-
* @property {RunUse[]} uses - Use types for the run (e.g. downhill, nordic), derived from OpenStreetMap "piste:type" tags.
|
|
44
|
-
* @property {string} id - Unique identifier for the run. The ID is just a hash of the feature, so will change if the feature changes in any way.
|
|
45
|
-
* @property {string | null} name - Name of the run, if available
|
|
46
|
-
* @property {string | null} ref - Reference code/number for the run. Derived from the OpenStreetMap piste:ref and ref tags.
|
|
47
|
-
* @property {Status} status - Operational status. Note: as only operational runs are included in this dataset, this will always be 'operating'.
|
|
48
|
-
* @property {string | null} description - Description of the run, derived from the OpenStreetMap "piste:description" or "description" tag.
|
|
49
|
-
* @property {RunDifficulty | null} difficulty - Difficulty rating of the run, derived from the OpenStreetMap "piste:difficulty" tag
|
|
50
|
-
* @property {RunDifficultyConvention} difficultyConvention - Regional convention used for representing run difficulty. Derived from location.
|
|
51
|
-
* @property {boolean | null} oneway - Whether the run is one-way only, derived from the OpenStreetMap "piste:oneway" or "oneway" tag, in addition to defaults based on run use.
|
|
52
|
-
* @property {boolean | null} lit - Whether the run has lighting for night skiing, derived from the OpenStreetMap "piste:lit" or "lit" tag.
|
|
53
|
-
* @property {boolean | null} gladed - Whether the run is through gladed/tree terrain, derived from the OpenStreetMap "piste:gladed" or "gladed" tag.
|
|
54
|
-
* @property {boolean | null} patrolled - Whether the run is patrolled by ski patrol, derived from the OpenStreetMap "piste:patrolled" or "patrolled" tag.
|
|
55
|
-
* @property {boolean | null} snowmaking - Whether the run has its operation secured through snowmaking, derived from the OpenStreetMap "piste:snowmaking" tag.
|
|
56
|
-
* @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.
|
|
57
|
-
* @property {boolean | null} tunnel - Whether the run passes through a tunnel, derived from the OpenStreetMap "tunnel" tag. True for any tunnel value (e.g. "yes", "avalanche_protector", "building_passage"), null if untagged.
|
|
58
|
-
* @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".
|
|
59
|
-
* @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.
|
|
60
|
-
* @property {ElevationProfile | null} elevationProfile - Elevation profile of the run, only available for runs with LineString geometry.
|
|
61
|
-
* @property {Source[]} sources - Data sources for this run's information
|
|
62
|
-
* @property {string[]} websites - Websites associated with this run, derived from the OpenStreetMap website tag
|
|
63
|
-
* @property {string | null} wikidataID - Wikidata identifier, if available, derived from the OpenStreetMap wikidata tag
|
|
64
|
-
* @property {Place[]} places - Geographic places this run is within (e.g., city, region, country). Derived from reverse geocoding.
|
|
65
|
-
* @property {SnowCoverHistory} [snowCoverHistory] - Historical snow cover data for this run, if available
|
|
66
|
-
*/
|
|
67
|
-
export type RunProperties = {
|
|
68
|
-
type: FeatureType.Run
|
|
69
|
-
uses: RunUse[]
|
|
70
|
-
id: string
|
|
71
|
-
name: string | null
|
|
72
|
-
ref: string | null
|
|
73
|
-
status: Status
|
|
74
|
-
description: string | null
|
|
75
|
-
difficulty: RunDifficulty | null
|
|
76
|
-
difficultyConvention: RunDifficultyConvention
|
|
77
|
-
oneway: boolean | null
|
|
78
|
-
lit: boolean | null
|
|
79
|
-
gladed: boolean | null
|
|
80
|
-
patrolled: boolean | null
|
|
81
|
-
snowmaking: boolean | null
|
|
82
|
-
snowfarming: boolean | null
|
|
83
|
-
tunnel: boolean | null
|
|
84
|
-
grooming: RunGrooming | null
|
|
85
|
-
skiAreas: SkiAreaSummaryFeature[]
|
|
86
|
-
elevationProfile: ElevationProfile | null
|
|
87
|
-
sources: Source[]
|
|
88
|
-
websites: string[]
|
|
89
|
-
wikidataID: string | null
|
|
90
|
-
places: Place[]
|
|
91
|
-
snowCoverHistory?: SnowCoverHistory
|
|
92
|
-
viewportHint: ViewportHint
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export enum RunUse {
|
|
96
|
-
Downhill = 'downhill',
|
|
97
|
-
Nordic = 'nordic',
|
|
98
|
-
Skitour = 'skitour',
|
|
99
|
-
Sled = 'sled',
|
|
100
|
-
Hike = 'hike',
|
|
101
|
-
Sleigh = 'sleigh',
|
|
102
|
-
IceSkate = 'ice_skate',
|
|
103
|
-
SnowPark = 'snow_park',
|
|
104
|
-
Playground = 'playground',
|
|
105
|
-
Connection = 'connection',
|
|
106
|
-
Fatbike = 'fatbike',
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export enum RunGrooming {
|
|
110
|
-
Classic = 'classic',
|
|
111
|
-
Mogul = 'mogul',
|
|
112
|
-
ClassicAndSkating = 'classic+skating',
|
|
113
|
-
Skating = 'skating',
|
|
114
|
-
Scooter = 'scooter',
|
|
115
|
-
Backcountry = 'backcountry',
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export enum RunDifficulty {
|
|
119
|
-
NOVICE = 'novice',
|
|
120
|
-
EASY = 'easy',
|
|
121
|
-
INTERMEDIATE = 'intermediate',
|
|
122
|
-
ADVANCED = 'advanced',
|
|
123
|
-
EXPERT = 'expert',
|
|
124
|
-
FREERIDE = 'freeride',
|
|
125
|
-
EXTREME = 'extreme',
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export enum RunColorName {
|
|
129
|
-
GREEN = 'green',
|
|
130
|
-
BLUE = 'blue',
|
|
131
|
-
RED = 'red',
|
|
132
|
-
BLACK = 'black',
|
|
133
|
-
ORANGE = 'orange',
|
|
134
|
-
GREY = 'grey',
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export enum RunColorValue {
|
|
138
|
-
GREEN = 'hsl(125, 100%, 33%)',
|
|
139
|
-
BLUE = 'hsl(208, 100%, 33%)',
|
|
140
|
-
RED = 'hsl(359, 94%, 53%)',
|
|
141
|
-
BLACK = 'hsl(0, 0%, 0%)',
|
|
142
|
-
ORANGE = 'hsl(34, 100%, 50%)',
|
|
143
|
-
GREY = 'hsl(0, 0%, 35%)',
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
export function getRunElevationData(feature: RunFeature): ElevationData | null {
|
|
148
|
-
const geometry = feature.geometry
|
|
149
|
-
const profile = feature.properties.elevationProfile
|
|
150
|
-
if (!profile || !geometry || geometry.type !== 'LineString') {
|
|
151
|
-
return null
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Get a geometry that includes elevation data at an even spacing along the run
|
|
155
|
-
const profileGeometry = getProfileGeometry(geometry, profile)
|
|
156
|
-
return getElevationData(profileGeometry)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function getRunColor(
|
|
160
|
-
convention: RunDifficultyConvention,
|
|
161
|
-
difficulty: RunDifficulty | null,
|
|
162
|
-
): RunColorValue {
|
|
163
|
-
return runColorNameToValue(getRunColorName(convention, difficulty))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function runColorNameToValue(color: RunColorName): RunColorValue {
|
|
167
|
-
switch (color) {
|
|
168
|
-
case RunColorName.GREEN:
|
|
169
|
-
return RunColorValue.GREEN
|
|
170
|
-
case RunColorName.BLUE:
|
|
171
|
-
return RunColorValue.BLUE
|
|
172
|
-
case RunColorName.RED:
|
|
173
|
-
return RunColorValue.RED
|
|
174
|
-
case RunColorName.BLACK:
|
|
175
|
-
return RunColorValue.BLACK
|
|
176
|
-
case RunColorName.ORANGE:
|
|
177
|
-
return RunColorValue.ORANGE
|
|
178
|
-
case RunColorName.GREY:
|
|
179
|
-
return RunColorValue.GREY
|
|
180
|
-
default:
|
|
181
|
-
throw 'invalid color'
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function getRunColorName(
|
|
186
|
-
convention: RunDifficultyConvention,
|
|
187
|
-
difficulty: RunDifficulty | null,
|
|
188
|
-
): RunColorName {
|
|
189
|
-
switch (convention) {
|
|
190
|
-
case RunDifficultyConvention.EUROPE:
|
|
191
|
-
switch (difficulty) {
|
|
192
|
-
case RunDifficulty.NOVICE:
|
|
193
|
-
return RunColorName.GREEN
|
|
194
|
-
case RunDifficulty.EASY:
|
|
195
|
-
return RunColorName.BLUE
|
|
196
|
-
case RunDifficulty.INTERMEDIATE:
|
|
197
|
-
return RunColorName.RED
|
|
198
|
-
case RunDifficulty.ADVANCED:
|
|
199
|
-
case RunDifficulty.EXPERT:
|
|
200
|
-
return RunColorName.BLACK
|
|
201
|
-
case RunDifficulty.FREERIDE:
|
|
202
|
-
case RunDifficulty.EXTREME:
|
|
203
|
-
return RunColorName.ORANGE
|
|
204
|
-
default:
|
|
205
|
-
return RunColorName.GREY
|
|
206
|
-
}
|
|
207
|
-
case RunDifficultyConvention.JAPAN:
|
|
208
|
-
switch (difficulty) {
|
|
209
|
-
case RunDifficulty.NOVICE:
|
|
210
|
-
case RunDifficulty.EASY:
|
|
211
|
-
return RunColorName.GREEN
|
|
212
|
-
case RunDifficulty.INTERMEDIATE:
|
|
213
|
-
return RunColorName.RED
|
|
214
|
-
case RunDifficulty.ADVANCED:
|
|
215
|
-
case RunDifficulty.EXPERT:
|
|
216
|
-
return RunColorName.BLACK
|
|
217
|
-
case RunDifficulty.FREERIDE:
|
|
218
|
-
case RunDifficulty.EXTREME:
|
|
219
|
-
return RunColorName.ORANGE
|
|
220
|
-
default:
|
|
221
|
-
return RunColorName.GREY
|
|
222
|
-
}
|
|
223
|
-
case RunDifficultyConvention.NORTH_AMERICA:
|
|
224
|
-
switch (difficulty) {
|
|
225
|
-
case RunDifficulty.NOVICE:
|
|
226
|
-
case RunDifficulty.EASY:
|
|
227
|
-
return RunColorName.GREEN
|
|
228
|
-
case RunDifficulty.INTERMEDIATE:
|
|
229
|
-
return RunColorName.BLUE
|
|
230
|
-
case RunDifficulty.ADVANCED:
|
|
231
|
-
case RunDifficulty.EXPERT:
|
|
232
|
-
return RunColorName.BLACK
|
|
233
|
-
case RunDifficulty.FREERIDE:
|
|
234
|
-
case RunDifficulty.EXTREME:
|
|
235
|
-
return RunColorName.ORANGE
|
|
236
|
-
default:
|
|
237
|
-
return RunColorName.GREY
|
|
238
|
-
}
|
|
239
|
-
default:
|
|
240
|
-
return exhaustiveMatchingGuard(convention)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { getRunDifficultyConvention, RunDifficultyConvention } from "./RunDifficultyConvention";
|
|
2
|
-
|
|
3
|
-
describe("getRunDifficultyConvention", () => {
|
|
4
|
-
it("should return JAPAN for locations in Japan", () => {
|
|
5
|
-
// Point in Japan (Tokyo)
|
|
6
|
-
const tokyoGeometry = {
|
|
7
|
-
type: "Point" as const,
|
|
8
|
-
coordinates: [139.6917, 35.6895]
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
expect(getRunDifficultyConvention(tokyoGeometry)).toBe(RunDifficultyConvention.JAPAN);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should return NORTH_AMERICA for locations in North America", () => {
|
|
15
|
-
// Point in Colorado, USA
|
|
16
|
-
const coloradoGeometry = {
|
|
17
|
-
type: "Point" as const,
|
|
18
|
-
coordinates: [-106.3731, 39.1911]
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
expect(getRunDifficultyConvention(coloradoGeometry)).toBe(RunDifficultyConvention.NORTH_AMERICA);
|
|
22
|
-
|
|
23
|
-
// Point in British Columbia, Canada
|
|
24
|
-
const bcGeometry = {
|
|
25
|
-
type: "Point" as const,
|
|
26
|
-
coordinates: [-123.1207, 49.2827]
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
expect(getRunDifficultyConvention(bcGeometry)).toBe(RunDifficultyConvention.NORTH_AMERICA);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should return EUROPE for locations in Europe", () => {
|
|
33
|
-
// Point in the Alps (Chamonix, France)
|
|
34
|
-
const chamonixGeometry = {
|
|
35
|
-
type: "Point" as const,
|
|
36
|
-
coordinates: [6.8694, 45.9237]
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
expect(getRunDifficultyConvention(chamonixGeometry)).toBe(RunDifficultyConvention.EUROPE);
|
|
40
|
-
|
|
41
|
-
// Point in Austria
|
|
42
|
-
const austriaGeometry = {
|
|
43
|
-
type: "Point" as const,
|
|
44
|
-
coordinates: [13.7558, 47.8095]
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
expect(getRunDifficultyConvention(austriaGeometry)).toBe(RunDifficultyConvention.EUROPE);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should return EUROPE as default for other locations", () => {
|
|
51
|
-
// Point in South America (should default to Europe)
|
|
52
|
-
const southAmericaGeometry = {
|
|
53
|
-
type: "Point" as const,
|
|
54
|
-
coordinates: [-58.3816, -34.6037] // Buenos Aires, Argentina
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
expect(getRunDifficultyConvention(southAmericaGeometry)).toBe(RunDifficultyConvention.EUROPE);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("should work with LineString geometry", () => {
|
|
61
|
-
// LineString crossing through Europe
|
|
62
|
-
const lineGeometry = {
|
|
63
|
-
type: "LineString" as const,
|
|
64
|
-
coordinates: [[6.8, 45.9], [6.9, 46.0]]
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
expect(getRunDifficultyConvention(lineGeometry)).toBe(RunDifficultyConvention.EUROPE);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("should work with Polygon geometry", () => {
|
|
71
|
-
// Polygon in North America
|
|
72
|
-
const polygonGeometry = {
|
|
73
|
-
type: "Polygon" as const,
|
|
74
|
-
coordinates: [[[-106.4, 39.1], [-106.3, 39.1], [-106.3, 39.2], [-106.4, 39.2], [-106.4, 39.1]]]
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
expect(getRunDifficultyConvention(polygonGeometry)).toBe(RunDifficultyConvention.NORTH_AMERICA);
|
|
78
|
-
});
|
|
79
|
-
});
|