File : core/mapIcon/MapIconFromOsmFactory.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 theSphericalTrigonometry from '../../core/lib/SphericalTrigonometry.js';
27
import SvgBuilder from './SvgBuilder.js';
28
import OverpassAPIDataLoader from '../../core/lib/OverpassAPIDataLoader.js';
29
import StreetFinder from './StreetFinder.js';
30
import ArrowAndTooltipFinder from './ArrowAndTooltipFinder.js';
31
import TranslationRotationFinder from './TranslationRotationFinder.js';
32
import NoteDataForMapIcon from './NoteDataForMapIcon.js';
33
import ComputeDataForMapIcon from './ComputeDataForMapIcon.js';
34
35
import { ICON_POSITION, ICON_DIMENSIONS, LAT_LNG, INVALID_OBJ_ID, ZERO, ONE } from '../../main/Constants.js';
36
37
/* ------------------------------------------------------------------------------------------------------------------------- */
38
/**
39
This class is used to create  an svg icon for a route note
40
*/
41
/* ------------------------------------------------------------------------------------------------------------------------- */
42
43
class MapIconFromOsmFactory {
44
45
    /**
46
    The NoteDataForMapIcon object needed to buid the note
47
    @type {NoteDataForMapIcon}
48
    */
49
50
    #noteData;
51
52
    /**
53
    The ComputeData object needed to buid the note
54
    @type {ComputeDataForMapIcon}
55
    */
56
57
    #computeData;
58
59
    /**
60
    An OverpassAPIDataLoader object used to search the osm data
61
    @type {OverpassAPIDataLoader}
62
    */
63
64
    #overpassAPIDataLoader;
65
66
    /**
67
    The distance used to search cities and hamlet in osm
68
    @type {Number}
69
    */
70
71
    #queryDistance;
72
73
    /**
74
    A guard to avoid to mutch requests at the same time
75
    @type {Boolean}
76
    */
77
78
    #requestStarted;
79
80
    /**
81
    A constant used for searching the OSM data
82
    @type {Number}
83
    */
84
85
    // eslint-disable-next-line no-magic-numbers
86
    static get #SEARCH_AROUND_FACTOR ( ) { return 1.5; }
87
88
    /**
89
    This method search the nearest itinerary point from the point given by the user
90
    */
91
92
    #searchNearestItineraryPoint ( ) {
93
94
        let nearestItineraryPoint = null;
95
96
        // Searching the nearest itinerary point
97
        let minDistance = Number.MAX_VALUE;
98
99
        // Iteration on the points...
100
        this.#computeData.route.itinerary.itineraryPoints.forEach (
101
            itineraryPoint => {
102
                const itineraryPointDistance =
103
                    theSphericalTrigonometry.pointsDistance ( this.#noteData.latLng, itineraryPoint.latLng );
104
                if ( minDistance > itineraryPointDistance ) {
105
                    minDistance = itineraryPointDistance;
106
                    nearestItineraryPoint = itineraryPoint;
107
                }
108
            }
109
        );
110
111
        // The coordinates of the nearest point are used as position of the SVG
112
        this.#noteData.latLng = nearestItineraryPoint.latLng;
113
        this.#computeData.nearestItineraryPointObjId = nearestItineraryPoint.objId;
114
    }
115
116
    /**
117
    Search and build all the needed data
118
    */
119
120
    #buildIconAndAdress ( ) {
121
122
        // calling the different objects used to build the note ...
123
        new TranslationRotationFinder ( ).findData ( this.#computeData, this.#noteData );
124
        new ArrowAndTooltipFinder ( ).findData ( this.#computeData, this.#noteData );
125
        new StreetFinder ( ).findData ( this.#computeData, this.#noteData, this.#overpassAPIDataLoader );
126
        new SvgBuilder ( ).buildSvg ( this.#computeData, this.#noteData, this.#overpassAPIDataLoader    );
127
128
        this.#requestStarted = false;
129
130
        // returning the results
131
        return this.#noteData;
132
    }
133
134
    /**
135
    Start the buid of the note data
136
    */
137
138
    async #exeGetIconAndAdress ( ) {
139
        if ( this.#requestStarted ) {
140
141
            // Return when another request is already running
142
            return null;
143
        }
144
145
        this.#requestStarted = true;
146
147
        // ComputeData and NoteData initialization
148
        // !!! ComputeData.route contains already the route
149
        // !!! NoteData.latLng contains already the note position
150
        this.#computeData.nearestItineraryPointObjId = INVALID_OBJ_ID;
151
        this.#computeData.positionOnRoute = ICON_POSITION.onRoute;
152
        this.#computeData.direction = null;
153
        this.#computeData.directionArrow = ' ';
154
        this.#computeData.translation = [ ZERO, ZERO ];
155
        this.#computeData.rotation = ZERO;
156
        this.#computeData.rcnRef = '';
157
158
        this.#noteData.iconContent = '';
159
        this.#noteData.tooltipContent = '';
160
        this.#noteData.address = '';
161
162
        // Moving the the nearest itinerary point
163
        this.#searchNearestItineraryPoint ( );
164
165
        // Starting the query to osm. Searching highways, administrative boundaries and places around the note
166
        /*
167
        Sample of query:
168
            way[highway](around:300,50.489312,5.501035)->.a;(.a >;.a;)->.a;.a out;
169
            is_in(50.644242,5.572354)->.e;area.e[admin_level][boundary="administrative"];out;
170
            node(around:1500,50.644242,5.572354)[place];out;
171
        */
172
173
        const queryLatLng =
174
            this.#noteData.latLng [ ZERO ].toFixed ( LAT_LNG.fixed ) +
175
            ',' +
176
            this.#noteData.latLng [ ONE ].toFixed ( LAT_LNG.fixed );
177
178
        const queries = [
179
            'way[highway](around:' +
180
            ( ICON_DIMENSIONS.svgViewboxDim * MapIconFromOsmFactory.#SEARCH_AROUND_FACTOR ).toFixed ( ZERO ) +
181
            ',' + queryLatLng + ')->.a;(.a >;.a;)->.a;.a out;' +
182
            'is_in(' + queryLatLng + ')->.e;area.e[admin_level][boundary="administrative"];out;' +
183
            'node(around:' + this.#queryDistance + ',' + queryLatLng + ')[place];out;'
184
        ];
185
186
        await this.#overpassAPIDataLoader.loadData ( queries, this.#noteData.latLng );
187
        if ( this.#overpassAPIDataLoader.statusOk ) {
188
            return this.#buildIconAndAdress ( );
189
        }
190
        return null;
191
    }
192
193
    /**
194
    The method used to buid the icon with a Promise
195
    @param {function} onOk The onOk handler of the Promise
196
    @param {function} onError The onError handler of the Promise
197
    */
198
199
    async #exeGetIconAndAdressWithPromise ( onOk, onError ) {
200
        const result = await this.#exeGetIconAndAdress ( );
201
202
        if ( result ) {
203
            onOk ( result );
204
        }
205
        else {
206
            onError ( 'An error occurs...' );
207
        }
208
    }
209
210
    /**
211
    The constructor
212
    */
213
214
    constructor ( ) {
215
        Object.freeze ( this );
216
        this.#noteData = new NoteDataForMapIcon ( );
217
        this.#computeData = new ComputeDataForMapIcon ( );
218
        this.#overpassAPIDataLoader = new OverpassAPIDataLoader ( { searchRelations : false, setGeometry : false } );
219
        this.#queryDistance = Math.max (
220
            theConfig.geoCoder.distances.hamlet,
221
            theConfig.geoCoder.distances.village,
222
            theConfig.geoCoder.distances.city,
223
            theConfig.geoCoder.distances.town
224
        );
225
        this.#requestStarted = false;
226
    }
227
228
    /**
229
    get the svg and the data needed for creating the icon, using an async function
230
    @param {Array.<Number>} iconLatLng The latitude and longitude of the icon
231
    @param {!Route} route The route to witch the icon will be attached.
232
    @return {?NoteDataForMapIcon} An object with the note data
233
    */
234
235
    async getIconAndAdressAsync ( iconLatLng, route ) {
236
        this.#noteData.latLng = iconLatLng;
237
        this.#computeData.route = route;
238
239
        return this.#exeGetIconAndAdress ( );
240
    }
241
242
    /**
243
    get the svg and the data needed for creating the icon, using a promise
244
    @param {MapIconData} mapIconData An object with the latLng of the note and a reference to the
245
    Route for witch the icon is build
246
    @return {Promise} A Promise fullfilled with the svg data
247
    */
248
249
    getIconAndAdressWithPromise ( mapIconData ) {
250
        this.#noteData.latLng = mapIconData.latLng;
251
        this.#computeData.route = mapIconData.route;
252
253
        return new Promise ( ( onOk, onError ) => this.#exeGetIconAndAdressWithPromise ( onOk, onError ) );
254
    }
255
256
}
257
258
export default MapIconFromOsmFactory;
259
260
/* --- End of file --------------------------------------------------------------------------------------------------------- */
261