File : core/lib/ProfileSmoothingIron.js

1
/*
2
Copyright - 2017 2023 - wwwouaiebe - Contact: https://www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v4.0.0:
21
        - created from v3.6.0
22
Doc reviewed 202208
23
 */
24
25
import theConfig from '../../data/Config.js';
26
27
import { ZERO, ONE, TWO, THREE } from '../../main/Constants.js';
28
29
/* ------------------------------------------------------------------------------------------------------------------------- */
30
/**
31
Smooth a route profile
32
*/
33
/* ------------------------------------------------------------------------------------------------------------------------- */
34
35
class ProfileSmoothingIron {
36
37
    /**
38
    The route for witch the profile will be smoothed
39
    @type {Route}
40
    */
41
42
    #route;
43
44
    /**
45
    The distance between smooth points
46
    @type {Number}
47
    */
48
49
    #smoothDistance;
50
51
    /**
52
    An array with point used to smooth the elevation
53
    @type {Array.<Object>}
54
    */
55
56
    #smoothPoints;
57
58
    /**
59
    The number of points used to compute the smooth elevation. Alias for theConfig.route.elev.smoothPoints.
60
    @type {Number}
61
    */
62
63
    get #smoothPointsNumber ( ) { return theConfig.route.elev.smoothPoints; }
64
65
    /**
66
    Create the array of points with the elevation
67
    */
68
69
    #createSmoothPoints ( ) {
70
        this.#smoothPoints = [];
71
72
        // first point
73
        this.#smoothPoints.push (
74
            {
75
                distance : ZERO,
76
                elev : this.#route.itinerary.itineraryPoints.first.elev,
77
                smoothElev : this.#route.itinerary.itineraryPoints.first.elev
78
            }
79
        );
80
        const itineraryPointsIterator = this.#route.itinerary.itineraryPoints.iterator;
81
        itineraryPointsIterator.done;
82
        let previousItineraryPointDistance = ZERO;
83
        let itineraryPointdistance = itineraryPointsIterator.value.distance;
84
        let previousItineraryPointElev = itineraryPointsIterator.value.elev;
85
        itineraryPointsIterator.done;
86
87
        let smoothPointDistance = this.#smoothDistance;
88
89
        // next points
90
        while ( smoothPointDistance < this.#route.distance ) {
91
92
            if ( itineraryPointdistance < smoothPointDistance ) {
93
                previousItineraryPointDistance = itineraryPointdistance;
94
                previousItineraryPointElev = itineraryPointsIterator.value.elev;
95
                while ( itineraryPointdistance < smoothPointDistance ) {
96
                    itineraryPointdistance += itineraryPointsIterator.value.distance;
97
                    itineraryPointsIterator.done;
98
                }
99
            }
100
101
            let ascentFactor =
102
                ( itineraryPointsIterator.value.elev - previousItineraryPointElev )
103
                /
104
                ( itineraryPointdistance - previousItineraryPointDistance );
105
            const smoothPointElev =
106
                previousItineraryPointElev
107
                +
108
                ( ( smoothPointDistance - previousItineraryPointDistance ) * ascentFactor );
109
            this.#smoothPoints.push (
110
                {
111
                    distance : smoothPointDistance,
112
                    elev : smoothPointElev,
113
                    smoothElev : ZERO
114
                }
115
            );
116
            smoothPointDistance += this.#smoothDistance;
117
        }
118
119
        // last point
120
        this.#smoothPoints.push (
121
            {
122
                distance : this.#route.distance,
123
                elev : this.#route.itinerary.itineraryPoints.last.elev,
124
                smoothElev : this.#route.itinerary.itineraryPoints.last.elev
125
            }
126
        );
127
    }
128
129
    /**
130
    Compute the smooth elevation for the points in the array
131
    */
132
133
    #computeSmoothElev ( ) {
134
        let deltaElev =
135
            ( this.#smoothPoints [ this.#smoothPointsNumber - ONE ].elev - this.#smoothPoints [ ZERO ].elev )
136
            /
137
            ( this.#smoothPointsNumber - ONE );
138
139
        let pointCounter = ZERO;
140
        for ( pointCounter = ZERO; pointCounter < this.#smoothPointsNumber; pointCounter ++ ) {
141
            this.#smoothPoints [ pointCounter ].smoothElev =
142
                this.#smoothPoints [ ZERO ].elev + ( deltaElev * pointCounter );
143
        }
144
145
        for (
146
            pointCounter = this.#smoothPointsNumber;
147
            pointCounter < this.#smoothPoints.length - this.#smoothPointsNumber;
148
            pointCounter ++
149
        ) {
150
            let elevSum = ZERO;
151
            for (
152
                let pointNumber = pointCounter - this.#smoothPointsNumber;
153
                pointNumber <= pointCounter + this.#smoothPointsNumber;
154
                pointNumber ++
155
            ) {
156
                elevSum += this.#smoothPoints [ pointNumber ].elev;
157
            }
158
            this.#smoothPoints [ pointCounter ].smoothElev = elevSum / ( ( this.#smoothPointsNumber * TWO ) + ONE );
159
        }
160
161
        pointCounter --;
162
163
        const deltaSmoothElev =
164
            (
165
                this.#smoothPoints [ pointCounter + this.#smoothPointsNumber ].smoothElev
166
                -
167
                this.#smoothPoints [ pointCounter ].smoothElev
168
            )
169
            /
170
            this.#smoothPointsNumber;
171
172
        let tmpSmoothElev = this.#smoothPoints [ pointCounter ].smoothElev;
173
        let tmpPointCounter = ONE;
174
175
        pointCounter ++;
176
177
        for ( ; pointCounter < this.#smoothPoints.length - ONE; tmpPointCounter ++, pointCounter ++ ) {
178
            this.#smoothPoints [ pointCounter ].smoothElev =
179
                tmpSmoothElev + ( deltaSmoothElev * tmpPointCounter );
180
        }
181
    }
182
183
    /**
184
    Compute the smooth distance
185
    The smooth distance is depending of the toal ascent and descent of the route and of the route distance.
186
    Smooth distance must be an integer.
187
    */
188
189
    #computeSmoothDistance ( ) {
190
191
        // computing the distance and elev of the route (elev is the sum of ascent and descent see Math.abs)
192
        const itineraryPointsIterator = this.#route.itinerary.itineraryPoints.iterator;
193
        let elev = ZERO;
194
        while ( ! itineraryPointsIterator.done ) {
195
            elev +=
196
                itineraryPointsIterator.next
197
                    ?
198
                    Math.abs ( itineraryPointsIterator.value.elev - itineraryPointsIterator.next.elev )
199
                    :
200
                    ZERO;
201
202
        }
203
204
        // Computing smooth distance
205
        this.#smoothDistance =
206
            Math.floor (
207
                Math.min (
208
                    theConfig.route.elev.smoothCoefficient * this.#route.distance / elev,
209
                    this.#route.distance / ( THREE * this.#smoothPointsNumber )
210
                )
211
            );
212
    }
213
214
    /**
215
    Report the smooth elev from the smoothPoints array to the route itinerary
216
    */
217
218
    #setSmoothElev ( ) {
219
220
        const itineraryPointsIterator = this.#route.itinerary.itineraryPoints.iterator;
221
222
        // we skip the first itinerary point
223
        itineraryPointsIterator.done;
224
        let itineraryPointsTotalDistance = itineraryPointsIterator.value.distance;
225
226
        // loop on the itinerary point to push the smooth elev
227
        while ( ! itineraryPointsIterator.done ) {
228
            const previousIronPoint =
229
            this.#smoothPoints [ Math.floor ( itineraryPointsTotalDistance / this.#smoothDistance ) ];
230
            const nextIronPoint =
231
                this.#smoothPoints [ Math.ceil ( itineraryPointsTotalDistance / this.#smoothDistance ) ];
232
233
            // nextIronPoint is null for the last itineray point, so the last point is also skipped
234
            if ( previousIronPoint && nextIronPoint ) {
235
                const deltaDist = itineraryPointsTotalDistance - previousIronPoint.distance;
236
                const ascentFactor = ( nextIronPoint.elev - previousIronPoint.elev ) /
237
                    ( nextIronPoint.distance - previousIronPoint.distance );
238
                itineraryPointsIterator.value.elev = previousIronPoint.elev + ( deltaDist * ascentFactor );
239
            }
240
            itineraryPointsTotalDistance += itineraryPointsIterator.value.distance;
241
        }
242
    }
243
244
    /**
245
    The constructor
246
    */
247
248
    constructor ( ) {
249
        Object.freeze ( this );
250
    }
251
252
    /**
253
    Smooth a route profile.
254
    @param {Route} route The route to smooth
255
    */
256
257
    smooth ( route ) {
258
        this.#route = route;
259
        this.#computeSmoothDistance ( );
260
        this.#createSmoothPoints ( );
261
        this.#computeSmoothElev ( );
262
        this.#setSmoothElev ( );
263
    }
264
}
265
266
export default ProfileSmoothingIron;
267
268
/* --- End of file --------------------------------------------------------------------------------------------------------- */
269