File : core/mapIcon/TranslationRotationFinder.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
import theGeometry from '../../core/lib/Geometry.js';
27
import theSphericalTrigonometry from '../../core/lib/SphericalTrigonometry.js';
28
29
import { ICON_DIMENSIONS, ZERO, ONE, TWO, DEGREES, ICON_POSITION } from '../../main/Constants.js';
30
31
/* ------------------------------------------------------------------------------------------------------------------------- */
32
/**
33
Search:
34
- the translation needed to have the icon point in the middle of the icon
35
- the rotation needed to have the entry point at the bottom of the icon
36
- the direction to follow
37
- adapt the icon if icon is on the start or the end point
38
*/
39
/* ------------------------------------------------------------------------------------------------------------------------- */
40
41
class TranslationRotationFinder {
42
43
    /**
44
    A reference to the computeData object of the MapIconFromOsmFactory
45
    @type {ComputeDataForMapIcon}
46
    */
47
48
    #computeData;
49
50
    /**
51
    A reference to the noteData Object of the MapIconFromOsmFactory
52
    @type {NoteDataForMapIcon}
53
    */
54
55
    #noteData;
56
57
    /**
58
    A reference to the itineraryPoint used to compute the rotation
59
    @type {ItineraryPoint}
60
    */
61
62
    #rotationItineraryPoint;
63
64
    /**
65
    A reference to the itineraryPoint used to compute the direction
66
    @type {ItineraryPoint}
67
    */
68
69
    #directionItineraryPoint;
70
71
    /**
72
    The coordinates in pixel of the icon point
73
    @type {Array.<Number>}
74
    */
75
76
    #iconPoint;
77
78
    /**
79
    This method compute the translation needed to have the itinerary point in the middle of the svg
80
    */
81
82
    #computeTranslation ( ) {
83
        this.#computeData.translation = theGeometry.subtrackPoints (
84
            [ ICON_DIMENSIONS.svgViewboxDim / TWO, ICON_DIMENSIONS.svgViewboxDim / TWO ],
85
            theGeometry.project ( this.#noteData.latLng, theConfig.note.svgIcon.zoom )
86
        );
87
    }
88
89
    /**
90
    Searching a point at least at 10 m ( theConfig.note.svgIcon.angleDistance ) from the icon point for rotation
91
    */
92
93
    #findRotationPoint ( ) {
94
95
        this.#rotationItineraryPoint = this.#computeData.route.itinerary.itineraryPoints.previous (
96
            this.#computeData.nearestItineraryPointObjId,
97
            itineraryPoint => theSphericalTrigonometry.pointsDistance ( itineraryPoint.latLng, this.#noteData.latLng )
98
                >
99
                theConfig.note.svgIcon.angleDistance
100
        )
101
        ||
102
        this.#computeData.route.itinerary.itineraryPoints.first;
103
    }
104
105
    /**
106
    Searching a point at least at 10 m ( theConfig.note.svgIcon.angleDistance ) from the icon point for direction
107
    */
108
109
    #findDirectionPoint ( ) {
110
111
        this.#directionItineraryPoint = this.#computeData.route.itinerary.itineraryPoints.next (
112
            this.#computeData.nearestItineraryPointObjId,
113
            itineraryPoint => theSphericalTrigonometry.pointsDistance ( itineraryPoint.latLng, this.#noteData.latLng )
114
                >
115
                theConfig.note.svgIcon.angleDistance
116
        )
117
        ||
118
        this.#computeData.route.itinerary.itineraryPoints.last;
119
    }
120
121
    /**
122
    Transform the latLng of the icon ro pixel coordinates relative to the map origin
123
    */
124
125
    #computeIconPoint ( ) {
126
        this.#iconPoint = theGeometry.addPoints (
127
            theGeometry.project ( this.#noteData.latLng, theConfig.note.svgIcon.zoom ),
128
            this.#computeData.translation
129
        );
130
131
    }
132
133
    /**
134
    Computing rotation... if possible
135
    */
136
137
    #findRotation ( ) {
138
139
        if (
140
            this.#computeData.nearestItineraryPointObjId
141
            !==
142
            this.#computeData.route.itinerary.itineraryPoints.first.objId
143
        ) {
144
            const rotationPoint = theGeometry.addPoints (
145
                theGeometry.project ( this.#rotationItineraryPoint.latLng, theConfig.note.svgIcon.zoom ),
146
                this.#computeData.translation
147
            );
148
            this.#computeData.rotation =
149
                Math.atan (
150
                    ( this.#iconPoint [ ONE ] - rotationPoint [ ONE ] )
151
                    /
152
                    ( rotationPoint [ ZERO ] - this.#iconPoint [ ZERO ] )
153
                )
154
                *
155
                DEGREES.d180 / Math.PI;
156
157
            if ( ZERO > this.#computeData.rotation ) {
158
                this.#computeData.rotation += DEGREES.d360;
159
            }
160
            this.#computeData.rotation -= DEGREES.d270;
161
162
            // point 0,0 of the svg is the UPPER left corner
163
            if ( ZERO > rotationPoint [ ZERO ] - this.#iconPoint [ ZERO ] ) {
164
                this.#computeData.rotation += DEGREES.d180;
165
            }
166
        }
167
    }
168
169
    /**
170
    Computing direction ... if possible
171
    */
172
173
    #findDirection ( ) {
174
        if (
175
            this.#computeData.nearestItineraryPointObjId
176
            !==
177
            this.#computeData.route.itinerary.itineraryPoints.last.objId
178
        ) {
179
            const directionPoint = theGeometry.addPoints (
180
                theGeometry.project ( this.#directionItineraryPoint.latLng, theConfig.note.svgIcon.zoom ),
181
                this.#computeData.translation
182
            );
183
            this.#computeData.direction = Math.atan (
184
                ( this.#iconPoint [ ONE ] - directionPoint [ ONE ] )
185
                /
186
                ( directionPoint [ ZERO ] - this.#iconPoint [ ZERO ] )
187
            )
188
                *
189
                DEGREES.d180 / Math.PI;
190
191
            // point 0,0 of the svg is the UPPER left corner
192
            if ( ZERO > directionPoint [ ZERO ] - this.#iconPoint [ ZERO ] ) {
193
                this.#computeData.direction += DEGREES.d180;
194
            }
195
            this.#computeData.direction -= this.#computeData.rotation;
196
197
            // setting direction between 0 and 360
198
            while ( DEGREES.d0 > this.#computeData.direction ) {
199
                this.#computeData.direction += DEGREES.d360;
200
            }
201
            while ( DEGREES.d360 < this.#computeData.direction ) {
202
                this.#computeData.direction -= DEGREES.d360;
203
            }
204
        }
205
    }
206
207
    /**
208
    Search if the icon is at the start or the end of the route and adapt data
209
    */
210
211
    #findPositionOnRoute ( ) {
212
        if (
213
            this.#computeData.nearestItineraryPointObjId
214
            ===
215
            this.#computeData.route.itinerary.itineraryPoints.first.objId
216
        ) {
217
            this.#computeData.rotation = -this.#computeData.direction - DEGREES.d90;
218
            this.#computeData.direction = null;
219
            this.#computeData.positionOnRoute = ICON_POSITION.atStart;
220
        }
221
222
        if (
223
            this.#noteData.latLng [ ZERO ] === this.#computeData.route.itinerary.itineraryPoints.last.lat
224
            &&
225
            this.#noteData.latLng [ ONE ] === this.#computeData.route.itinerary.itineraryPoints.last.lng
226
        ) {
227
228
            // using lat & lng because last point is sometime duplicated
229
            this.#computeData.direction = null;
230
            this.#computeData.positionOnRoute = ICON_POSITION.atEnd;
231
        }
232
    }
233
234
    /**
235
    The constructor
236
    */
237
238
    constructor ( ) {
239
        Object.freeze ( this );
240
    }
241
242
    /**
243
    this method compute the rotation needed to have the SVG oriented on the itinerary
244
    and compute also the direction to take after the icon
245
    @param {ComputeDataForMapIcon} computeData The object with the data needed for the computations
246
    @param {NoteDataForMapIcon} noteData The object with the nota data
247
    */
248
249
    findData ( computeData, noteData ) {
250
251
        this.#computeData = computeData;
252
        this.#noteData = noteData;
253
254
        this.#computeTranslation ( );
255
        this.#findRotationPoint ( );
256
        this.#findDirectionPoint ( );
257
        this.#computeIconPoint ( );
258
        this.#findRotation ( );
259
        this.#findDirection ( );
260
        this.#findPositionOnRoute ( );
261
    }
262
}
263
264
export default TranslationRotationFinder;
265
266
/* --- End of file --------------------------------------------------------------------------------------------------------- */
267