openskidata-format 8.0.0 → 10.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 +28 -3
- package/dist/ElevationProfile.js +81 -38
- package/dist/ElevationProfile.js.map +1 -1
- package/dist/Lift.d.ts +11 -1
- package/dist/Lift.js.map +1 -1
- package/dist/SkiArea.d.ts +2 -0
- package/dist/Spot.d.ts +6 -0
- package/dist/Spot.js.map +1 -1
- package/package.json +1 -1
- package/src/ElevationProfile.test.ts +370 -18
- package/src/ElevationProfile.ts +122 -44
- package/src/Lift.ts +12 -1
- package/src/SkiArea.ts +6 -1
- package/src/Spot.ts +10 -0
|
@@ -6,10 +6,12 @@ import { LineString } from 'geojson';
|
|
|
6
6
|
* except for the final height which corresponds to the LineString endpoint and may have a different spacing.
|
|
7
7
|
* Height values can be mapped to geographical coordinates using Turf.js: `turf.lineChunk(geometry, resolution, {units: 'meters'})`.
|
|
8
8
|
* @property {number} resolution - The horizontal sampling distance in meters between consecutive height measurements.
|
|
9
|
+
* @property {number} targetResolution - The horizontal sampling distance in meters that was requested for the elevation profile. The actual resolution may be less to ensure an integer number of segments along the line. This is used for reconstructing the profile geometry from the feature geometry.
|
|
9
10
|
*/
|
|
10
11
|
export type ElevationProfile = {
|
|
11
12
|
heights: number[];
|
|
12
13
|
resolution: number;
|
|
14
|
+
targetResolution: number;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
17
|
* Elevation data that can be computed on demand fom a Run or Lift feature.
|
|
@@ -29,13 +31,36 @@ type PitchData = {
|
|
|
29
31
|
maxPitchInPercent: number | null;
|
|
30
32
|
inclinedLengthInMeters: number;
|
|
31
33
|
overallPitchInPercent: number | null;
|
|
34
|
+
pitchCalculationResolutionInMeters: number;
|
|
32
35
|
};
|
|
33
36
|
export declare function getElevationData(profileGeometry: GeoJSON.LineString): ElevationData;
|
|
34
|
-
export declare function getPitchData(profileGeometry: GeoJSON.LineString,
|
|
37
|
+
export declare function getPitchData(profileGeometry: GeoJSON.LineString, minResolutionInMeters?: number): PitchData;
|
|
35
38
|
export declare function getProfileGeometry(geometry: GeoJSON.LineString, elevationProfile: ElevationProfile): GeoJSON.LineString;
|
|
36
39
|
/**
|
|
37
|
-
* Determines the points to use for an elevation profile.
|
|
40
|
+
* Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
|
|
38
41
|
*/
|
|
39
|
-
export declare function extractPointsForElevationProfile(geometry: LineString,
|
|
42
|
+
export declare function extractPointsForElevationProfile(geometry: LineString, minResolutionInMeters: number): {
|
|
43
|
+
resolutionInMeters: number;
|
|
44
|
+
geometry: GeoJSON.LineString;
|
|
45
|
+
};
|
|
40
46
|
export declare function getAscentAndDescent(profileGeometry: GeoJSON.LineString): AscentDescentData;
|
|
47
|
+
/**
|
|
48
|
+
* Turf's lineChunk has several issues:
|
|
49
|
+
* - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
|
|
50
|
+
* change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
|
|
51
|
+
* - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
|
|
52
|
+
* fix: strip Z coordinates from non-original points
|
|
53
|
+
* - it can produce a last point that is very close but not exactly the same as the original endpoint
|
|
54
|
+
* fix: set the last point to be exactly the original endpoint
|
|
55
|
+
* - it can produce two points very close to the end of line due to floating point precision issues
|
|
56
|
+
* fix: drop the second to last point if it's very close to the end
|
|
57
|
+
*
|
|
58
|
+
* @param geometry
|
|
59
|
+
* @param minResolutionInMeters
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
export declare function lineChunkPatched(geometry: LineString, minResolutionInMeters: number): {
|
|
63
|
+
resolutionInMeters: number;
|
|
64
|
+
geometry: GeoJSON.LineString[];
|
|
65
|
+
};
|
|
41
66
|
export {};
|
package/dist/ElevationProfile.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.getPitchData = getPitchData;
|
|
|
5
5
|
exports.getProfileGeometry = getProfileGeometry;
|
|
6
6
|
exports.extractPointsForElevationProfile = extractPointsForElevationProfile;
|
|
7
7
|
exports.getAscentAndDescent = getAscentAndDescent;
|
|
8
|
+
exports.lineChunkPatched = lineChunkPatched;
|
|
8
9
|
const tslib_1 = require("tslib");
|
|
9
10
|
const distance_1 = tslib_1.__importDefault(require("@turf/distance"));
|
|
10
11
|
const length_1 = tslib_1.__importDefault(require("@turf/length"));
|
|
@@ -16,7 +17,7 @@ function getElevationData(profileGeometry) {
|
|
|
16
17
|
profileGeometry,
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
|
-
function getPitchData(profileGeometry,
|
|
20
|
+
function getPitchData(profileGeometry, minResolutionInMeters = 25) {
|
|
20
21
|
const coordinates = profileGeometry.coordinates;
|
|
21
22
|
if (coordinates[0].length < 3) {
|
|
22
23
|
throw 'Elevation data is required for slope analysis';
|
|
@@ -44,59 +45,54 @@ function getPitchData(profileGeometry, resolutionInMeters = 25) {
|
|
|
44
45
|
totalInclinedLength += Math.sqrt(Math.pow(lengthInMeters, 2) + Math.pow(elevation, 2));
|
|
45
46
|
totalElevationChange += Math.abs(elevation);
|
|
46
47
|
}
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
// If the line is too short relative to the resolution, pitch calculations are unreliable
|
|
49
|
+
// due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
|
|
50
|
+
// which can easily happen due to elevation data precision issues.
|
|
51
|
+
if (totalLength < minResolutionInMeters / 2) {
|
|
52
|
+
return {
|
|
53
|
+
averagePitchInPercent: null,
|
|
54
|
+
maxPitchInPercent: null,
|
|
55
|
+
inclinedLengthInMeters: totalInclinedLength,
|
|
56
|
+
overallPitchInPercent: null,
|
|
57
|
+
pitchCalculationResolutionInMeters: totalLength,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Chunk the line into segments using the calculated resolution for max pitch calculation
|
|
61
|
+
const { geometry: chunkedGeometries, resolutionInMeters: actualResolutionInMeters, } = lineChunkPatched(profileGeometry, minResolutionInMeters);
|
|
56
62
|
// Mutates the geometries in place to add elevation data
|
|
57
63
|
interpolateElevation(chunkedGeometries);
|
|
58
|
-
//
|
|
59
|
-
let maxPitchValue =
|
|
60
|
-
// Calculate max pitch over the fixed-length chunks
|
|
64
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
65
|
+
let maxPitchValue = 0;
|
|
61
66
|
for (const chunk of chunkedGeometries) {
|
|
62
67
|
const chunkCoords = chunk.coordinates;
|
|
63
68
|
const startPoint = chunkCoords[0];
|
|
64
69
|
const endPoint = chunkCoords[chunkCoords.length - 1];
|
|
65
70
|
const elevationChange = endPoint[2] - startPoint[2];
|
|
66
71
|
const chunkLengthInMeters = (0, length_1.default)({ type: 'Feature', geometry: chunk, properties: {} }, { units: 'meters' });
|
|
67
|
-
// Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
|
|
68
|
-
if (chunkLengthInMeters < resolutionInMeters / 2)
|
|
69
|
-
continue;
|
|
70
72
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters);
|
|
71
|
-
if (
|
|
73
|
+
if (chunkPitch > maxPitchValue) {
|
|
72
74
|
maxPitchValue = chunkPitch;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
const averagePitch = totalElevationChange / totalLength;
|
|
76
78
|
const overallElevationChange = Math.abs(coordinates[coordinates.length - 1][2] - coordinates[0][2]);
|
|
77
|
-
// null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
|
|
78
|
-
// is questionable as a 1m change in elevation over
|
|
79
|
-
// 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
|
|
80
|
-
if (maxPitchValue === null) {
|
|
81
|
-
return {
|
|
82
|
-
averagePitchInPercent: null,
|
|
83
|
-
maxPitchInPercent: null,
|
|
84
|
-
inclinedLengthInMeters: totalInclinedLength,
|
|
85
|
-
overallPitchInPercent: null,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
79
|
return {
|
|
89
80
|
averagePitchInPercent: averagePitch,
|
|
90
81
|
maxPitchInPercent: maxPitchValue,
|
|
91
82
|
inclinedLengthInMeters: totalInclinedLength,
|
|
92
83
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
84
|
+
pitchCalculationResolutionInMeters: actualResolutionInMeters,
|
|
93
85
|
};
|
|
94
86
|
}
|
|
95
87
|
function getProfileGeometry(geometry, elevationProfile) {
|
|
96
|
-
const profileLine = extractPointsForElevationProfile(geometry, elevationProfile.
|
|
88
|
+
const { geometry: profileLine, resolutionInMeters: actualResolution } = extractPointsForElevationProfile(geometry, elevationProfile.targetResolution);
|
|
97
89
|
if (profileLine.coordinates.length !== elevationProfile.heights.length) {
|
|
98
90
|
throw `Mismatch of points & elevation profile`;
|
|
99
91
|
}
|
|
92
|
+
if (Math.abs(actualResolution - elevationProfile.resolution) >
|
|
93
|
+
elevationProfile.resolution * 0.001) {
|
|
94
|
+
throw `Resolution mismatch between profile geometry (${actualResolution}) and elevation profile (${elevationProfile.resolution})`;
|
|
95
|
+
}
|
|
100
96
|
for (let i = 0; i < profileLine.coordinates.length; i++) {
|
|
101
97
|
const point = profileLine.coordinates[i];
|
|
102
98
|
const height = elevationProfile.heights[i];
|
|
@@ -105,13 +101,10 @@ function getProfileGeometry(geometry, elevationProfile) {
|
|
|
105
101
|
return profileLine;
|
|
106
102
|
}
|
|
107
103
|
/**
|
|
108
|
-
* Determines the points to use for an elevation profile.
|
|
104
|
+
* Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
|
|
109
105
|
*/
|
|
110
|
-
function extractPointsForElevationProfile(geometry,
|
|
111
|
-
|
|
112
|
-
const lineChunks = (0, line_chunk_1.lineChunk)(geometry, resolution, {
|
|
113
|
-
units: 'meters',
|
|
114
|
-
}).features.map((feature) => feature.geometry);
|
|
106
|
+
function extractPointsForElevationProfile(geometry, minResolutionInMeters) {
|
|
107
|
+
const { geometry: lineChunks, resolutionInMeters } = lineChunkPatched(geometry, minResolutionInMeters);
|
|
115
108
|
const points = [];
|
|
116
109
|
for (let subline of lineChunks) {
|
|
117
110
|
const point = subline.coordinates[0];
|
|
@@ -126,8 +119,11 @@ function extractPointsForElevationProfile(geometry, resolution) {
|
|
|
126
119
|
}
|
|
127
120
|
}
|
|
128
121
|
return {
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
resolutionInMeters,
|
|
123
|
+
geometry: {
|
|
124
|
+
type: 'LineString',
|
|
125
|
+
coordinates: points,
|
|
126
|
+
},
|
|
131
127
|
};
|
|
132
128
|
}
|
|
133
129
|
function getAscentAndDescent(profileGeometry) {
|
|
@@ -168,6 +164,53 @@ function getAscentAndDescent(profileGeometry) {
|
|
|
168
164
|
verticalInMeters: result.maxElevation - result.minElevation,
|
|
169
165
|
};
|
|
170
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Turf's lineChunk has several issues:
|
|
169
|
+
* - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
|
|
170
|
+
* change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
|
|
171
|
+
* - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
|
|
172
|
+
* fix: strip Z coordinates from non-original points
|
|
173
|
+
* - it can produce a last point that is very close but not exactly the same as the original endpoint
|
|
174
|
+
* fix: set the last point to be exactly the original endpoint
|
|
175
|
+
* - it can produce two points very close to the end of line due to floating point precision issues
|
|
176
|
+
* fix: drop the second to last point if it's very close to the end
|
|
177
|
+
*
|
|
178
|
+
* @param geometry
|
|
179
|
+
* @param minResolutionInMeters
|
|
180
|
+
* @returns
|
|
181
|
+
*/
|
|
182
|
+
function lineChunkPatched(geometry, minResolutionInMeters) {
|
|
183
|
+
const resolutionInMeters = horizontalResolutionFitting(geometry, minResolutionInMeters);
|
|
184
|
+
const lineChunks = (0, line_chunk_1.lineChunk)(geometry, resolutionInMeters, {
|
|
185
|
+
units: 'meters',
|
|
186
|
+
}).features.map((feature) => feature.geometry);
|
|
187
|
+
// Check the last segment has the expected length
|
|
188
|
+
const lastSegment = lineChunks[lineChunks.length - 1];
|
|
189
|
+
const lastSegmentLength = (0, length_1.default)({ type: 'Feature', geometry: lastSegment, properties: {} }, { units: 'meters' });
|
|
190
|
+
if (lastSegmentLength < resolutionInMeters * 0.01) {
|
|
191
|
+
// If the last segment is very short, which can happen due to floating point precision issues, drop it to avoid issues with elevation interpolation
|
|
192
|
+
lineChunks.pop();
|
|
193
|
+
}
|
|
194
|
+
// Set the last point from the original coordinates, turf line chunk can sometimes produce a last point that
|
|
195
|
+
// is very close but not exactly the same as the original endpoint, which can cause issues with elevation interpolation
|
|
196
|
+
const lastOriginalPoint = geometry.coordinates[geometry.coordinates.length - 1];
|
|
197
|
+
const lastChunkCoords = lineChunks[lineChunks.length - 1].coordinates;
|
|
198
|
+
lastChunkCoords[lastChunkCoords.length - 1] = lastOriginalPoint;
|
|
199
|
+
// turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
|
|
200
|
+
// but we need distance-based interpolation. Strip Z from non-original vertices so
|
|
201
|
+
// interpolateElevation can add them correctly.
|
|
202
|
+
// https://github.com/Turfjs/turf/issues/3007
|
|
203
|
+
stripNonOriginalElevations(lineChunks, geometry.coordinates);
|
|
204
|
+
return { resolutionInMeters, geometry: lineChunks };
|
|
205
|
+
}
|
|
206
|
+
function horizontalResolutionFitting(geometry, minResolution) {
|
|
207
|
+
const totalLength = (0, length_1.default)({ type: 'Feature', geometry, properties: {} }, { units: 'meters' });
|
|
208
|
+
if (totalLength === 0) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
const numSegments = Math.ceil(totalLength / minResolution);
|
|
212
|
+
return totalLength / numSegments;
|
|
213
|
+
}
|
|
171
214
|
/**
|
|
172
215
|
* Strip Z coordinates from points that were created by lineChunk (not original vertices).
|
|
173
216
|
* Original vertices are identified by matching lon/lat coordinates.
|
|
@@ -223,7 +266,7 @@ function interpolateElevation(geometries) {
|
|
|
223
266
|
// Find elevation reference points
|
|
224
267
|
const referencePoints = allPoints.filter((p) => p.hasElevation);
|
|
225
268
|
if (referencePoints.length <= 1) {
|
|
226
|
-
throw
|
|
269
|
+
throw `At least two points with elevation data are required ${JSON.stringify(allPoints)}`;
|
|
227
270
|
}
|
|
228
271
|
// Process segments between each pair of reference points
|
|
229
272
|
for (let i = 0; i < referencePoints.length - 1; i++) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElevationProfile.js","sourceRoot":"","sources":["../src/ElevationProfile.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ElevationProfile.js","sourceRoot":"","sources":["../src/ElevationProfile.ts"],"names":[],"mappings":"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"}
|
package/dist/Lift.d.ts
CHANGED
|
@@ -3,16 +3,22 @@ import { FeatureType } from './FeatureType';
|
|
|
3
3
|
import { Place } from './Place';
|
|
4
4
|
import { SkiAreaSummaryFeature } from './SkiArea';
|
|
5
5
|
import { Source } from './Source';
|
|
6
|
+
import { LiftStationSpotFeature } from './Spot';
|
|
6
7
|
import { Status } from './Status';
|
|
7
8
|
export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>;
|
|
8
9
|
export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
|
|
10
|
+
/**
|
|
11
|
+
* Access restriction for a lift feature.
|
|
12
|
+
* - 'private': The lift has restricted access (derived from OSM access=private tag)
|
|
13
|
+
* - null: No access restriction or access information not available
|
|
14
|
+
*/
|
|
15
|
+
export type Access = 'private' | null;
|
|
9
16
|
/**
|
|
10
17
|
* A feature representing a ski lift.
|
|
11
18
|
*
|
|
12
19
|
* Lifts are derived from OpenStreetMap aerialway/railway features that are commonly used for winter sports.
|
|
13
20
|
*
|
|
14
21
|
* Note:
|
|
15
|
-
* - Private lifts are not included in this dataset.
|
|
16
22
|
* - Railways (except funiculars) are included only if they are part of a site=piste relation.
|
|
17
23
|
* - Railway parts are not merged together so a single railway line may be represented as multiple features.
|
|
18
24
|
* - Some lifts included in the dataset may be for other purposes (amusement parks, etc).
|
|
@@ -22,6 +28,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
|
|
|
22
28
|
* @property {string} id - Unique identifier for the lift. The ID is just a hash of the feature, so will change if the feature changes in any way.
|
|
23
29
|
* @property {LiftType} liftType - Type of lift (e.g. chair_lift, gondola). Derived from OpenStreetMap aerialway/railway tags.
|
|
24
30
|
* @property {Status} status - Operational status of the lift. Derived from OpenStreetMap lifecycle tags.
|
|
31
|
+
* @property {Access} access - Access restriction for the lift. Set to "private" when derived from OpenStreetMap access=private tag, otherwise null.
|
|
25
32
|
* @property {string | null} name - Name of the lift. Derived from the OpenStreetMap name tag.
|
|
26
33
|
* @property {string | null} ref - Reference code/number for the lift. Derived from the OpenStreetMap ref tag.
|
|
27
34
|
* @property {string | null} refFRCAIRN - French CAIRN reference identifier. Derived from the OpenStreetMap ref:FR:CAIRN tag.
|
|
@@ -33,6 +40,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString;
|
|
|
33
40
|
* @property {boolean | null} detachable - Whether the lift has detachable grips. Derived from the OpenStreetMap aerialway:detachable tag.
|
|
34
41
|
* @property {boolean | null} bubble - Whether the lift has bubbles/covers to protect from weather. Derived from the OpenStreetMap aerialway:bubble tag.
|
|
35
42
|
* @property {boolean | null} heating - Whether the lift has heated carriers/seats. Derived from the OpenStreetMap aerialway:heating tag.
|
|
43
|
+
* @property {LiftStationSpotFeature[]} stations - Lift station spot features associated with this lift.
|
|
36
44
|
* @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this lift is a part of.
|
|
37
45
|
* @property {Source[]} sources - Data sources for the feature.
|
|
38
46
|
* @property {string[]} websites - Websites associated with this lift. Derived from the OpenStreetMap website tag.
|
|
@@ -44,6 +52,7 @@ export type LiftProperties = {
|
|
|
44
52
|
id: string;
|
|
45
53
|
liftType: LiftType;
|
|
46
54
|
status: Status;
|
|
55
|
+
access: Access;
|
|
47
56
|
name: string | null;
|
|
48
57
|
ref: string | null;
|
|
49
58
|
refFRCAIRN: string | null;
|
|
@@ -55,6 +64,7 @@ export type LiftProperties = {
|
|
|
55
64
|
detachable: boolean | null;
|
|
56
65
|
bubble: boolean | null;
|
|
57
66
|
heating: boolean | null;
|
|
67
|
+
stations: LiftStationSpotFeature[];
|
|
58
68
|
skiAreas: SkiAreaSummaryFeature[];
|
|
59
69
|
sources: Source[];
|
|
60
70
|
websites: string[];
|
package/dist/Lift.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lift.js","sourceRoot":"","sources":["../src/Lift.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Lift.js","sourceRoot":"","sources":["../src/Lift.ts"],"names":[],"mappings":"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"}
|
package/dist/SkiArea.d.ts
CHANGED
package/dist/Spot.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ import { Source } from './Source';
|
|
|
9
9
|
* Spots are used to represent lift stations, road crossings, or certain terrain features.
|
|
10
10
|
*/
|
|
11
11
|
export type SpotFeature = GeoJSON.Feature<SpotGeometry, SpotProperties>;
|
|
12
|
+
/**
|
|
13
|
+
* A GeoJSON feature representing a lift station spot.
|
|
14
|
+
*/
|
|
15
|
+
export type LiftStationSpotFeature = GeoJSON.Feature<SpotGeometry, LiftStationSpotProperties>;
|
|
12
16
|
/**
|
|
13
17
|
* Geometry type for spots - always a point.
|
|
14
18
|
*/
|
|
@@ -65,6 +69,7 @@ export declare enum DismountRequirement {
|
|
|
65
69
|
* @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
|
|
66
70
|
* @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
|
|
67
71
|
* @property {boolean | null} exit - Whether passengers can alight here. From OpenStreetMap tag `aerialway:access=exit|both|no`.
|
|
72
|
+
* @property {string} liftId - ID of the lift feature associated with this station.
|
|
68
73
|
*/
|
|
69
74
|
export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
70
75
|
spotType: SpotType.LiftStation;
|
|
@@ -72,6 +77,7 @@ export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
|
72
77
|
position: LiftStationPosition | null;
|
|
73
78
|
entry: boolean | null;
|
|
74
79
|
exit: boolean | null;
|
|
80
|
+
liftId: string;
|
|
75
81
|
};
|
|
76
82
|
/**
|
|
77
83
|
* Position of a lift station along the lift line.
|
package/dist/Spot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"Spot.js","sourceRoot":"","sources":["../src/Spot.ts"],"names":[],"mappings":";;;AA+CA;;GAEG;AACH,IAAY,QAMX;AAND,WAAY,QAAQ;IAClB,iCAAqB,CAAA;IACrB,wCAA4B,CAAA;IAC5B,2EAA+D,CAAA;IAC/D,+EAAmE,CAAA;IACnE,iCAAqB,CAAA;AACvB,CAAC,EANW,QAAQ,wBAAR,QAAQ,QAMnB;AAaD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,gCAAS,CAAA;IACT,8CAAuB,CAAA;AACzB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B;AAuBD;;GAEG;AACH,IAAY,mBAIX;AAJD,WAAY,mBAAmB;IAC7B,kCAAW,CAAA;IACX,kCAAW,CAAA;IACX,wCAAiB,CAAA;AACnB,CAAC,EAJW,mBAAmB,mCAAnB,mBAAmB,QAI9B"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
+
import length from '@turf/length'
|
|
1
2
|
import {
|
|
2
3
|
extractPointsForElevationProfile,
|
|
3
4
|
getAscentAndDescent,
|
|
4
5
|
getPitchData,
|
|
6
|
+
lineChunkPatched,
|
|
5
7
|
} from './ElevationProfile'
|
|
6
8
|
import { FeatureType } from './FeatureType'
|
|
7
9
|
import { getLiftElevationData, LiftFeature, LiftType } from './Lift'
|
|
8
|
-
import {
|
|
9
|
-
getRunElevationData,
|
|
10
|
-
RunDifficulty,
|
|
11
|
-
RunFeature,
|
|
12
|
-
RunUse,
|
|
13
|
-
} from './Run'
|
|
10
|
+
import { getRunElevationData, RunDifficulty, RunFeature, RunUse } from './Run'
|
|
14
11
|
import { RunDifficultyConvention } from './RunDifficultyConvention'
|
|
15
12
|
import { Status } from './Status'
|
|
16
13
|
|
|
@@ -74,7 +71,11 @@ describe('ElevationProfile', () => {
|
|
|
74
71
|
|
|
75
72
|
const result = getPitchData(profileGeometry)
|
|
76
73
|
|
|
77
|
-
|
|
74
|
+
// With the new resolution calculation, the actual resolution divides the distance evenly
|
|
75
|
+
// Total length is ~60.44m, with default resolution of 25m: numSegments = ceil(60.44/25) = 3, actualResolution = 60.44/3 ≈ 20.14m
|
|
76
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.14)
|
|
77
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(25)
|
|
78
|
+
expect(result.maxPitchInPercent).toBeCloseTo(0.489)
|
|
78
79
|
expect(result.averagePitchInPercent).toBeCloseTo(0.2482)
|
|
79
80
|
expect(result.inclinedLengthInMeters).toBeCloseTo(63.0597)
|
|
80
81
|
})
|
|
@@ -97,6 +98,101 @@ describe('ElevationProfile', () => {
|
|
|
97
98
|
// The overall change should be more reasonable when calculated over fixed distances
|
|
98
99
|
expect(result.maxPitchInPercent).toBeLessThan(0.5) // Should be much less than the individual segment pitch
|
|
99
100
|
})
|
|
101
|
+
|
|
102
|
+
it('calculates resolution that divides distance evenly', () => {
|
|
103
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
104
|
+
type: 'LineString',
|
|
105
|
+
coordinates: [
|
|
106
|
+
[0, 0, 100],
|
|
107
|
+
[0, 0.001, 110], // ~111m distance
|
|
108
|
+
[0, 0.002, 105], // ~111m distance
|
|
109
|
+
],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const result = getPitchData(profileGeometry, 50)
|
|
113
|
+
|
|
114
|
+
// Resolution should be <= 50
|
|
115
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
|
|
116
|
+
|
|
117
|
+
// With ~222m total length and 50m input resolution:
|
|
118
|
+
// numSegments = ceil(222/50) = 5
|
|
119
|
+
// actualResolution = 222/5 ≈ 44.48m
|
|
120
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(44.48)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('uses total length as resolution when line is shorter than input resolution', () => {
|
|
124
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
125
|
+
type: 'LineString',
|
|
126
|
+
coordinates: [
|
|
127
|
+
[0, 0, 100],
|
|
128
|
+
[0, 0.0001, 110], // ~11m distance
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const result = getPitchData(profileGeometry, 50)
|
|
133
|
+
|
|
134
|
+
// Resolution should equal the total length since numSegments = ceil(11/50) = 1
|
|
135
|
+
// actualResolution = 11/1 ≈ 11.12m
|
|
136
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(11.12)
|
|
137
|
+
expect(result.pitchCalculationResolutionInMeters).toBeLessThanOrEqual(50)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('ensures all chunks are processed without skipping short chunks', () => {
|
|
141
|
+
// Create a line that would previously have a short final chunk
|
|
142
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
143
|
+
type: 'LineString',
|
|
144
|
+
coordinates: [
|
|
145
|
+
[11.177425600412874, 47.31265682344346, 100],
|
|
146
|
+
[11.177224899122194, 47.312533118812354, 120], // ~20m, steep
|
|
147
|
+
[11.176823496540862, 47.31229807921545, 105], // ~40m, gentle descent
|
|
148
|
+
],
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result = getPitchData(profileGeometry, 25)
|
|
152
|
+
|
|
153
|
+
// With evenly divided chunks, no chunks should be skipped
|
|
154
|
+
// The max pitch should be calculated from all segments
|
|
155
|
+
expect(result.maxPitchInPercent).toBeGreaterThan(0)
|
|
156
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(20.14)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('returns null pitch data for very short lines', () => {
|
|
160
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
161
|
+
type: 'LineString',
|
|
162
|
+
coordinates: [
|
|
163
|
+
[0, 0, 100],
|
|
164
|
+
[0, 0.00001, 101], // ~1.11m distance
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = getPitchData(profileGeometry, 25)
|
|
169
|
+
|
|
170
|
+
// Lines shorter than half the resolution should return null pitch data
|
|
171
|
+
// because elevation data resolution makes pitch calculations unreliable
|
|
172
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(1.11)
|
|
173
|
+
expect(result.maxPitchInPercent).toBeNull()
|
|
174
|
+
expect(result.averagePitchInPercent).toBeNull()
|
|
175
|
+
expect(result.overallPitchInPercent).toBeNull()
|
|
176
|
+
expect(result.inclinedLengthInMeters).toBeGreaterThan(0) // This should still be calculated
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('calculates pitch data for lines just above the minimum threshold', () => {
|
|
180
|
+
const profileGeometry: GeoJSON.LineString = {
|
|
181
|
+
type: 'LineString',
|
|
182
|
+
coordinates: [
|
|
183
|
+
[0, 0, 100],
|
|
184
|
+
[0, 0.00015, 105], // ~16.7m distance, just above half of 25m
|
|
185
|
+
],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = getPitchData(profileGeometry, 25)
|
|
189
|
+
|
|
190
|
+
// Lines at or above half the resolution should calculate pitch data
|
|
191
|
+
expect(result.pitchCalculationResolutionInMeters).toBeCloseTo(16.68)
|
|
192
|
+
expect(result.maxPitchInPercent).not.toBeNull()
|
|
193
|
+
expect(result.averagePitchInPercent).not.toBeNull()
|
|
194
|
+
expect(result.overallPitchInPercent).not.toBeNull()
|
|
195
|
+
})
|
|
100
196
|
})
|
|
101
197
|
|
|
102
198
|
describe('extractPointsForElevationProfile', () => {
|
|
@@ -112,13 +208,13 @@ describe('ElevationProfile', () => {
|
|
|
112
208
|
// With 20m resolution, there should be 12 points
|
|
113
209
|
const result = extractPointsForElevationProfile(geometry, 20)
|
|
114
210
|
|
|
115
|
-
expect(result.coordinates.length).toEqual(12)
|
|
116
|
-
expect(result.coordinates[0]).toEqual([
|
|
211
|
+
expect(result.geometry.coordinates.length).toEqual(12)
|
|
212
|
+
expect(result.geometry.coordinates[0]).toEqual([
|
|
117
213
|
11.177452968770694, 47.312650638218656,
|
|
118
214
|
])
|
|
119
|
-
expect(
|
|
120
|
-
|
|
121
|
-
])
|
|
215
|
+
expect(
|
|
216
|
+
result.geometry.coordinates[result.geometry.coordinates.length - 1],
|
|
217
|
+
).toEqual([11.175409464719593, 47.31138883724759])
|
|
122
218
|
})
|
|
123
219
|
|
|
124
220
|
it('handles points that already have elevation', () => {
|
|
@@ -133,9 +229,9 @@ describe('ElevationProfile', () => {
|
|
|
133
229
|
const result = extractPointsForElevationProfile(geometry, 20)
|
|
134
230
|
|
|
135
231
|
// Should still work properly, returning 2D points regardless of input dimension
|
|
136
|
-
expect(result.coordinates.length).toBeGreaterThan(1)
|
|
137
|
-
expect(result.coordinates[0].length).toBe(2) // Should always return 2D points
|
|
138
|
-
expect(result.coordinates[0]).toEqual([
|
|
232
|
+
expect(result.geometry.coordinates.length).toBeGreaterThan(1)
|
|
233
|
+
expect(result.geometry.coordinates[0].length).toBe(2) // Should always return 2D points
|
|
234
|
+
expect(result.geometry.coordinates[0]).toEqual([
|
|
139
235
|
11.177452968770694, 47.312650638218656,
|
|
140
236
|
])
|
|
141
237
|
})
|
|
@@ -232,13 +328,19 @@ describe('ElevationProfile', () => {
|
|
|
232
328
|
type: 'Feature',
|
|
233
329
|
geometry: {
|
|
234
330
|
type: 'MultiLineString',
|
|
235
|
-
coordinates: [
|
|
331
|
+
coordinates: [
|
|
332
|
+
[
|
|
333
|
+
[0, 0, 100],
|
|
334
|
+
[1, 1, 200],
|
|
335
|
+
],
|
|
336
|
+
],
|
|
236
337
|
},
|
|
237
338
|
properties: {
|
|
238
339
|
type: FeatureType.Lift,
|
|
239
340
|
id: '123',
|
|
240
341
|
liftType: LiftType.ChairLift,
|
|
241
342
|
status: Status.Operating,
|
|
343
|
+
access: null,
|
|
242
344
|
name: 'Test Lift',
|
|
243
345
|
skiAreas: [],
|
|
244
346
|
sources: [],
|
|
@@ -252,6 +354,7 @@ describe('ElevationProfile', () => {
|
|
|
252
354
|
detachable: null,
|
|
253
355
|
bubble: null,
|
|
254
356
|
heating: null,
|
|
357
|
+
stations: [],
|
|
255
358
|
websites: [],
|
|
256
359
|
wikidataID: null,
|
|
257
360
|
places: [],
|
|
@@ -294,7 +397,8 @@ describe('ElevationProfile', () => {
|
|
|
294
397
|
1574, 1572, 1571, 1571, 1572, 1572, 1573, 1574, 1573, 1572, 1572,
|
|
295
398
|
1573, 1575, 1576, 1577, 1580, 1583, 1584, 1586,
|
|
296
399
|
],
|
|
297
|
-
resolution:
|
|
400
|
+
resolution: 24.737575778032642,
|
|
401
|
+
targetResolution: 25,
|
|
298
402
|
},
|
|
299
403
|
sources: [],
|
|
300
404
|
websites: [],
|
|
@@ -371,7 +475,255 @@ describe('ElevationProfile', () => {
|
|
|
371
475
|
} as RunFeature
|
|
372
476
|
|
|
373
477
|
const result = getRunElevationData(runFeature)
|
|
374
|
-
expect(result?.maxPitchInPercent).toBeCloseTo(0.12
|
|
478
|
+
expect(result?.maxPitchInPercent).toBeCloseTo(0.12)
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
describe('lineChunkPatched', () => {
|
|
483
|
+
it('chunks a line into evenly spaced segments', () => {
|
|
484
|
+
const geometry: GeoJSON.LineString = {
|
|
485
|
+
type: 'LineString',
|
|
486
|
+
coordinates: [
|
|
487
|
+
[0, 0, 100],
|
|
488
|
+
[0, 0.001, 110], // ~111m distance
|
|
489
|
+
[0, 0.002, 105], // ~111m distance
|
|
490
|
+
],
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const result = lineChunkPatched(geometry, 50)
|
|
494
|
+
|
|
495
|
+
// Total length ~222m, with 50m input resolution:
|
|
496
|
+
// numSegments = ceil(222/50) = 5, actualResolution = 222/5 ≈ 44.48m
|
|
497
|
+
expect(result.resolutionInMeters).toBeCloseTo(44.48)
|
|
498
|
+
expect(result.geometry.length).toBe(5)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('drops very short last segment caused by floating point precision issues', () => {
|
|
502
|
+
// This geometry used to produce a 0-length last segment
|
|
503
|
+
const geometry: GeoJSON.LineString = {
|
|
504
|
+
type: 'LineString',
|
|
505
|
+
coordinates: [
|
|
506
|
+
[11.0796655, 47.4527256, 1692.2],
|
|
507
|
+
[11.079720619903608, 47.45294351099136, 1681.8],
|
|
508
|
+
[11.079741829533312, 47.4531647479081, 1668.6],
|
|
509
|
+
[11.079727503541202, 47.453383892073894, 1656.7],
|
|
510
|
+
[11.07965005139028, 47.453599318647775, 1644.3],
|
|
511
|
+
[11.079572598604779, 47.4538147451695, 1630.9],
|
|
512
|
+
[11.07942834590789, 47.45401183160125, 1618.7],
|
|
513
|
+
[11.079261771477254, 47.45420278959679, 1607.3],
|
|
514
|
+
[11.079114389973567, 47.454399266146474, 1594.7],
|
|
515
|
+
[11.0790705, 47.45461691834462, 1583.1],
|
|
516
|
+
[11.079181898893397, 47.45481861566543, 1576.6],
|
|
517
|
+
[11.079441578857713, 47.45494597233321, 1569.5],
|
|
518
|
+
[11.079759061257176, 47.454999538815876, 1562],
|
|
519
|
+
[11.080078284221905, 47.4550501436389, 1556.1],
|
|
520
|
+
[11.080397507801036, 47.45510074757592, 1552.5],
|
|
521
|
+
[11.080713989344984, 47.45515789593482, 1548.1],
|
|
522
|
+
[11.081008027508169, 47.45525121278351, 1543.2],
|
|
523
|
+
[11.081212874246143, 47.455416412781105, 1540.9],
|
|
524
|
+
[11.081238270576348, 47.455636585144894, 1535.5],
|
|
525
|
+
[11.081208499760809, 47.45585723881769, 1526.8],
|
|
526
|
+
[11.081175991164459, 47.456077847750485, 1510.4],
|
|
527
|
+
[11.081099444790018, 47.456292146692356, 1497.9],
|
|
528
|
+
[11.080996819007606, 47.45650270883295, 1484.3],
|
|
529
|
+
[11.080897169545302, 47.45671391641284, 1472.4],
|
|
530
|
+
[11.080799550285963, 47.45692556428702, 1456.4],
|
|
531
|
+
[11.080701930240755, 47.457137212078344, 1443.9],
|
|
532
|
+
[11.08059047871202, 47.4573452579998, 1434.7],
|
|
533
|
+
[11.080452900000004, 47.45754649999998, 1427.4],
|
|
534
|
+
],
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const result = lineChunkPatched(geometry, 25)
|
|
538
|
+
|
|
539
|
+
// Verify that all segments have correct length
|
|
540
|
+
for (const chunk of result.geometry) {
|
|
541
|
+
const chunkLength = length(
|
|
542
|
+
{ type: 'Feature', geometry: chunk, properties: {} },
|
|
543
|
+
{ units: 'meters' },
|
|
544
|
+
)
|
|
545
|
+
expect(chunkLength).toBeCloseTo(result.resolutionInMeters)
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('preserves the exact last point from the original geometry', () => {
|
|
550
|
+
// This geometry had a split last point that was different from the input last point
|
|
551
|
+
const geometry: GeoJSON.LineString = {
|
|
552
|
+
type: 'LineString',
|
|
553
|
+
coordinates: [
|
|
554
|
+
[11.1215994, 47.466431, 886.6],
|
|
555
|
+
[11.118092699999998, 47.4702057, 768.5],
|
|
556
|
+
],
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const result = lineChunkPatched(geometry, 25)
|
|
560
|
+
|
|
561
|
+
// The last point of the last chunk should be exactly the same as the original last point
|
|
562
|
+
const lastChunk = result.geometry[result.geometry.length - 1]
|
|
563
|
+
const lastPoint = lastChunk.coordinates[lastChunk.coordinates.length - 1]
|
|
564
|
+
const originalLastPoint =
|
|
565
|
+
geometry.coordinates[geometry.coordinates.length - 1]
|
|
566
|
+
|
|
567
|
+
expect(lastPoint[0]).toBe(originalLastPoint[0])
|
|
568
|
+
expect(lastPoint[1]).toBe(originalLastPoint[1])
|
|
569
|
+
expect(lastPoint[2]).toBe(originalLastPoint[2])
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
it('strips elevation from non-original points', () => {
|
|
573
|
+
const geometry: GeoJSON.LineString = {
|
|
574
|
+
type: 'LineString',
|
|
575
|
+
coordinates: [
|
|
576
|
+
[0, 0, 100],
|
|
577
|
+
[0, 0.001, 110], // ~111m distance
|
|
578
|
+
[0, 0.002, 120], // ~111m distance
|
|
579
|
+
],
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const result = lineChunkPatched(geometry, 50)
|
|
583
|
+
|
|
584
|
+
// Check that interpolated points (not from original geometry) don't have elevation
|
|
585
|
+
for (const chunk of result.geometry) {
|
|
586
|
+
for (const point of chunk.coordinates) {
|
|
587
|
+
const key = `${point[0]},${point[1]}`
|
|
588
|
+
const originalKey = geometry.coordinates.some(
|
|
589
|
+
(c) => `${c[0]},${c[1]}` === key,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if (!originalKey) {
|
|
593
|
+
// Non-original points should not have elevation (should be 2D)
|
|
594
|
+
expect(point.length).toBe(2)
|
|
595
|
+
} else {
|
|
596
|
+
// Original points should still have elevation
|
|
597
|
+
expect(point.length).toBe(3)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
it('handles very short lines', () => {
|
|
604
|
+
const geometry: GeoJSON.LineString = {
|
|
605
|
+
type: 'LineString',
|
|
606
|
+
coordinates: [
|
|
607
|
+
[0, 0, 100],
|
|
608
|
+
[0, 0.00001, 101], // ~1.11m distance
|
|
609
|
+
],
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const result = lineChunkPatched(geometry, 25)
|
|
613
|
+
|
|
614
|
+
// Should create a single chunk for very short lines
|
|
615
|
+
expect(result.geometry.length).toBe(1)
|
|
616
|
+
expect(result.resolutionInMeters).toBeCloseTo(1.11)
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
it('handles lines with exact multiples of resolution', () => {
|
|
620
|
+
const geometry: GeoJSON.LineString = {
|
|
621
|
+
type: 'LineString',
|
|
622
|
+
coordinates: [
|
|
623
|
+
[0, 0, 100],
|
|
624
|
+
[0, 0.0009, 110],
|
|
625
|
+
],
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const result = lineChunkPatched(geometry, 50)
|
|
629
|
+
|
|
630
|
+
// The actual distance is calculated and divided evenly
|
|
631
|
+
// Resolution should be <= 50m
|
|
632
|
+
expect(result.resolutionInMeters).toBeLessThanOrEqual(50)
|
|
633
|
+
expect(result.geometry.length).toBeGreaterThanOrEqual(1)
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
it('returns first and last points matching original geometry', () => {
|
|
637
|
+
const geometry: GeoJSON.LineString = {
|
|
638
|
+
type: 'LineString',
|
|
639
|
+
coordinates: [
|
|
640
|
+
[11.177425600412874, 47.31265682344346, 100],
|
|
641
|
+
[11.177224899122194, 47.312533118812354, 110],
|
|
642
|
+
[11.176823496540862, 47.31229807921545, 105],
|
|
643
|
+
],
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const result = lineChunkPatched(geometry, 25)
|
|
647
|
+
|
|
648
|
+
// First point of first chunk should match original first point
|
|
649
|
+
const firstPoint = result.geometry[0].coordinates[0]
|
|
650
|
+
expect(firstPoint[0]).toBe(geometry.coordinates[0][0])
|
|
651
|
+
expect(firstPoint[1]).toBe(geometry.coordinates[0][1])
|
|
652
|
+
expect(firstPoint[2]).toBe(geometry.coordinates[0][2])
|
|
653
|
+
|
|
654
|
+
// Last point of last chunk should match original last point
|
|
655
|
+
const lastChunk = result.geometry[result.geometry.length - 1]
|
|
656
|
+
const lastPoint = lastChunk.coordinates[lastChunk.coordinates.length - 1]
|
|
657
|
+
const originalLastPoint =
|
|
658
|
+
geometry.coordinates[geometry.coordinates.length - 1]
|
|
659
|
+
expect(lastPoint[0]).toBe(originalLastPoint[0])
|
|
660
|
+
expect(lastPoint[1]).toBe(originalLastPoint[1])
|
|
661
|
+
expect(lastPoint[2]).toBe(originalLastPoint[2])
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it('calculates resolution that is less than or equal to requested resolution', () => {
|
|
665
|
+
const geometry: GeoJSON.LineString = {
|
|
666
|
+
type: 'LineString',
|
|
667
|
+
coordinates: [
|
|
668
|
+
[0, 0, 100],
|
|
669
|
+
[0, 0.0015, 110], // ~166.7m distance
|
|
670
|
+
],
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const result = lineChunkPatched(geometry, 50)
|
|
674
|
+
|
|
675
|
+
// Resolution should never exceed the requested minimum resolution
|
|
676
|
+
expect(result.resolutionInMeters).toBeLessThanOrEqual(50)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('handles zero-length segments in original geometry', () => {
|
|
680
|
+
const geometry: GeoJSON.LineString = {
|
|
681
|
+
type: 'LineString',
|
|
682
|
+
coordinates: [
|
|
683
|
+
[0, 0, 100],
|
|
684
|
+
[0, 0, 100], // Duplicate point (0 length segment)
|
|
685
|
+
[0, 0.001, 110],
|
|
686
|
+
],
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const result = lineChunkPatched(geometry, 25)
|
|
690
|
+
|
|
691
|
+
// Should still work and produce valid chunks
|
|
692
|
+
expect(result.geometry.length).toBeGreaterThan(0)
|
|
693
|
+
expect(result.resolutionInMeters).toBeGreaterThan(0)
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
it('preserves all original points that fall on chunk boundaries', () => {
|
|
697
|
+
const geometry: GeoJSON.LineString = {
|
|
698
|
+
type: 'LineString',
|
|
699
|
+
coordinates: [
|
|
700
|
+
[0, 0, 100],
|
|
701
|
+
[0, 0.0005, 105], // ~55.6m - should be close to a chunk boundary with 50m resolution
|
|
702
|
+
[0, 0.001, 110],
|
|
703
|
+
],
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const result = lineChunkPatched(geometry, 50)
|
|
707
|
+
|
|
708
|
+
// Count how many original points are preserved in the chunks
|
|
709
|
+
let preservedOriginalPoints = 0
|
|
710
|
+
for (const chunk of result.geometry) {
|
|
711
|
+
for (const point of chunk.coordinates) {
|
|
712
|
+
if (
|
|
713
|
+
geometry.coordinates.some(
|
|
714
|
+
(c) => c[0] === point[0] && c[1] === point[1],
|
|
715
|
+
)
|
|
716
|
+
) {
|
|
717
|
+
preservedOriginalPoints++
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// All original points should be present somewhere in the chunks
|
|
723
|
+
// (Note: points may appear multiple times if they're at chunk boundaries)
|
|
724
|
+
expect(preservedOriginalPoints).toBeGreaterThanOrEqual(
|
|
725
|
+
geometry.coordinates.length,
|
|
726
|
+
)
|
|
375
727
|
})
|
|
376
728
|
})
|
|
377
729
|
})
|
package/src/ElevationProfile.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { LineString } from 'geojson'
|
|
|
10
10
|
* except for the final height which corresponds to the LineString endpoint and may have a different spacing.
|
|
11
11
|
* Height values can be mapped to geographical coordinates using Turf.js: `turf.lineChunk(geometry, resolution, {units: 'meters'})`.
|
|
12
12
|
* @property {number} resolution - The horizontal sampling distance in meters between consecutive height measurements.
|
|
13
|
+
* @property {number} targetResolution - The horizontal sampling distance in meters that was requested for the elevation profile. The actual resolution may be less to ensure an integer number of segments along the line. This is used for reconstructing the profile geometry from the feature geometry.
|
|
13
14
|
*/
|
|
14
15
|
export type ElevationProfile = {
|
|
15
16
|
heights: number[]
|
|
16
17
|
resolution: number
|
|
18
|
+
targetResolution: number
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -37,6 +39,7 @@ type PitchData = {
|
|
|
37
39
|
maxPitchInPercent: number | null
|
|
38
40
|
inclinedLengthInMeters: number
|
|
39
41
|
overallPitchInPercent: number | null
|
|
42
|
+
pitchCalculationResolutionInMeters: number
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function getElevationData(
|
|
@@ -51,7 +54,7 @@ export function getElevationData(
|
|
|
51
54
|
|
|
52
55
|
export function getPitchData(
|
|
53
56
|
profileGeometry: GeoJSON.LineString,
|
|
54
|
-
|
|
57
|
+
minResolutionInMeters: number = 25, // Default resolution of 25 meters for pitch calculation
|
|
55
58
|
): PitchData {
|
|
56
59
|
const coordinates = profileGeometry.coordinates
|
|
57
60
|
if (coordinates[0].length < 3) {
|
|
@@ -86,24 +89,30 @@ export function getPitchData(
|
|
|
86
89
|
totalElevationChange += Math.abs(elevation)
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// If the line is too short relative to the resolution, pitch calculations are unreliable
|
|
93
|
+
// due to elevation data resolution. A 1m elevation change over 1m distance would be a 45 degree slope,
|
|
94
|
+
// which can easily happen due to elevation data precision issues.
|
|
95
|
+
if (totalLength < minResolutionInMeters / 2) {
|
|
96
|
+
return {
|
|
97
|
+
averagePitchInPercent: null,
|
|
98
|
+
maxPitchInPercent: null,
|
|
99
|
+
inclinedLengthInMeters: totalInclinedLength,
|
|
100
|
+
overallPitchInPercent: null,
|
|
101
|
+
pitchCalculationResolutionInMeters: totalLength,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
93
104
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
// Chunk the line into segments using the calculated resolution for max pitch calculation
|
|
106
|
+
const {
|
|
107
|
+
geometry: chunkedGeometries,
|
|
108
|
+
resolutionInMeters: actualResolutionInMeters,
|
|
109
|
+
} = lineChunkPatched(profileGeometry, minResolutionInMeters)
|
|
99
110
|
|
|
100
111
|
// Mutates the geometries in place to add elevation data
|
|
101
112
|
interpolateElevation(chunkedGeometries)
|
|
102
113
|
|
|
103
|
-
//
|
|
104
|
-
let maxPitchValue
|
|
105
|
-
|
|
106
|
-
// Calculate max pitch over the fixed-length chunks
|
|
114
|
+
// Calculate max pitch over the evenly-divided chunks
|
|
115
|
+
let maxPitchValue = 0
|
|
107
116
|
for (const chunk of chunkedGeometries) {
|
|
108
117
|
const chunkCoords = chunk.coordinates
|
|
109
118
|
|
|
@@ -116,11 +125,8 @@ export function getPitchData(
|
|
|
116
125
|
{ units: 'meters' },
|
|
117
126
|
)
|
|
118
127
|
|
|
119
|
-
// Skip chunks that are too close together to calculate pitch reliably (can happen for the last chunk)
|
|
120
|
-
if (chunkLengthInMeters < resolutionInMeters / 2) continue
|
|
121
|
-
|
|
122
128
|
const chunkPitch = Math.abs(elevationChange / chunkLengthInMeters)
|
|
123
|
-
if (
|
|
129
|
+
if (chunkPitch > maxPitchValue) {
|
|
124
130
|
maxPitchValue = chunkPitch
|
|
125
131
|
}
|
|
126
132
|
}
|
|
@@ -130,23 +136,12 @@ export function getPitchData(
|
|
|
130
136
|
coordinates[coordinates.length - 1][2] - coordinates[0][2],
|
|
131
137
|
)
|
|
132
138
|
|
|
133
|
-
// null maxPitchValue indicates the line is shorter than half the resolution, in which case the accuracy of pitch calculations
|
|
134
|
-
// is questionable as a 1m change in elevation over
|
|
135
|
-
// 1m of distance would be a 45 degree slope, this can happen due to resolution of the elevation data.
|
|
136
|
-
if (maxPitchValue === null) {
|
|
137
|
-
return {
|
|
138
|
-
averagePitchInPercent: null,
|
|
139
|
-
maxPitchInPercent: null,
|
|
140
|
-
inclinedLengthInMeters: totalInclinedLength,
|
|
141
|
-
overallPitchInPercent: null,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
139
|
return {
|
|
146
140
|
averagePitchInPercent: averagePitch,
|
|
147
141
|
maxPitchInPercent: maxPitchValue,
|
|
148
142
|
inclinedLengthInMeters: totalInclinedLength,
|
|
149
143
|
overallPitchInPercent: overallElevationChange / totalLength,
|
|
144
|
+
pitchCalculationResolutionInMeters: actualResolutionInMeters,
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
147
|
|
|
@@ -154,14 +149,22 @@ export function getProfileGeometry(
|
|
|
154
149
|
geometry: GeoJSON.LineString,
|
|
155
150
|
elevationProfile: ElevationProfile,
|
|
156
151
|
): GeoJSON.LineString {
|
|
157
|
-
const profileLine =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
const { geometry: profileLine, resolutionInMeters: actualResolution } =
|
|
153
|
+
extractPointsForElevationProfile(
|
|
154
|
+
geometry,
|
|
155
|
+
elevationProfile.targetResolution,
|
|
156
|
+
)
|
|
161
157
|
if (profileLine.coordinates.length !== elevationProfile.heights.length) {
|
|
162
158
|
throw `Mismatch of points & elevation profile`
|
|
163
159
|
}
|
|
164
160
|
|
|
161
|
+
if (
|
|
162
|
+
Math.abs(actualResolution - elevationProfile.resolution) >
|
|
163
|
+
elevationProfile.resolution * 0.001
|
|
164
|
+
) {
|
|
165
|
+
throw `Resolution mismatch between profile geometry (${actualResolution}) and elevation profile (${elevationProfile.resolution})`
|
|
166
|
+
}
|
|
167
|
+
|
|
165
168
|
for (let i = 0; i < profileLine.coordinates.length; i++) {
|
|
166
169
|
const point = profileLine.coordinates[i]
|
|
167
170
|
const height = elevationProfile.heights[i]
|
|
@@ -172,16 +175,16 @@ export function getProfileGeometry(
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
/**
|
|
175
|
-
* Determines the points to use for an elevation profile.
|
|
178
|
+
* Determines the points to use for an elevation profile, given a minimum horizontal resolution. The resolution is selected to have evenly spaced points along the line.
|
|
176
179
|
*/
|
|
177
180
|
export function extractPointsForElevationProfile(
|
|
178
181
|
geometry: LineString,
|
|
179
|
-
|
|
180
|
-
): GeoJSON.LineString {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
minResolutionInMeters: number,
|
|
183
|
+
): { resolutionInMeters: number; geometry: GeoJSON.LineString } {
|
|
184
|
+
const { geometry: lineChunks, resolutionInMeters } = lineChunkPatched(
|
|
185
|
+
geometry,
|
|
186
|
+
minResolutionInMeters,
|
|
187
|
+
)
|
|
185
188
|
|
|
186
189
|
const points: GeoJSON.Position[] = []
|
|
187
190
|
for (let subline of lineChunks) {
|
|
@@ -198,8 +201,11 @@ export function extractPointsForElevationProfile(
|
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
return {
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
resolutionInMeters,
|
|
205
|
+
geometry: {
|
|
206
|
+
type: 'LineString',
|
|
207
|
+
coordinates: points,
|
|
208
|
+
},
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
|
|
@@ -254,6 +260,78 @@ export function getAscentAndDescent(
|
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Turf's lineChunk has several issues:
|
|
265
|
+
* - it doesn't allow for a custom resolution that divides the line evenly, which is important for consistent pitch calculations
|
|
266
|
+
* change: calculate a resolution that divides the line into an integer number of segments, with a maximum of the provided resolution
|
|
267
|
+
* - it includes elevations from the original geometry on newly created points which are wrong (https://github.com/Turfjs/turf/issues/3007)
|
|
268
|
+
* fix: strip Z coordinates from non-original points
|
|
269
|
+
* - it can produce a last point that is very close but not exactly the same as the original endpoint
|
|
270
|
+
* fix: set the last point to be exactly the original endpoint
|
|
271
|
+
* - it can produce two points very close to the end of line due to floating point precision issues
|
|
272
|
+
* fix: drop the second to last point if it's very close to the end
|
|
273
|
+
*
|
|
274
|
+
* @param geometry
|
|
275
|
+
* @param minResolutionInMeters
|
|
276
|
+
* @returns
|
|
277
|
+
*/
|
|
278
|
+
export function lineChunkPatched(
|
|
279
|
+
geometry: LineString,
|
|
280
|
+
minResolutionInMeters: number,
|
|
281
|
+
): { resolutionInMeters: number; geometry: GeoJSON.LineString[] } {
|
|
282
|
+
const resolutionInMeters = horizontalResolutionFitting(
|
|
283
|
+
geometry,
|
|
284
|
+
minResolutionInMeters,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const lineChunks = lineChunk(geometry, resolutionInMeters, {
|
|
288
|
+
units: 'meters',
|
|
289
|
+
}).features.map((feature) => feature.geometry)
|
|
290
|
+
|
|
291
|
+
// Check the last segment has the expected length
|
|
292
|
+
const lastSegment = lineChunks[lineChunks.length - 1]
|
|
293
|
+
const lastSegmentLength = length(
|
|
294
|
+
{ type: 'Feature', geometry: lastSegment, properties: {} },
|
|
295
|
+
{ units: 'meters' },
|
|
296
|
+
)
|
|
297
|
+
if (lastSegmentLength < resolutionInMeters * 0.01) {
|
|
298
|
+
// If the last segment is very short, which can happen due to floating point precision issues, drop it to avoid issues with elevation interpolation
|
|
299
|
+
lineChunks.pop()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Set the last point from the original coordinates, turf line chunk can sometimes produce a last point that
|
|
303
|
+
// is very close but not exactly the same as the original endpoint, which can cause issues with elevation interpolation
|
|
304
|
+
const lastOriginalPoint =
|
|
305
|
+
geometry.coordinates[geometry.coordinates.length - 1]
|
|
306
|
+
const lastChunkCoords = lineChunks[lineChunks.length - 1].coordinates
|
|
307
|
+
lastChunkCoords[lastChunkCoords.length - 1] = lastOriginalPoint
|
|
308
|
+
|
|
309
|
+
// turf 7.3+ adds Z coordinates to newly created points using segment-end elevation,
|
|
310
|
+
// but we need distance-based interpolation. Strip Z from non-original vertices so
|
|
311
|
+
// interpolateElevation can add them correctly.
|
|
312
|
+
// https://github.com/Turfjs/turf/issues/3007
|
|
313
|
+
stripNonOriginalElevations(lineChunks, geometry.coordinates)
|
|
314
|
+
|
|
315
|
+
return { resolutionInMeters, geometry: lineChunks }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function horizontalResolutionFitting(
|
|
319
|
+
geometry: LineString,
|
|
320
|
+
minResolution: number,
|
|
321
|
+
): number {
|
|
322
|
+
const totalLength = length(
|
|
323
|
+
{ type: 'Feature', geometry, properties: {} },
|
|
324
|
+
{ units: 'meters' },
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if (totalLength === 0) {
|
|
328
|
+
return 0
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const numSegments = Math.ceil(totalLength / minResolution)
|
|
332
|
+
return totalLength / numSegments
|
|
333
|
+
}
|
|
334
|
+
|
|
257
335
|
/**
|
|
258
336
|
* Strip Z coordinates from points that were created by lineChunk (not original vertices).
|
|
259
337
|
* Original vertices are identified by matching lon/lat coordinates.
|
|
@@ -326,7 +404,7 @@ function interpolateElevation(geometries: GeoJSON.LineString[]) {
|
|
|
326
404
|
const referencePoints = allPoints.filter((p) => p.hasElevation)
|
|
327
405
|
|
|
328
406
|
if (referencePoints.length <= 1) {
|
|
329
|
-
throw
|
|
407
|
+
throw `At least two points with elevation data are required ${JSON.stringify(allPoints)}`
|
|
330
408
|
}
|
|
331
409
|
|
|
332
410
|
// Process segments between each pair of reference points
|
package/src/Lift.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FeatureType } from './FeatureType'
|
|
|
3
3
|
import { Place } from './Place'
|
|
4
4
|
import { SkiAreaSummaryFeature } from './SkiArea'
|
|
5
5
|
import { Source } from './Source'
|
|
6
|
+
import { LiftStationSpotFeature } from './Spot'
|
|
6
7
|
import { Status } from './Status'
|
|
7
8
|
import { exhaustiveMatchingGuard } from './util/exhaustiveMatchingGuard'
|
|
8
9
|
|
|
@@ -10,13 +11,19 @@ export type LiftFeature = GeoJSON.Feature<LiftGeometry, LiftProperties>
|
|
|
10
11
|
|
|
11
12
|
export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Access restriction for a lift feature.
|
|
16
|
+
* - 'private': The lift has restricted access (derived from OSM access=private tag)
|
|
17
|
+
* - null: No access restriction or access information not available
|
|
18
|
+
*/
|
|
19
|
+
export type Access = 'private' | null
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* A feature representing a ski lift.
|
|
15
23
|
*
|
|
16
24
|
* Lifts are derived from OpenStreetMap aerialway/railway features that are commonly used for winter sports.
|
|
17
25
|
*
|
|
18
26
|
* Note:
|
|
19
|
-
* - Private lifts are not included in this dataset.
|
|
20
27
|
* - Railways (except funiculars) are included only if they are part of a site=piste relation.
|
|
21
28
|
* - Railway parts are not merged together so a single railway line may be represented as multiple features.
|
|
22
29
|
* - Some lifts included in the dataset may be for other purposes (amusement parks, etc).
|
|
@@ -26,6 +33,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
|
|
|
26
33
|
* @property {string} id - Unique identifier for the lift. The ID is just a hash of the feature, so will change if the feature changes in any way.
|
|
27
34
|
* @property {LiftType} liftType - Type of lift (e.g. chair_lift, gondola). Derived from OpenStreetMap aerialway/railway tags.
|
|
28
35
|
* @property {Status} status - Operational status of the lift. Derived from OpenStreetMap lifecycle tags.
|
|
36
|
+
* @property {Access} access - Access restriction for the lift. Set to "private" when derived from OpenStreetMap access=private tag, otherwise null.
|
|
29
37
|
* @property {string | null} name - Name of the lift. Derived from the OpenStreetMap name tag.
|
|
30
38
|
* @property {string | null} ref - Reference code/number for the lift. Derived from the OpenStreetMap ref tag.
|
|
31
39
|
* @property {string | null} refFRCAIRN - French CAIRN reference identifier. Derived from the OpenStreetMap ref:FR:CAIRN tag.
|
|
@@ -37,6 +45,7 @@ export type LiftGeometry = GeoJSON.LineString | GeoJSON.MultiLineString
|
|
|
37
45
|
* @property {boolean | null} detachable - Whether the lift has detachable grips. Derived from the OpenStreetMap aerialway:detachable tag.
|
|
38
46
|
* @property {boolean | null} bubble - Whether the lift has bubbles/covers to protect from weather. Derived from the OpenStreetMap aerialway:bubble tag.
|
|
39
47
|
* @property {boolean | null} heating - Whether the lift has heated carriers/seats. Derived from the OpenStreetMap aerialway:heating tag.
|
|
48
|
+
* @property {LiftStationSpotFeature[]} stations - Lift station spot features associated with this lift.
|
|
40
49
|
* @property {SkiAreaSummaryFeature[]} skiAreas - Ski areas this lift is a part of.
|
|
41
50
|
* @property {Source[]} sources - Data sources for the feature.
|
|
42
51
|
* @property {string[]} websites - Websites associated with this lift. Derived from the OpenStreetMap website tag.
|
|
@@ -48,6 +57,7 @@ export type LiftProperties = {
|
|
|
48
57
|
id: string
|
|
49
58
|
liftType: LiftType
|
|
50
59
|
status: Status
|
|
60
|
+
access: Access
|
|
51
61
|
name: string | null
|
|
52
62
|
ref: string | null
|
|
53
63
|
refFRCAIRN: string | null
|
|
@@ -59,6 +69,7 @@ export type LiftProperties = {
|
|
|
59
69
|
detachable: boolean | null
|
|
60
70
|
bubble: boolean | null
|
|
61
71
|
heating: boolean | null
|
|
72
|
+
stations: LiftStationSpotFeature[]
|
|
62
73
|
skiAreas: SkiAreaSummaryFeature[]
|
|
63
74
|
sources: Source[]
|
|
64
75
|
websites: string[]
|
package/src/SkiArea.ts
CHANGED
|
@@ -102,7 +102,12 @@ export type RunStatisticsByActivityAndDifficulty = {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
export type RunStatisticsByDifficulty = {
|
|
105
|
-
[key in RunDifficulty | 'other']?: {
|
|
105
|
+
[key in RunDifficulty | 'other']?: {
|
|
106
|
+
count: number
|
|
107
|
+
lengthInKm: number
|
|
108
|
+
snowmakingLengthInKm?: number
|
|
109
|
+
snowfarmingLengthInKm?: number
|
|
110
|
+
}
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
export type LiftStatistics = {
|
package/src/Spot.ts
CHANGED
|
@@ -11,6 +11,14 @@ import { Source } from './Source'
|
|
|
11
11
|
*/
|
|
12
12
|
export type SpotFeature = GeoJSON.Feature<SpotGeometry, SpotProperties>
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A GeoJSON feature representing a lift station spot.
|
|
16
|
+
*/
|
|
17
|
+
export type LiftStationSpotFeature = GeoJSON.Feature<
|
|
18
|
+
SpotGeometry,
|
|
19
|
+
LiftStationSpotProperties
|
|
20
|
+
>
|
|
21
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Geometry type for spots - always a point.
|
|
16
24
|
*/
|
|
@@ -78,6 +86,7 @@ export enum DismountRequirement {
|
|
|
78
86
|
* @property {LiftStationPosition | null} position - From OpenStreetMap tag `aerialway:station=bottom|mid|top`.
|
|
79
87
|
* @property {boolean | null} entry - Whether passengers can board here. From OpenStreetMap tag `aerialway:access=entry|both|no`.
|
|
80
88
|
* @property {boolean | null} exit - Whether passengers can alight here. From OpenStreetMap tag `aerialway:access=exit|both|no`.
|
|
89
|
+
* @property {string} liftId - ID of the lift feature associated with this station.
|
|
81
90
|
*/
|
|
82
91
|
export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
83
92
|
spotType: SpotType.LiftStation
|
|
@@ -85,6 +94,7 @@ export type LiftStationSpotProperties = SpotBaseProperties & {
|
|
|
85
94
|
position: LiftStationPosition | null
|
|
86
95
|
entry: boolean | null
|
|
87
96
|
exit: boolean | null
|
|
97
|
+
liftId: string
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/**
|