File : routeProviders/PublicTransportData.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 theSphericalTrigonometry from '../core/lib/SphericalTrigonometry.js';
26
27
import { ZERO, INVALID_OBJ_ID, ONE, TWO } from '../main/Constants.js';
28
29
/* ------------------------------------------------------------------------------------------------------------------------- */
30
/**
31
coming soon...
32
@ignore
33
*/
34
/* ------------------------------------------------------------------------------------------------------------------------- */
35
36
class PublicTransportData {
37
38
    #newId;
39
    #selectedRelationId;
40
    #waysMap;
41
    #nodesMap;
42
    #stopsMap;
43
    #nodes3WaysCounter;
44
45
    /**
46
    The constructor
47
    */
48
49
    constructor ( selectedRelationId ) {
50
        Object.freeze ( this );
51
        this.#newId = INVALID_OBJ_ID;
52
        this.#selectedRelationId = selectedRelationId;
53
        this.#waysMap = new Map ( );
54
        this.#nodesMap = new Map ( );
55
        this.#stopsMap = new Map ( );
56
        this.#nodes3WaysCounter = ZERO;
57
    }
58
59
    get nodes3WaysCounter ( ) { return this.#nodes3WaysCounter; }
60
    set nodes3WaysCounter ( nodes3WaysCounter ) { this.#nodes3WaysCounter = nodes3WaysCounter; }
61
62
    get waysMap ( ) { return this.#waysMap; }
63
    get nodesMap ( ) { return this.#nodesMap; }
64
    get stopsMap ( ) { return this.#stopsMap; }
65
    get newId ( ) { return this.#newId --; }
66
67
    /**
68
    */
69
70
    firstOf ( array ) {
71
        return array [ ZERO ];
72
    }
73
74
    /**
75
    */
76
77
    lastOf ( array ) {
78
        return array [ array.length - ONE ];
79
    }
80
81
    /**
82
    */
83
84
    removeFrom ( array, value ) {
85
        array.splice ( array.indexOf ( value ), ONE );
86
    }
87
88
    /**
89
    */
90
91
    #reverseWay ( way ) {
92
93
        const oldStartNode = this.#nodesMap.get ( this.firstOf ( way.nodesIds ) );
94
        const oldEndNode = this.#nodesMap.get ( this.lastOf ( way.nodesIds ) );
95
96
        this.removeFrom ( oldStartNode.startingWaysIds, way.id );
97
        oldStartNode.endingWaysIds.push ( way.id );
98
99
        this.removeFrom ( oldEndNode.endingWaysIds, way.id );
100
        oldEndNode.startingWaysIds.push ( way.id );
101
102
        way.nodesIds.reverse ( );
103
104
    }
105
106
    /**
107
    */
108
109
    mergeWays ( waysId1, waysId2 ) {
110
111
        const way1 = this.#waysMap.get ( waysId1 );
112
        const way2 = this.#waysMap.get ( waysId2 );
113
114
        // reversing some ways, so :
115
        // - the 2 ways have the same direction
116
        // - the starting node of the merged way is the starting node of way1
117
        // - the ending node of the merged way is the ending node of way2
118
        // - the removed node is the ending node of way1
119
120
        if ( this.lastOf ( way1.nodesIds ) === this.lastOf ( way2.nodesIds ) ) {
121
            this.#reverseWay ( way2 );
122
        }
123
        else if ( this.firstOf ( way1.nodesIds ) === this.firstOf ( way2.nodesIds ) ) {
124
            this.#reverseWay ( way1 );
125
        }
126
        else if ( this.firstOf ( way1.nodesIds ) === this.lastOf ( way2.nodesIds ) ) {
127
            this.#reverseWay ( way1 );
128
            this.#reverseWay ( way2 );
129
130
        }
131
132
        // removing the node at the merging node and all the starting or ending ways of the node
133
        const mergedNode = this.#nodesMap.get ( way1.nodesIds.pop ( ) );
134
        mergedNode.startingWaysIds = [];
135
        mergedNode.endingWaysIds = [];
136
137
        // and then merging the 2 ways
138
        way1.nodesIds = way1.nodesIds.concat ( way2.nodesIds );
139
        way1.distance += way2.distance;
140
141
        // and changing the ending ways in the last node
142
        const endNode = this.#nodesMap.get ( this.lastOf ( way1.nodesIds ) );
143
        this.removeFrom ( endNode.endingWaysIds, way2.id );
144
        endNode.endingWaysIds.push ( way1.id );
145
146
        // finally we remove the second way from the ways map
147
        this.#waysMap.delete ( way2.id );
148
149
        return way1.id;
150
    }
151
152
    /**
153
    */
154
155
    #cloneNode ( nodeId ) {
156
157
        const node = this.#nodesMap.get ( nodeId );
158
159
        const clonedNode = {
160
            id : this.newId,
161
            lat : node.lat,
162
            lon : node.lon,
163
            type : 'node',
164
            startingWaysIds : [],
165
            endingWaysIds : [],
166
            isNode3Ways : node.isNode3Ways
167
        };
168
169
        this.#nodesMap.set ( clonedNode.id, clonedNode );
170
171
        return clonedNode.id;
172
    }
173
174
    /**
175
    */
176
177
    cloneWay ( wayId ) {
178
179
        const way = this.#waysMap.get ( wayId );
180
181
        const clonedWay = {
182
            id : this.newId,
183
            type : 'way',
184
            nodesIds : [],
185
            distance : way.distance
186
        };
187
188
        way.nodesIds.forEach ( nodeId => clonedWay.nodesIds.push ( this.#cloneNode ( nodeId ) ) );
189
190
        this.#nodesMap.get ( this.firstOf ( clonedWay.nodesIds ) ).startingWaysIds.push ( clonedWay.id );
191
        this.#nodesMap.get ( this.lastOf ( clonedWay.nodesIds ) ).endingWaysIds.push ( clonedWay.id );
192
193
        this.#waysMap.set ( clonedWay.id, clonedWay );
194
195
        return clonedWay.id;
196
    }
197
198
    /**
199
    */
200
201
    createMaps ( elements ) {
202
203
        this.#waysMap.clear ( );
204
        this.#nodesMap.clear ( );
205
        this.#stopsMap.clear ( );
206
207
        // Elements are pushed in 2 maps: 1 for nodes and 1 for ways
208
        elements.forEach (
209
            element => {
210
                switch ( element.type ) {
211
                case 'way' :
212
213
                    // replacing the nodes property with the nodesId property to
214
                    // avoid confusion between nodes and nodesId. the element.nodes contains nodesIds!!
215
                    element.nodesIds = element.nodes;
216
                    delete element.nodes;
217
                    if ( TWO <= element.nodesIds.length ) {
218
                        element.distance = ZERO;
219
                        this.#waysMap.set ( element.id, element );
220
                    }
221
                    break;
222
                case 'node' :
223
                    element.startingWaysIds = [];
224
                    element.endingWaysIds = [];
225
                    element.isNode3Ways = false;
226
                    this.#nodesMap.set ( element.id, element );
227
                    break;
228
                case 'relation' :
229
                    element.members.forEach (
230
                        member => {
231
232
                            // extracting all nodes with role 'stop'
233
                            if ( 'node' === member.type && member.role && 'stop' === member.role ) {
234
                                this.#stopsMap.set ( member.ref, member.ref );
235
                            }
236
                        }
237
                    );
238
                    break;
239
                default :
240
                    break;
241
                }
242
            }
243
        );
244
245
        // The stop map contain only the nodeId
246
        // we replace the nodeId with the node when possible
247
        this.#stopsMap.forEach (
248
            nodeId => {
249
                const node = this.#nodesMap.get ( nodeId );
250
                if ( node ) {
251
                    this.#stopsMap.set ( nodeId, node );
252
                }
253
                else {
254
                    window.TaN.showInfo (
255
                        'the relation ' +
256
                        this.#selectedRelationId +
257
                        ' have nodes not positionned on the railway ( node ' +
258
                        nodeId +
259
                        ').' );
260
                    this.#stopsMap.delete ( nodeId );
261
                }
262
            }
263
        );
264
265
        // Starting and ending ways are added to each node and length computed
266
        this.#waysMap.forEach (
267
            way => {
268
                this.#nodesMap.get ( this.firstOf ( way.nodesIds ) ).startingWaysIds.push ( way.id );
269
                this.#nodesMap.get ( this.lastOf ( way.nodesIds ) ).endingWaysIds.push ( way.id );
270
                let previousNode = null;
271
                way.nodesIds.forEach (
272
                    nodeId => {
273
                        const node = this.#nodesMap.get ( nodeId );
274
                        if ( previousNode ) {
275
                            way.distance += theSphericalTrigonometry.pointsDistance (
276
                                [ node.lat, node.lon ], [ previousNode.lat, previousNode.lon ]
277
                            );
278
                        }
279
                        previousNode = node;
280
                    }
281
                );
282
            }
283
        );
284
    }
285
}
286
287
export default PublicTransportData;
288
289
/* --- End of file --------------------------------------------------------------------------------------------------------- */
290