2
* uNav http://launchpad.net/unav
3
* Copyright (C) 2015-2016 Marcos Alvarez Costales https://launchpad.net/~costales
5
* uNav is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* uNav is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
16
function Navigator(maths, lang) {
20
this.pos_now = new Object();
21
this.pos_now['lat'] = null;
22
this.pos_now['lng'] = null;
24
this.pos_start = new Object();
25
this.pos_start['lat'] = null;
26
this.pos_start['lng'] = null;
28
this.pos_end = new Object();
29
this.pos_end['lat'] = null;
30
this.pos_end['lng'] = null;
32
this.accu = this.ACCU4DRIVE + 1;
35
this.gps_device_ok = true;
37
this.route = new Object();
38
this.route['start_check_out'] = false;
39
this.route['status'] = 'no'; // no | errorAPI | waiting4signal | calc | drawing | 2review | yes | out | ended
40
this.route['total'] = {
44
this.route['track'] = {
49
this.route['tracks'] = [];
50
this.route['turf'] = null;
51
this.route['radars'] = [];
53
this.nearest = new Object();
54
this.nearest['indication'] = '1';
55
this.nearest['dist2turn'] = 0;
56
this.nearest['msg'] = '';
57
this.nearest['distance'] = 0;
58
this.nearest['dist_track_done'] = 0;
59
this.nearest['distance_total'] = 0;
60
this.nearest['time'] = 0;
61
this.nearest['voice'] = false;
62
this.nearest['speaked'] = false;
63
this.nearest['radar'] = false;
64
this.nearest['radar_sound'] = false;
65
this.nearest['radar_speed'] = '!';
68
Navigator.prototype.ACCU4DRIVE = 250;
69
Navigator.prototype.IS_IN_ROUTE = 95;
70
Navigator.prototype.MAX_START_CHECK_OUT = 250;
71
Navigator.prototype.DIST4INDICATION = 99; // Never > 99 for preserve voices
72
Navigator.prototype.SPEED_CITY = 62;
73
Navigator.prototype.SPEED_INTERCITY = 82;
74
Navigator.prototype.RADAR_IN_ROUTE = 27;
75
Navigator.prototype.RADARS_MAX = 60;
78
Navigator.prototype.set_gps_data = function(lat, lng, accu, speed) {
79
// Hack: Phones are losting the signal 1/15 times #1469008
80
if (this.pos_now['lat'] !== null && this.pos_now['lng'] !== null && this.gps_device_ok && accu > this.ACCU4DRIVE) {
81
this.gps_device_ok = false;
85
this.gps_device_ok = true;
88
this.pos_now['lat'] = lat;
89
this.pos_now['lng'] = lng;
90
this.accuracy = parseInt(accu);
92
this.speed = this.maths.speed2human(speed);
97
Navigator.prototype.get_pos_data = function() {
99
now_lat: this.pos_now['lat'],
100
now_lng: this.pos_now['lng'],
101
start_lat: this.pos_start['lat'],
102
start_lng: this.pos_start['lng'],
103
end_lat: this.pos_end['lat'],
104
end_lng: this.pos_end['lng'],
107
gps_is_OK: this.gps_device_ok
111
Navigator.prototype.radars_clear = function () {
112
this.route['radars'] = [];
115
Navigator.prototype.set_radar = function(lat, lng, speed) {
117
var pt_radar = turf.point([lng, lat]);
118
var pt_near = turf.pointOnLine(this.route['complete_line'], pt_radar);
120
dist2line = geolib.getDistance(
121
{latitude: lat, longitude: lng},
122
{latitude: pt_near.geometry.coordinates[1], longitude: pt_near.geometry.coordinates[0]}
125
if (dist2line <= this.RADAR_IN_ROUTE && this.route['radars'].length <= this.RADARS_MAX) { // Add radar
126
this.route['radars'].push({
135
Navigator.prototype.get_radars = function () {
136
return this.route['radars'];
139
Navigator.prototype.set_route = function(total_m, total_s, line_encoded, tracks) {
141
this.route['status'] = 'drawing';
144
this.nearest['distance_total'] = total_m;
145
this.nearest['time'] = total_s;
148
this.route['start_check_out'] = false;
149
var coords = this.maths.decode(line_encoded, true);
151
// Set new start/end points for markers
152
this.pos_start['lat'] = coords[0][0];
153
this.pos_start['lng'] = coords[0][1];
154
this.pos_end['lat'] = coords[(coords.length)-1][0];
155
this.pos_end['lng'] = coords[(coords.length)-1][1];
158
this.route['tracks'] = [];
159
this.route['turf'] = [];
160
this.route['complete_line'] = {
164
"type": "LineString",
165
"coordinates": this.maths.decode(line_encoded, false)
170
var adding_coord = false;
171
var instruction = '';
173
for (i=0; i < tracks.length; i++) {
176
if (tracks[i].type == 26) {
177
switch (tracks[i].roundabout_exit_count) {
179
tracks[i].type = 261;
182
tracks[i].type = 262;
185
tracks[i].type = 263;
188
tracks[i].type = 264;
192
if (tracks[i].type == 27 && i > 0) {
193
switch (tracks[i-1].type) {
195
tracks[i].type = 271;
198
tracks[i].type = 272;
201
tracks[i].type = 273;
204
tracks[i].type = 274;
211
if (this.ui_lang == 'en' || this.ui_lang == 'e1' || this.ui_lang == 'e2')
212
instruction = tracks[i].instruction.slice(0, -1);
214
instruction = this.compose_instruction(
216
tracks[i].instruction,
217
tracks[i].street_names,
219
tracks[i].roundabout_exit_count
224
if (tracks[i].type < 4 || tracks[i].type > 6)
225
this.route['tracks'].push({
226
type: tracks[i].type,
227
coordinates: [coords[tracks[i].begin_shape_index][1], coords[tracks[i].begin_shape_index][0]], // begin coord lng,lat
228
instruction: instruction,
229
distance: (tracks[i].length*1000),
230
duration: tracks[i].time,
231
way_name: tracks[i].hasOwnProperty('street_names') ? tracks[i].street_names[0] : '', // get 1st for split instruction
235
this.route['tracks'].push({
236
type: tracks[i].type,
237
coordinates: [coords[tracks[i].begin_shape_index][1], coords[tracks[i].begin_shape_index][0]], // begin coord lng,lat
238
instruction: t("You are near to the destination"),
245
// Check Inside + near point
246
if (i_coord=tracks[i].begin_shape_index != tracks[i].end_shape_index) { // Not considerate end point
248
for (i_coord=tracks[i].begin_shape_index; i_coord <= tracks[i].end_shape_index; i_coord++) {
249
turf_line.push([coords[i_coord][1], coords[i_coord][0]]); // lng,lat
251
this.route['turf'].push(turf.linestring(turf_line));
256
Navigator.prototype.compose_instruction = function(type, en_instruction, street_names, sign, roundabout_exit) {
257
var instruction = '';
264
instruction = t("Go");
266
case 2: // StartRight
267
instruction = t("Go to your right");
270
instruction = t("Go to your left");
272
case 4: // Destination
273
instruction = t("You have arrived at your destination");
275
case 5: // DestinationRight
276
instruction = t("Your destination is on the right");
278
case 6: // DestinationLeft
279
instruction = t("Your destination is on the left");
282
instruction = t("Current road becomes");
285
instruction = t("Continue");
287
case 9: // SlightRight
288
instruction = t("Bear right");
291
instruction = t("Turn right");
293
case 11: // SharpRight
294
instruction = t("Make a sharp right");
296
case 12: // UturnRight
297
instruction = t("Make a right U-turn");
299
case 13: // UturnLeft
300
instruction = t("Make a left U-turn");
302
case 14: // SharpLeft
303
instruction = t("Make a sharp left");
306
instruction = t("Turn left");
308
case 16: // SlightLeft
309
instruction = t("Bear left");
311
case 17: // RampStraight
312
instruction = t("Stay straight on ramp");
314
case 18: // RampRight
315
instruction = t("Turn right on ramp");
318
instruction = t("Turn left on ramp");
320
case 20: // ExitRight
321
instruction = t("Take the exit on the right");
324
instruction = t("Take the exit on the left");
326
case 22: // StayStraight
327
instruction = t("Keep straight at the fork");
329
case 23: // StayRight
330
instruction = t("Keep right at the fork");
333
instruction = t("Keep left at the fork");
336
instruction = t("Merge");
338
case 26: // RoundaboutsEnter
343
instruction = t("Enter the roundabout and take the exit %1").replace('%1', roundabout_exit);
345
case 27: // RoundaboutExits
346
instruction = t("Exit the roundabout");
349
instruction = t("Enter the roundabout and take the exit %1").replace('%1', '1');
352
instruction = t("Enter the roundabout and take the exit %1").replace('%1', '2');
355
instruction = t("Enter the roundabout and take the exit %1").replace('%1', '3');
358
instruction = t("Enter the roundabout and take the exit %1").replace('%1', '4');
360
case 28: // FerryEnter
361
instruction = t("Take the Ferry");
363
case 29: // FerryExit
364
instruction = t("Leave the Ferry");
374
if (sign.hasOwnProperty('exit_number_elements')) {
376
sign.exit_number_elements.forEach( function( item ) {
379
number = item['text'];
381
number = number + ', ' + item['text'].trim();
387
if (sign.hasOwnProperty('exit_branch_elements')) {
389
sign.exit_branch_elements.forEach( function( item ) {
392
branch = item['text'];
394
branch = branch + ', ' + item['text'].trim();
399
if (sign.hasOwnProperty('exit_toward_elements')) {
401
sign.exit_toward_elements.forEach( function( item ) {
404
toward = item['text'];
406
toward = toward + ', ' + item['text'].trim();
411
if (sign.hasOwnProperty('exit_name_elements')) {
413
sign.exit_name_elements.forEach( function( item ) {
418
name = name + ', ' + item['text'].trim();
424
if ((type != 20 && type != 21) &&
425
(street_names && !branch && !toward && !name)) {
426
instruction = instruction + t(" onto %1").replace('%1', street_names);
429
instruction = instruction + t(". Exit %1").replace('%1', number);
431
instruction = instruction + t(" to take the %1").replace('%1', branch);
433
instruction = instruction + t(" toward %1").replace('%1', toward);
435
instruction = instruction + t(", %1").replace('%1', name);
440
Navigator.prototype.get_route_indication = function() {
441
var voice_tmp = this.nearest['voice'];
442
if (voice_tmp) // Need because ended will call here several times
443
this.nearest['voice'] = false;
446
indication: this.nearest['indication'],
447
dist2turn: this.nearest['dist2turn'],
448
msg: this.nearest['msg'],
449
time: this.nearest['time'],
450
distance: this.nearest['distance'],
451
dist_track_done: this.nearest['dist_track_done'],
452
distance_total: this.nearest['distance_total'],
455
speaked: this.nearest['speaked'],
456
radar: this.nearest['radar'],
457
radar_sound: this.nearest['radar_sound'],
458
radar_speed: this.nearest['radar_speed']
462
Navigator.prototype.get_route_tracks = function() {
463
return nav.route['tracks'];
466
Navigator.prototype.get_route_line_map = function() {
467
var line_coords = [];
468
var line_added = false;
471
for (i=0; i < this.route['turf'].length; i++) {
472
// For all coordenates in each track
473
for (i2=0; i2 < this.route['turf'][i].geometry.coordinates.length; i2++) {
474
// Already added? Because the last of each will be the same of next start
476
for (i3=0; i3 < line_coords.length; i3++)
477
if (this.route['turf'][i].geometry.coordinates[i2][0] == line_coords[i3][0] && this.route['turf'][i].geometry.coordinates[i2][1] == line_coords[i3][1])
481
line_coords.push(this.route['turf'][i].geometry.coordinates[i2]);
488
Navigator.prototype.update = function() {
489
if (this.route['status'] != 'yes' && this.route['status'] != 'out' && this.route['status'] != 'calc_from_out')
495
this.nearest['distance'] = 0;
496
this.nearest['dist_track_done'] = 0;
497
this.nearest['distance_total'] = 0;
498
this.nearest['time'] = 0;
499
this.nearest['voice'] = false;
500
this.nearest['speaked'] = false;
501
this.nearest['radar'] = false;
502
this.nearest['radar_sound'] = false;
505
// DISTANCES pos to route lines
506
var all_distances = [];
507
for (i=0; i < this.route['turf'].length; i++) {
508
pt_now = turf.point([this.pos_now['lng'], this.pos_now['lat']]);
509
pt_near = turf.pointOnLine(this.route['turf'][i], pt_now);
510
dist2line = geolib.getDistance(
511
{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
512
{latitude: pt_near.geometry.coordinates[1], longitude: pt_near.geometry.coordinates[0]}
514
all_distances.push(dist2line);
516
var ind_nearest = all_distances.indexOf(Math.min.apply(Math, all_distances));
519
// STORE the nearest. There are i+1 tracks, because the turf are lines and tracks are end points of track
520
this.nearest['indication'] = this.route['tracks'][ind_nearest+1]['type'];
521
this.nearest['msg'] = this.route['tracks'][ind_nearest+1]['instruction'];
522
this.nearest['dist2turn'] = geolib.getDistance(
523
{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
524
{latitude: this.route['tracks'][ind_nearest+1]['coordinates'][1], longitude: this.route['tracks'][ind_nearest+1]['coordinates'][0]}
526
this.nearest['dist_track_done'] = geolib.getDistance(
527
{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
528
{latitude: this.route['tracks'][ind_nearest]['coordinates'][1], longitude: this.route['tracks'][ind_nearest]['coordinates'][0]}
532
if (!this.route['tracks'][ind_nearest+1]['speaked']) {
533
var dist2speed = this.DIST4INDICATION;
534
if (this.speed > this.SPEED_CITY)
535
dist2speed = this.DIST4INDICATION * 3;
536
if (this.speed > this.SPEED_INTERCITY)
537
dist2speed = this.DIST4INDICATION * 10;
539
if (this.nearest['dist2turn'] < dist2speed) {
540
this.route['tracks'][ind_nearest+1]['speaked'] = true;
541
this.nearest['voice'] = true;
542
this.nearest['speaked'] = true;
546
this.nearest['speaked'] = true;
549
// Total distance + time
550
for (i=ind_nearest; i < this.route['tracks'].length; i++) {
551
if (i == ind_nearest) { // Percent left
552
var percent_completed = ((this.route['tracks'][i]['distance'] - this.nearest['dist2turn']) / this.route['tracks'][i]['distance']) * 100;
553
this.nearest['time'] = this.route['tracks'][i]['duration'] - Math.abs(this.route['tracks'][i]['duration'] * percent_completed / 100);
554
this.nearest['distance'] = this.route['tracks'][i]['distance'] - Math.abs(this.route['tracks'][i]['distance'] * percent_completed / 100);
555
this.nearest['distance_total'] = this.nearest['distance'];
558
this.nearest['time'] += this.route['tracks'][i]['duration'];
559
this.nearest['distance'] += this.route['tracks'][i]['distance'];
560
this.nearest['distance_total'] += this.route['tracks'][i]['distance'];
567
var dist_radar_nearest = 9999;
568
var ind_radar_nearest = -1;
569
var dist2radar = this.DIST4INDICATION;
570
if (this.speed > this.SPEED_CITY)
571
dist2radar = this.DIST4INDICATION * 3;
573
for (i=0; i<this.route['radars'].length; i++) {
575
var dist_radar = geolib.getDistance(
576
{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
577
{latitude: this.route['radars'][i]['lat'], longitude: this.route['radars'][i]['lng']}
579
if (dist_radar <= dist2radar && dist_radar < dist_radar_nearest) {
580
ind_radar_nearest = i;
581
dist_radar_nearest = dist_radar;
585
if (ind_radar_nearest !== -1) {
586
this.nearest['radar'] = true;
587
this.nearest['radar_speed'] = this.route['radars'][ind_radar_nearest]['speed'];
588
if (!this.route['radars'][ind_radar_nearest]['alerted']) {
589
this.route['radars'][ind_radar_nearest]['alerted'] = true;
590
this.nearest['radar_sound'] = true;
598
if (all_distances[ind_nearest] <= this.IS_IN_ROUTE) {
599
this.route['start_check_out'] = true;
600
this.set_route_status('yes');
602
else if ((this.route['start_check_out'] || all_distances[ind_nearest] > this.MAX_START_CHECK_OUT) && nav.get_route_status() != 'calc_from_out'){
603
this.set_route_status('out');
608
if (this.nearest['indication'] >= 4 && this.nearest['indication'] <= 6 && this.nearest['dist2turn'] <= (this.DIST4INDICATION/2)) {
609
this.set_route_status('ended');
614
Navigator.prototype.set_route_status = function(status) {
615
this.route['status'] = status;
618
Navigator.prototype.get_route_status = function() {
619
return this.route['status'];
622
Navigator.prototype.calc2coord = function(lat, lng) {
623
this.set_route_status('waiting4signal');
625
this.pos_start['lat'] = this.pos_now['lat'];
626
this.pos_start['lng'] = this.pos_now['lng'];
627
this.pos_end['lat'] = lat;
628
this.pos_end['lng'] = lng;
630
this.nearest['dist2turn'] = 0;
633
Navigator.prototype.cancel_route = function() {
634
this.set_route_status('no');
636
this.pos_start['lat'] = null;
637
this.pos_start['lng'] = null;
638
this.pos_end['lat'] = null;
639
this.pos_end['lng'] = null;