File : core/mapIcon/SvgBuilder.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 { SVG_NS, ICON_DIMENSIONS, ZERO, ONE, TWO, NOT_FOUND } from '../../main/Constants.js';
28
29
/* ------------------------------------------------------------------------------------------------------------------------- */
30
/**
31
This class is used to create  the svg for a map icon
32
*/
33
/* ------------------------------------------------------------------------------------------------------------------------- */
34
35
class SvgBuilder {
36
37
    /**
38
    A reference to the computeData object of the MapIconFromOsmFactory
39
    @type {ComputeDataForMapIcon}
40
    */
41
42
    #computeData;
43
44
    /**
45
    A reference to the overpassAPIDataLoader of the MapIconFromOsmFactory
46
    @type {OverpassAPIDataLoader}
47
    */
48
49
    #overpassAPIDataLoader;
50
51
    /**
52
    The svg element to build
53
    @type {SVGElement}
54
    */
55
56
    #svgElement;
57
58
    /**
59
    This method creates the svgElement
60
    */
61
62
    #createSvg ( ) {
63
64
        const FOUR = 4;
65
        this.#svgElement = document.createElementNS ( SVG_NS, 'svg' );
66
        this.#svgElement.setAttributeNS (
67
            null,
68
            'viewBox',
69
            String ( ICON_DIMENSIONS.svgViewboxDim / FOUR ) + ' ' +
70
            ( ICON_DIMENSIONS.svgViewboxDim / FOUR ) + ' ' +
71
            ( ICON_DIMENSIONS.svgViewboxDim / TWO ) + ' ' +
72
            ( ICON_DIMENSIONS.svgViewboxDim / TWO )
73
        );
74
        this.#svgElement.setAttributeNS ( null, 'class', 'TravelNotes-SvgIcon' );
75
    }
76
77
    /**
78
    This method create the SVG polyline for the route
79
    */
80
81
    #createRoute ( ) {
82
83
        // to avoid a big svg, all points outside the svg viewBox are not added
84
        let index = -ONE;
85
        let firstPointIndex = NOT_FOUND;
86
        let lastPointIndex = NOT_FOUND;
87
        const points = [];
88
        this.#computeData.route.itinerary.itineraryPoints.forEach (
89
            itineraryPoint => {
90
                index ++;
91
                const point = theGeometry.addPoints (
92
                    theGeometry.project ( itineraryPoint.latLng, theConfig.note.svgIcon.zoom ),
93
                    this.#computeData.translation
94
                );
95
                points.push ( point );
96
                const pointIsInside =
97
                    point [ ZERO ] >= ZERO
98
                    &&
99
                    point [ ONE ] >= ZERO
100
                    &&
101
                    point [ ZERO ] <= ICON_DIMENSIONS.svgViewboxDim
102
                    &&
103
                    point [ ONE ] <= ICON_DIMENSIONS.svgViewboxDim;
104
                if ( pointIsInside ) {
105
                    if ( NOT_FOUND === firstPointIndex ) {
106
                        firstPointIndex = index;
107
                    }
108
                    lastPointIndex = index;
109
                }
110
            }
111
        );
112
        if ( NOT_FOUND !== firstPointIndex && NOT_FOUND !== lastPointIndex ) {
113
            if ( ZERO < firstPointIndex ) {
114
                firstPointIndex --;
115
            }
116
            if ( this.#computeData.route.itinerary.itineraryPoints.length - ONE > lastPointIndex ) {
117
                lastPointIndex ++;
118
            }
119
            let pointsAttribute = '';
120
            for ( index = firstPointIndex; index <= lastPointIndex; index ++ ) {
121
                pointsAttribute += points[ index ] [ ZERO ].toFixed ( ZERO ) + ',' +
122
                    points[ index ] [ ONE ].toFixed ( ZERO ) + ' ';
123
            }
124
            const polyline = document.createElementNS ( SVG_NS, 'polyline' );
125
            polyline.setAttributeNS ( null, 'points', pointsAttribute );
126
            polyline.setAttributeNS ( null, 'class', 'TravelNotes-OSM-Itinerary' );
127
            polyline.setAttributeNS (
128
                null,
129
                'transform',
130
                'rotate(' + this.#computeData.rotation +
131
                    ',' + ( ICON_DIMENSIONS.svgViewboxDim / TWO ) +
132
                    ',' + ( ICON_DIMENSIONS.svgViewboxDim / TWO )
133
                    + ')'
134
            );
135
            this.#svgElement.appendChild ( polyline );
136
        }
137
    }
138
139
    /**
140
    This method creates the SVG elements for ways from OSM
141
    */
142
143
    #createWays ( ) {
144
145
        // to avoid a big svg, all points outside the svg viewBox are not added
146
        this.#overpassAPIDataLoader.ways.forEach (
147
            way => {
148
                let firstPointIndex = NOT_FOUND;
149
                let lastPointIndex = NOT_FOUND;
150
                let index = -ONE;
151
                const points = [ ];
152
                way.nodes.forEach (
153
                    nodeId => {
154
                        index ++;
155
                        const node = this.#overpassAPIDataLoader.nodes.get ( nodeId );
156
                        const point = theGeometry.addPoints (
157
                            theGeometry.project ( [ node.lat, node.lon ], theConfig.note.svgIcon.zoom ),
158
                            this.#computeData.translation
159
                        );
160
                        points.push ( point );
161
                        const pointIsInside =
162
                            point [ ZERO ] >= ZERO
163
                            &&
164
                            point [ ONE ] >= ZERO
165
                            &&
166
                            point [ ZERO ] <= ICON_DIMENSIONS.svgViewboxDim
167
                            &&
168
                            point [ ONE ] <= ICON_DIMENSIONS.svgViewboxDim;
169
                        if ( pointIsInside ) {
170
                            if ( NOT_FOUND === firstPointIndex ) {
171
                                firstPointIndex = index;
172
                            }
173
                            lastPointIndex = index;
174
                        }
175
                    }
176
                );
177
                if ( NOT_FOUND !== firstPointIndex && NOT_FOUND !== lastPointIndex ) {
178
                    if ( ZERO < firstPointIndex ) {
179
                        firstPointIndex --;
180
                    }
181
                    if ( way.nodes.length - ONE > lastPointIndex ) {
182
                        lastPointIndex ++;
183
                    }
184
                    let pointsAttribute = '';
185
                    for ( index = firstPointIndex; index <= lastPointIndex; index ++ ) {
186
                        pointsAttribute +=
187
                            points[ index ] [ ZERO ].toFixed ( ZERO ) + ',' +
188
                            points[ index ] [ ONE ].toFixed ( ZERO ) + ' ';
189
                    }
190
191
                    const polyline = document.createElementNS ( SVG_NS, 'polyline' );
192
                    polyline.setAttributeNS ( null, 'points', pointsAttribute );
193
                    polyline.setAttributeNS (
194
                        null,
195
                        'class',
196
                        'TravelNotes-OSM-Highway TravelNotes-OSM-Highway-' + way.tags.highway
197
                    );
198
                    polyline.setAttributeNS (
199
                        null,
200
                        'transform',
201
                        'rotate(' + this.#computeData.rotation +
202
                            ',' + ( ICON_DIMENSIONS.svgViewboxDim / TWO ) +
203
                            ',' + ( ICON_DIMENSIONS.svgViewboxDim / TWO ) +
204
                            ')'
205
                    );
206
207
                    this.#svgElement.appendChild ( polyline );
208
                }
209
            }
210
        );
211
    }
212
213
    /**
214
    This method creates the SVG element for RcnRef from OSM
215
    */
216
217
    #createRcnRef ( ) {
218
219
        if ( '' === this.#computeData.rcnRef ) {
220
            return;
221
        }
222
        const svgText = document.createElementNS ( SVG_NS, 'text' );
223
        svgText.textContent = this.#computeData.rcnRef;
224
        svgText.setAttributeNS ( null, 'x', String ( ICON_DIMENSIONS.svgViewboxDim / TWO ) );
225
        svgText.setAttributeNS ( null, 'y', String ( ICON_DIMENSIONS.svgViewboxDim / TWO ) );
226
        svgText.setAttributeNS ( null, 'class', 'TravelNotes-OSM-RcnRef' );
227
        this.#svgElement.appendChild ( svgText );
228
    }
229
230
    /**
231
    The constructor
232
    */
233
234
    constructor ( ) {
235
        Object.freeze ( this );
236
    }
237
238
    /**
239
    This method build the SVG element for the icon
240
    @param {ComputeDataForMapIcon} computeData The object with the data needed for the computations
241
    @param {NoteDataForMapIcon} noteData The object with the nota data
242
    @param {OverpassAPIDataLoader} overpassAPIDataLoader The OverpassAPIDataLoader object containing the data found in OSM
243
    */
244
245
    buildSvg ( computeData, noteData, overpassAPIDataLoader ) {
246
247
        this.#computeData = computeData;
248
        this.#overpassAPIDataLoader = overpassAPIDataLoader;
249
250
        this.#createSvg ( );
251
        this.#createRoute ( );
252
        this.#createWays ( );
253
        this.#createRcnRef ( );
254
255
        noteData.iconContent = this.#svgElement.outerHTML;
256
    }
257
}
258
259
export default SvgBuilder;
260
261
/* --- End of file --------------------------------------------------------------------------------------------------------- */
262