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.
Files changed (94) hide show
  1. package/dist/ElevationProfile.d.ts +1 -0
  2. package/dist/ElevationProfile.d.ts.map +1 -0
  3. package/dist/ElevationProfile.js +15 -24
  4. package/dist/ElevationProfile.js.map +1 -1
  5. package/dist/FeatureType.d.ts +1 -0
  6. package/dist/FeatureType.d.ts.map +1 -0
  7. package/dist/FeatureType.js +2 -5
  8. package/dist/FeatureType.js.map +1 -1
  9. package/dist/Lift.d.ts +10 -8
  10. package/dist/Lift.d.ts.map +1 -0
  11. package/dist/Lift.js +22 -29
  12. package/dist/Lift.js.map +1 -1
  13. package/dist/LiftNameFormatter.d.ts +2 -1
  14. package/dist/LiftNameFormatter.d.ts.map +1 -0
  15. package/dist/LiftNameFormatter.js +11 -14
  16. package/dist/LiftNameFormatter.js.map +1 -1
  17. package/dist/Place.d.ts +1 -0
  18. package/dist/Place.d.ts.map +1 -0
  19. package/dist/Place.js +1 -2
  20. package/dist/Run.d.ts +10 -9
  21. package/dist/Run.d.ts.map +1 -0
  22. package/dist/Run.js +23 -30
  23. package/dist/Run.js.map +1 -1
  24. package/dist/RunDifficultyConvention.d.ts +2 -1
  25. package/dist/RunDifficultyConvention.d.ts.map +1 -0
  26. package/dist/RunDifficultyConvention.js +10 -15
  27. package/dist/RunDifficultyConvention.js.map +1 -1
  28. package/dist/SkiArea.d.ts +11 -12
  29. package/dist/SkiArea.d.ts.map +1 -0
  30. package/dist/SkiArea.js +2 -5
  31. package/dist/SkiArea.js.map +1 -1
  32. package/dist/SlopeGradingScale.d.ts +3 -2
  33. package/dist/SlopeGradingScale.d.ts.map +1 -0
  34. package/dist/SlopeGradingScale.js +21 -26
  35. package/dist/SlopeGradingScale.js.map +1 -1
  36. package/dist/SnowCoverHistory.d.ts +1 -0
  37. package/dist/SnowCoverHistory.d.ts.map +1 -0
  38. package/dist/SnowCoverHistory.js +9 -8
  39. package/dist/SnowCoverHistory.js.map +1 -1
  40. package/dist/Source.d.ts +2 -14
  41. package/dist/Source.d.ts.map +1 -0
  42. package/dist/Source.js +7 -43
  43. package/dist/Source.js.map +1 -1
  44. package/dist/Spot.d.ts +6 -5
  45. package/dist/Spot.d.ts.map +1 -0
  46. package/dist/Spot.js +6 -9
  47. package/dist/Spot.js.map +1 -1
  48. package/dist/Status.d.ts +1 -0
  49. package/dist/Status.d.ts.map +1 -0
  50. package/dist/Status.js +2 -5
  51. package/dist/Status.js.map +1 -1
  52. package/dist/ViewportHint.d.ts +1 -0
  53. package/dist/ViewportHint.d.ts.map +1 -0
  54. package/dist/ViewportHint.js +4 -10
  55. package/dist/ViewportHint.js.map +1 -1
  56. package/dist/index.d.ts +15 -15
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +14 -18
  59. package/dist/index.js.map +1 -1
  60. package/dist/util/exhaustiveMatchingGuard.d.ts +1 -0
  61. package/dist/util/exhaustiveMatchingGuard.d.ts.map +1 -0
  62. package/dist/util/exhaustiveMatchingGuard.js +1 -5
  63. package/dist/util/exhaustiveMatchingGuard.js.map +1 -1
  64. package/package.json +35 -23
  65. package/dist/SkiPass.d.ts +0 -82
  66. package/dist/SkiPass.js +0 -3
  67. package/dist/SkiPass.js.map +0 -1
  68. package/dist/testUtils.d.ts +0 -2
  69. package/dist/testUtils.js +0 -7
  70. package/dist/testUtils.js.map +0 -1
  71. package/src/ElevationProfile.test.ts +0 -738
  72. package/src/ElevationProfile.ts +0 -452
  73. package/src/FeatureType.ts +0 -6
  74. package/src/Lift.test.ts +0 -195
  75. package/src/Lift.ts +0 -219
  76. package/src/LiftNameFormatter.ts +0 -116
  77. package/src/Place.ts +0 -11
  78. package/src/Run.ts +0 -242
  79. package/src/RunDifficultyConvention.test.ts +0 -79
  80. package/src/RunDifficultyConvention.ts +0 -335
  81. package/src/SkiArea.ts +0 -128
  82. package/src/SkiPass.ts +0 -89
  83. package/src/SlopeGradingScale.test.ts +0 -151
  84. package/src/SlopeGradingScale.ts +0 -96
  85. package/src/SnowCoverHistory.test.ts +0 -103
  86. package/src/SnowCoverHistory.ts +0 -107
  87. package/src/Source.test.ts +0 -50
  88. package/src/Source.ts +0 -72
  89. package/src/Spot.ts +0 -140
  90. package/src/Status.ts +0 -20
  91. package/src/ViewportHint.ts +0 -208
  92. package/src/index.ts +0 -15
  93. package/src/testUtils.ts +0 -5
  94. package/src/util/exhaustiveMatchingGuard.ts +0 -3
@@ -1,151 +0,0 @@
1
- import { RunDifficulty, RunUse } from "./Run";
2
- import { getEstimatedRunDifficulty, getSlopeGradingScale } from "./SlopeGradingScale";
3
- import { mockViewportHint } from "./testUtils";
4
-
5
- describe("getEstimatedRunDifficulty", () => {
6
- it("should return null for steepness below minimum threshold", () => {
7
- const downhillScale = getSlopeGradingScale({
8
- type: "Feature",
9
- geometry: { type: "LineString", coordinates: [] },
10
- properties: {
11
- type: "Run" as any,
12
- uses: [RunUse.Downhill],
13
- id: "test",
14
- name: null,
15
- ref: null,
16
- status: "operating" as any,
17
- description: null,
18
- difficulty: null,
19
- difficultyConvention: "europe" as any,
20
- oneway: null,
21
- lit: null,
22
- gladed: null,
23
- patrolled: null,
24
- snowmaking: null,
25
- snowfarming: null,
26
- grooming: null,
27
- skiAreas: [],
28
- elevationProfile: null,
29
- sources: [],
30
- websites: [],
31
- wikidataID: null,
32
- tunnel: null,
33
- places: [],
34
- viewportHint: mockViewportHint(),
35
- },
36
- });
37
-
38
- expect(getEstimatedRunDifficulty(0.03, downhillScale)).toBeNull(); // 3% steepness
39
- });
40
-
41
- it("should return correct difficulty for downhill slopes", () => {
42
- const downhillScale = getSlopeGradingScale({
43
- type: "Feature",
44
- geometry: { type: "LineString", coordinates: [] },
45
- properties: {
46
- type: "Run" as any,
47
- uses: [RunUse.Downhill],
48
- id: "test",
49
- name: null,
50
- ref: null,
51
- status: "operating" as any,
52
- description: null,
53
- difficulty: null,
54
- difficultyConvention: "europe" as any,
55
- oneway: null,
56
- lit: null,
57
- gladed: null,
58
- patrolled: null,
59
- snowmaking: null,
60
- snowfarming: null,
61
- grooming: null,
62
- skiAreas: [],
63
- elevationProfile: null,
64
- sources: [],
65
- websites: [],
66
- wikidataID: null,
67
- tunnel: null,
68
- places: [],
69
- viewportHint: mockViewportHint(),
70
- },
71
- });
72
-
73
- expect(getEstimatedRunDifficulty(0.1, downhillScale)).toBe(RunDifficulty.EASY); // 10%
74
- expect(getEstimatedRunDifficulty(0.3, downhillScale)).toBe(RunDifficulty.INTERMEDIATE); // 30%
75
- expect(getEstimatedRunDifficulty(0.6, downhillScale)).toBe(RunDifficulty.ADVANCED); // 60%
76
- expect(getEstimatedRunDifficulty(0.9, downhillScale)).toBe(RunDifficulty.EXPERT); // 90%
77
- expect(getEstimatedRunDifficulty(1.0, downhillScale)).toBe(RunDifficulty.FREERIDE); // 100%
78
- });
79
-
80
- it("should return correct difficulty for nordic slopes", () => {
81
- const nordicScale = getSlopeGradingScale({
82
- type: "Feature",
83
- geometry: { type: "LineString", coordinates: [] },
84
- properties: {
85
- type: "Run" as any,
86
- uses: [RunUse.Nordic],
87
- id: "test",
88
- name: null,
89
- ref: null,
90
- status: "operating" as any,
91
- description: null,
92
- difficulty: null,
93
- difficultyConvention: "europe" as any,
94
- oneway: null,
95
- lit: null,
96
- gladed: null,
97
- patrolled: null,
98
- snowmaking: null,
99
- snowfarming: null,
100
- grooming: null,
101
- skiAreas: [],
102
- elevationProfile: null,
103
- sources: [],
104
- websites: [],
105
- wikidataID: null,
106
- tunnel: null,
107
- places: [],
108
- viewportHint: mockViewportHint(),
109
- },
110
- });
111
-
112
- expect(getEstimatedRunDifficulty(0.05, nordicScale)).toBe(RunDifficulty.EASY); // 5%
113
- expect(getEstimatedRunDifficulty(0.12, nordicScale)).toBe(RunDifficulty.INTERMEDIATE); // 12%
114
- expect(getEstimatedRunDifficulty(0.25, nordicScale)).toBe(RunDifficulty.ADVANCED); // 25%
115
- });
116
-
117
- it("should handle negative steepness (uphill)", () => {
118
- const downhillScale = getSlopeGradingScale({
119
- type: "Feature",
120
- geometry: { type: "LineString", coordinates: [] },
121
- properties: {
122
- type: "Run" as any,
123
- uses: [RunUse.Downhill],
124
- id: "test",
125
- name: null,
126
- ref: null,
127
- status: "operating" as any,
128
- description: null,
129
- difficulty: null,
130
- difficultyConvention: "europe" as any,
131
- oneway: null,
132
- lit: null,
133
- gladed: null,
134
- patrolled: null,
135
- snowmaking: null,
136
- snowfarming: null,
137
- grooming: null,
138
- skiAreas: [],
139
- elevationProfile: null,
140
- sources: [],
141
- websites: [],
142
- wikidataID: null,
143
- tunnel: null,
144
- places: [],
145
- viewportHint: mockViewportHint(),
146
- },
147
- });
148
-
149
- expect(getEstimatedRunDifficulty(-0.1, downhillScale)).toBe(RunDifficulty.EASY); // -10%
150
- });
151
- });
@@ -1,96 +0,0 @@
1
- import { RunDifficulty, RunFeature, RunUse } from "./Run";
2
- import { RunDifficultyConvention } from "./RunDifficultyConvention";
3
-
4
- /**
5
- * Represents a slope grading scale with steepness thresholds for different run types.
6
- * Used to estimate run difficulty based on slope steepness. Each scale defines
7
- * threshold points where the difficulty classification changes based on gradient.
8
- */
9
- export type SlopeGradingScale = {
10
- /** Stops must be ordered ascending by steepness */
11
- stops: { maxSteepness: number; difficulty: RunDifficulty | null }[];
12
- };
13
-
14
- /**
15
- * Gets the appropriate slope grading scale for a run based on its use types.
16
- * Different run uses (downhill, nordic, etc.) have different steepness thresholds
17
- * for difficulty classification due to the nature of the activity and equipment used.
18
- *
19
- * @param feature - The run feature to get the grading scale for
20
- * @returns The appropriate slope grading scale with steepness thresholds
21
- */
22
- export function getSlopeGradingScale(feature: RunFeature): SlopeGradingScale {
23
- // Check for downhill or skitour uses first (they have the same scale)
24
- if (
25
- feature.properties.uses.includes(RunUse.Downhill) ||
26
- feature.properties.uses.includes(RunUse.Skitour)
27
- ) {
28
- return getSlopeGradingScaleForUse(RunUse.Downhill, feature.properties.difficultyConvention);
29
- } else if (feature.properties.uses.includes(RunUse.Nordic)) {
30
- return getSlopeGradingScaleForUse(RunUse.Nordic, feature.properties.difficultyConvention);
31
- } else {
32
- return { stops: [] };
33
- }
34
- }
35
-
36
- /**
37
- * Estimates the run difficulty based on steepness and the provided slope grading scale.
38
- * Uses the absolute value of steepness to handle both uphill and downhill slopes.
39
- * Finds the first threshold that the steepness falls under.
40
- *
41
- * @param steepness - The steepness value (slope gradient as a decimal, e.g., 0.1 = 10%)
42
- * @param scale - The slope grading scale with steepness thresholds
43
- * @returns The estimated difficulty based on steepness, or null if no match or below minimum threshold
44
- */
45
- export function getEstimatedRunDifficulty(
46
- steepness: number,
47
- scale: SlopeGradingScale
48
- ): RunDifficulty | null {
49
- const absoluteSteepness = Math.abs(steepness);
50
- return (
51
- scale.stops.find((stop) => stop.maxSteepness > absoluteSteepness)
52
- ?.difficulty || null
53
- );
54
- }
55
-
56
- /**
57
- * Gets the appropriate slope grading scale for a specific run use and region.
58
- * Different run uses (downhill, nordic, etc.) have different steepness thresholds
59
- * for difficulty classification. This function allows for regional variations
60
- * in the future if different conventions use different slope scales.
61
- *
62
- * @param runUse - The type of run use to get the grading scale for
63
- * @param convention - The regional difficulty convention (currently not used but available for future regional variations)
64
- * @returns The appropriate slope grading scale with steepness thresholds
65
- */
66
- export function getSlopeGradingScaleForUse(
67
- runUse: RunUse,
68
- convention: RunDifficultyConvention
69
- ): SlopeGradingScale {
70
- // Currently convention is not used, but is available for future regional variations
71
- switch (runUse) {
72
- case RunUse.Downhill:
73
- case RunUse.Skitour:
74
- return {
75
- stops: [
76
- { maxSteepness: 0.05, difficulty: null }, // No difficulty below 5%
77
- { maxSteepness: 0.25, difficulty: RunDifficulty.EASY },
78
- { maxSteepness: 0.4, difficulty: RunDifficulty.INTERMEDIATE },
79
- { maxSteepness: 0.8, difficulty: RunDifficulty.ADVANCED }, // Advanced up to 80%
80
- { maxSteepness: 1.0, difficulty: RunDifficulty.EXPERT }, // Expert up to 100%
81
- { maxSteepness: 1.2, difficulty: RunDifficulty.FREERIDE }, // Freeride up to 120%
82
- { maxSteepness: Infinity, difficulty: RunDifficulty.EXTREME }, // Extreme above 120%
83
- ],
84
- };
85
- case RunUse.Nordic:
86
- return {
87
- stops: [
88
- { maxSteepness: 0.1, difficulty: RunDifficulty.EASY },
89
- { maxSteepness: 0.15, difficulty: RunDifficulty.INTERMEDIATE },
90
- { maxSteepness: 0.3, difficulty: RunDifficulty.ADVANCED },
91
- ],
92
- };
93
- default:
94
- return { stops: [] };
95
- }
96
- }
@@ -1,103 +0,0 @@
1
- import { getMonthlySnowCover, SnowCoverHistory, MonthlySnowCoverHistory } from './SnowCoverHistory'
2
-
3
- describe('getMonthlySnowCover', () => {
4
- it('should process daily data into monthly averages', () => {
5
- const input: SnowCoverHistory = [
6
- {
7
- year: 2023,
8
- days: [
9
- [1, 80, 95], // Jan 1
10
- [15, 75, 90], // Jan 15
11
- [32, 60, 85], // Feb 1
12
- [46, 65, 80], // Feb 15
13
- [60, 40, 75], // Mar 1
14
- [74, 35, 70], // Mar 15
15
- ]
16
- }
17
- ]
18
-
19
- const result = getMonthlySnowCover(input)
20
-
21
- expect(result).toHaveLength(1)
22
- expect(result[0].year).toBe(2023)
23
- expect(result[0].months).toHaveLength(3)
24
-
25
- // January: (80*95 + 75*90) / (95 + 90) = (7600 + 6750) / 185 = 77.57
26
- expect(result[0].months[0][0]).toBe(1) // Month
27
- expect(result[0].months[0][1]).toBeCloseTo(77.57, 1) // Snow cover
28
- expect(result[0].months[0][2]).toBe(92.5) // Valid pixels average
29
-
30
- // February: (60*85 + 65*80) / (85 + 80) = (5100 + 5200) / 165 = 62.42
31
- expect(result[0].months[1][0]).toBe(2)
32
- expect(result[0].months[1][1]).toBeCloseTo(62.42, 1)
33
- expect(result[0].months[1][2]).toBe(82.5)
34
-
35
- // March: (40*75 + 35*70) / (75 + 70) = (3000 + 2450) / 145 = 37.59
36
- expect(result[0].months[2][0]).toBe(3)
37
- expect(result[0].months[2][1]).toBeCloseTo(37.59, 1)
38
- expect(result[0].months[2][2]).toBe(72.5)
39
- })
40
-
41
- it('should handle multiple years', () => {
42
- const input: SnowCoverHistory = [
43
- {
44
- year: 2022,
45
- days: [[1, 50, 80]]
46
- },
47
- {
48
- year: 2023,
49
- days: [[1, 60, 90]]
50
- }
51
- ]
52
-
53
- const result = getMonthlySnowCover(input)
54
-
55
- expect(result).toHaveLength(2)
56
- expect(result[0].year).toBe(2022)
57
- expect(result[1].year).toBe(2023)
58
- })
59
-
60
- it('should handle empty data', () => {
61
- const input: SnowCoverHistory = []
62
- const result = getMonthlySnowCover(input)
63
- expect(result).toEqual([])
64
- })
65
-
66
- it('should handle zero valid pixels gracefully', () => {
67
- const input: SnowCoverHistory = [
68
- {
69
- year: 2023,
70
- days: [
71
- [1, 80, 0], // No valid pixels
72
- [15, 75, 100] // Full valid pixels
73
- ]
74
- }
75
- ]
76
-
77
- const result = getMonthlySnowCover(input)
78
-
79
- expect(result[0].months[0][0]).toBe(1)
80
- expect(result[0].months[0][1]).toBe(75) // Only the second day contributes
81
- expect(result[0].months[0][2]).toBe(50) // Average of 0 and 100
82
- })
83
-
84
- it('should handle data across multiple months', () => {
85
- const input: SnowCoverHistory = [
86
- {
87
- year: 2023,
88
- days: [
89
- [1, 80, 90], // January
90
- [100, 40, 70], // April (day 100)
91
- [335, 50, 80] // December (day 335)
92
- ]
93
- }
94
- ]
95
-
96
- const result = getMonthlySnowCover(input)
97
-
98
- expect(result[0].months).toHaveLength(3)
99
- expect(result[0].months[0][0]).toBe(1) // January
100
- expect(result[0].months[1][0]).toBe(4) // April
101
- expect(result[0].months[2][0]).toBe(12) // December
102
- })
103
- })
@@ -1,107 +0,0 @@
1
- /**
2
- * Historical snow cover information for a geometry.
3
- * Data is organized chronologically, with the oldest year first.
4
- *
5
- * @property {number} year - The year for this data
6
- * @property {Array<[number, number, number]>} days - Daily measurements as tuples of [day of year, NDSI snow cover value, % of valid pixels]
7
- */
8
- export type SnowCoverHistory = {
9
- year: number
10
- /**
11
- * Array of daily snow cover measurements ordered chronologically by day of year.
12
- * Each tuple contains:
13
- * - Day of year (1-365/366)
14
- * - NDSI snow cover value (0-100) - measures snow presence, not depth
15
- * - Percentage of valid pixels (0-100) - pixels with valid data for that day
16
- *
17
- * Data source: VIIRS/NPP CGF Snow Cover Daily L3 Global 375m product (VNP10A1F)
18
- * Resolution: 375m (affects measurement accuracy for small geometries)
19
- * Frequency: Weekly under cloud-free conditions, varies in practice
20
- *
21
- * Valid pixel percentage may be less than 100% due to:
22
- * - Cloud cover
23
- * - Different cloud gap filling dates for pixels
24
- * - Incomplete pipeline data
25
- */
26
- days: [number, number, number][]
27
- }[]
28
-
29
- /**
30
- * Monthly aggregated snow cover history.
31
- * Data is organized chronologically, with the oldest year first.
32
- *
33
- * @property {number} year - The year for this data
34
- * @property {Array<[number, number, number]>} months - Monthly aggregations as tuples of [month, average snow cover, average valid pixels]
35
- */
36
- export type MonthlySnowCoverHistory = {
37
- year: number
38
- /**
39
- * Array of monthly snow cover aggregations ordered chronologically by month.
40
- * Each tuple contains:
41
- * - Month (1-12)
42
- * - Average NDSI snow cover value (0-100) - weighted by valid pixels
43
- * - Average percentage of valid pixels (0-100) - across all days in month
44
- */
45
- months: [number, number, number][]
46
- }[]
47
-
48
- /**
49
- * Converts daily snow cover measurements to monthly aggregations.
50
- *
51
- * Snow cover values are weighted by valid pixel percentages when calculating monthly averages.
52
- * Input data is assumed to be sorted chronologically.
53
- *
54
- * @param snowCoverHistory Daily snow cover history to aggregate
55
- * @returns Monthly aggregated snow cover history
56
- */
57
- export function getMonthlySnowCover(
58
- snowCoverHistory: SnowCoverHistory
59
- ): MonthlySnowCoverHistory {
60
- return snowCoverHistory.map(yearData => {
61
- const monthlyData = new Map<number, { values: number[], validPixels: number[], count: number }>()
62
-
63
- // Group daily data by month
64
- yearData.days.forEach(([dayOfYear, snowCover, validPixelPercent]) => {
65
- const date = dayOfYearToDate(yearData.year, dayOfYear)
66
- const month = date.getMonth() + 1 // Convert to 1-12
67
-
68
- if (!monthlyData.has(month)) {
69
- monthlyData.set(month, { values: [], validPixels: [], count: 0 })
70
- }
71
-
72
- const monthData = monthlyData.get(month)!
73
- monthData.values.push(snowCover * validPixelPercent / 100) // Weight by valid pixels
74
- monthData.validPixels.push(validPixelPercent)
75
- monthData.count++
76
- })
77
-
78
- // Calculate monthly averages
79
- const months: [number, number, number][] = []
80
- monthlyData.forEach((data, month) => {
81
- const totalValidPixels = data.validPixels.reduce((sum, val) => sum + val, 0)
82
- const weightedSnowCover = data.values.reduce((sum, val) => sum + val, 0)
83
- const avgValidPixels = totalValidPixels / data.count
84
- const avgSnowCover = avgValidPixels > 0 ? (weightedSnowCover / totalValidPixels) * 100 : 0
85
-
86
- months.push([month, Math.round(avgSnowCover * 100) / 100, Math.round(avgValidPixels * 100) / 100])
87
- })
88
-
89
- return {
90
- year: yearData.year,
91
- months
92
- }
93
- })
94
- }
95
-
96
- /**
97
- * Converts day of year to a Date object.
98
- *
99
- * @param year The year
100
- * @param dayOfYear Day of year (1-365/366)
101
- * @returns Date object for the specified day
102
- */
103
- function dayOfYearToDate(year: number, dayOfYear: number): Date {
104
- const date = new Date(year, 0, 1)
105
- date.setDate(dayOfYear)
106
- return date
107
- }
@@ -1,50 +0,0 @@
1
- import {
2
- getSourceName,
3
- getSourceURL,
4
- getStormSkiingChartCSVURL,
5
- SourceType,
6
- } from './Source'
7
-
8
- describe('getSourceURL', () => {
9
- it('links to an OpenStreetMap element', () => {
10
- expect(
11
- getSourceURL({ type: SourceType.OPENSTREETMAP, id: 'relation/5883752' }),
12
- ).toBe('https://www.openstreetmap.org/relation/5883752')
13
- })
14
-
15
- it('links to a Skimap.org ski area', () => {
16
- expect(getSourceURL({ type: SourceType.SKIMAP_ORG, id: '181' })).toBe(
17
- 'https://www.skimap.org/SkiAreas/view/181',
18
- )
19
- })
20
-
21
- it('links to a cell of the ski pass chart', () => {
22
- expect(
23
- getSourceURL({ type: SourceType.STORM_SKIING, id: '677843907!AI64' }),
24
- ).toBe(
25
- 'https://docs.google.com/spreadsheets/d/1G2-l2DVg7-QwroOi7EqRDrJYJ4ICYLcJbLx-nARJdrA/edit?gid=677843907#gid=677843907&range=AI64',
26
- )
27
- })
28
-
29
- it('rejects a ski pass chart id without a sheet', () => {
30
- expect(() =>
31
- getSourceURL({ type: SourceType.STORM_SKIING, id: 'AI64' }),
32
- ).toThrow('Malformed stormskiing.com source id')
33
- })
34
- })
35
-
36
- describe('getSourceName', () => {
37
- it('names every source type', () => {
38
- for (const type of Object.values(SourceType)) {
39
- expect(getSourceName(type).length).toBeGreaterThan(0)
40
- }
41
- })
42
- })
43
-
44
- describe('getStormSkiingChartCSVURL', () => {
45
- it('builds the CSV export URL for a sheet', () => {
46
- expect(getStormSkiingChartCSVURL('677843907')).toBe(
47
- 'https://docs.google.com/spreadsheets/d/1G2-l2DVg7-QwroOi7EqRDrJYJ4ICYLcJbLx-nARJdrA/export?format=csv&gid=677843907',
48
- )
49
- })
50
- })
package/src/Source.ts DELETED
@@ -1,72 +0,0 @@
1
- import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
2
-
3
- export enum SourceType {
4
- SKIMAP_ORG = 'skimap.org',
5
- OPENSTREETMAP = 'openstreetmap',
6
- STORM_SKIING = 'stormskiing.com',
7
- }
8
-
9
- export type Source = {
10
- type: SourceType
11
- id: string
12
- }
13
-
14
- /**
15
- * The Storm Skiing Journal's ski pass chart, a Google Sheets document.
16
- *
17
- * A `STORM_SKIING` source id references a single cell of that document, in `<gid>!<A1>` form
18
- * (e.g. `677843907!AI64`), so it can be linked to directly.
19
- */
20
- export const STORM_SKIING_CHART_DOCUMENT_ID =
21
- '1G2-l2DVg7-QwroOi7EqRDrJYJ4ICYLcJbLx-nARJdrA'
22
-
23
- /**
24
- * URL of the CSV export of one sheet of the ski pass chart. Used to fetch the chart; the ids of
25
- * the sources derived from it are resolved by `getSourceURL`.
26
- */
27
- export function getStormSkiingChartCSVURL(gid: string): string {
28
- return (
29
- `https://docs.google.com/spreadsheets/d/${STORM_SKIING_CHART_DOCUMENT_ID}` +
30
- `/export?format=csv&gid=${gid}`
31
- )
32
- }
33
-
34
- export function getSourceURL(source: Source): string {
35
- switch (source.type) {
36
- case SourceType.OPENSTREETMAP:
37
- return 'https://www.openstreetmap.org/' + source.id
38
- case SourceType.SKIMAP_ORG:
39
- return 'https://www.skimap.org/SkiAreas/view/' + source.id
40
- case SourceType.STORM_SKIING: {
41
- const separatorIndex = source.id.indexOf('!')
42
- if (separatorIndex === -1) {
43
- throw new Error(
44
- `Malformed ${SourceType.STORM_SKIING} source id "${source.id}". Expected <gid>!<A1 reference>.`,
45
- )
46
- }
47
- const gid = source.id.slice(0, separatorIndex)
48
- const range = source.id.slice(separatorIndex + 1)
49
- // The gid appears twice: as a query parameter to select the sheet, and in the fragment,
50
- // which is the only place Google Sheets accepts a cell range.
51
- return (
52
- `https://docs.google.com/spreadsheets/d/${STORM_SKIING_CHART_DOCUMENT_ID}` +
53
- `/edit?gid=${gid}#gid=${gid}&range=${range}`
54
- )
55
- }
56
- default:
57
- return exhaustiveMatchingGuard(source.type)
58
- }
59
- }
60
-
61
- export function getSourceName(type: SourceType): string {
62
- switch (type) {
63
- case SourceType.OPENSTREETMAP:
64
- return 'OpenStreetMap'
65
- case SourceType.SKIMAP_ORG:
66
- return 'Skimap.org'
67
- case SourceType.STORM_SKIING:
68
- return 'The Storm Skiing Journal'
69
- default:
70
- return exhaustiveMatchingGuard(type)
71
- }
72
- }