mirror of https://github.com/espruino/BangleApps
minor stuff
parent
d2b3840518
commit
f4ca3afe34
225
apps/gipy/app.js
225
apps/gipy/app.js
|
@ -1,30 +1,22 @@
|
|||
|
||||
let simulated = true;
|
||||
let file_version = 3;
|
||||
let code_key = 47490;
|
||||
|
||||
let interests_colors = [
|
||||
0x780F, // Bakery, purple
|
||||
0x001F, // DrinkingWater, blue
|
||||
0x07FF, // Toilets, cyan
|
||||
0x7BEF, // BikeShop, dark grey
|
||||
0xAFE5, // ChargingStation, green yellow
|
||||
0x7800, // Bank, maroon
|
||||
0xF800, // Supermarket, red
|
||||
0xF81F, // Table, pink
|
||||
0xFD20, // Artwork, orange
|
||||
0x07E0, // Pharmacy, green
|
||||
0x780f, // Bakery, purple
|
||||
0x001f, // DrinkingWater, blue
|
||||
0x07ff, // Toilets, cyan
|
||||
0xfd20, // Artwork, orange
|
||||
];
|
||||
|
||||
function binary_search(array, x) {
|
||||
let start = 0, end = array.length - 1;
|
||||
let start = 0,
|
||||
end = array.length - 1;
|
||||
|
||||
while (start <= end){
|
||||
while (start <= end) {
|
||||
let mid = Math.floor((start + end) / 2);
|
||||
if (array[mid] < x)
|
||||
start = mid + 1;
|
||||
else
|
||||
end = mid - 1;
|
||||
if (array[mid] < x) start = mid + 1;
|
||||
else end = mid - 1;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
@ -54,8 +46,12 @@ class Status {
|
|||
this.remaining_distances = r; // how much distance remains at start of each segment
|
||||
}
|
||||
update_position(new_position, direction) {
|
||||
|
||||
if (Bangle.isLocked() && this.position !== null && new_position.lon == this.position.lon && new_position.lat == this.position.lat) {
|
||||
if (
|
||||
Bangle.isLocked() &&
|
||||
this.position !== null &&
|
||||
new_position.lon == this.position.lon &&
|
||||
new_position.lat == this.position.lat
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -64,18 +60,31 @@ class Status {
|
|||
this.position = new_position;
|
||||
|
||||
// detect segment we are on now
|
||||
let next_segment = this.path.nearest_segment(this.position, Math.max(0, this.current_segment-1), Math.min(this.current_segment+2, this.path.len - 1), this.cos_direction, this.sin_direction);
|
||||
let next_segment = this.path.nearest_segment(
|
||||
this.position,
|
||||
Math.max(0, this.current_segment - 1),
|
||||
Math.min(this.current_segment + 2, this.path.len - 1),
|
||||
this.cos_direction,
|
||||
this.sin_direction
|
||||
);
|
||||
|
||||
if (this.is_lost(next_segment)) {
|
||||
// it did not work, try anywhere
|
||||
next_segment = this.path.nearest_segment(this.position, 0, this.path.len - 1, this.cos_direction, this.sin_direction);
|
||||
next_segment = this.path.nearest_segment(
|
||||
this.position,
|
||||
0,
|
||||
this.path.len - 1,
|
||||
this.cos_direction,
|
||||
this.sin_direction
|
||||
);
|
||||
}
|
||||
// now check if we strayed away from path or back to it
|
||||
let lost = this.is_lost(next_segment);
|
||||
if (this.on_path == lost) { // if status changes
|
||||
if (this.on_path == lost) {
|
||||
// if status changes
|
||||
if (lost) {
|
||||
Bangle.buzz(); // we lost path
|
||||
setTimeout(()=>Bangle.buzz(), 500);
|
||||
setTimeout(() => Bangle.buzz(), 500);
|
||||
}
|
||||
this.on_path = !lost;
|
||||
}
|
||||
|
@ -84,7 +93,9 @@ class Status {
|
|||
|
||||
// check if we are nearing the next point on our path and alert the user
|
||||
let next_point = this.current_segment + 1;
|
||||
this.distance_to_next_point = Math.ceil(this.position.distance(this.path.point(next_point)));
|
||||
this.distance_to_next_point = Math.ceil(
|
||||
this.position.distance(this.path.point(next_point))
|
||||
);
|
||||
if (this.reaching != next_point && this.distance_to_next_point <= 20) {
|
||||
this.reaching = next_point;
|
||||
let reaching_waypoint = this.path.is_waypoint(next_point);
|
||||
|
@ -101,12 +112,18 @@ class Status {
|
|||
}
|
||||
}
|
||||
remaining_distance() {
|
||||
return this.remaining_distances[this.current_segment+1] + this.position.distance(this.path.point(this.current_segment+1));
|
||||
return (
|
||||
this.remaining_distances[this.current_segment + 1] +
|
||||
this.position.distance(this.path.point(this.current_segment + 1))
|
||||
);
|
||||
}
|
||||
is_lost(segment) {
|
||||
let distance_to_nearest = this.position.fake_distance_to_segment(this.path.point(segment), this.path.point(segment+1));
|
||||
let distance_to_nearest = this.position.fake_distance_to_segment(
|
||||
this.path.point(segment),
|
||||
this.path.point(segment + 1)
|
||||
);
|
||||
let meters = 6371e3 * distance_to_nearest;
|
||||
return (meters > 20);
|
||||
return meters > 20;
|
||||
}
|
||||
display() {
|
||||
g.clear();
|
||||
|
@ -118,17 +135,32 @@ class Status {
|
|||
display_interest_points() {
|
||||
// this is the algorithm in case we have a lot of interest points
|
||||
// let's draw all points for 5 segments centered on current one
|
||||
let starting_group = Math.floor(Math.max(this.current_segment-2, 0) / 3);
|
||||
let ending_group = Math.floor(Math.min(this.current_segment+2, this.path.len-2) / 3);
|
||||
let starting_bucket = binary_search(this.path.interests_starts, starting_group);
|
||||
let ending_bucket = binary_search(this.path.interests_starts, ending_group+0.5);
|
||||
let starting_group = Math.floor(Math.max(this.current_segment - 2, 0) / 3);
|
||||
let ending_group = Math.floor(
|
||||
Math.min(this.current_segment + 2, this.path.len - 2) / 3
|
||||
);
|
||||
let starting_bucket = binary_search(
|
||||
this.path.interests_starts,
|
||||
starting_group
|
||||
);
|
||||
let ending_bucket = binary_search(
|
||||
this.path.interests_starts,
|
||||
ending_group + 0.5
|
||||
);
|
||||
// we have 5 points per bucket
|
||||
let end_index = Math.min(this.path.interests_types.length - 1, ending_bucket*5);
|
||||
for (let i = starting_bucket*5 ; i <= end_index ; i++) {
|
||||
let end_index = Math.min(
|
||||
this.path.interests_types.length - 1,
|
||||
ending_bucket * 5
|
||||
);
|
||||
for (let i = starting_bucket * 5; i <= end_index; i++) {
|
||||
let index = this.path.interests_on_path[i];
|
||||
let interest_point = this.path.interest_point(index);
|
||||
let color = this.path.interest_color(i);
|
||||
let c = interest_point.coordinates(this.position, this.cos_direction, this.sin_direction);
|
||||
let c = interest_point.coordinates(
|
||||
this.position,
|
||||
this.cos_direction,
|
||||
this.sin_direction
|
||||
);
|
||||
g.setColor(color).fillCircle(c[0], c[1], 5);
|
||||
}
|
||||
}
|
||||
|
@ -138,12 +170,24 @@ class Status {
|
|||
let now = new Date();
|
||||
let minutes = now.getMinutes().toString();
|
||||
if (minutes.length < 2) {
|
||||
minutes = '0' + minutes;
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
let hours = now.getHours().toString();
|
||||
g.setFont("6x8:2").setColor(g.theme.fg).drawString(hours + ":" + minutes, 0, g.getHeight() - 49);
|
||||
g.setFont("6x8:2")
|
||||
.setColor(g.theme.fg)
|
||||
.drawString(hours + ":" + minutes, 0, g.getHeight() - 49);
|
||||
g.drawString("d. " + rounded_distance + "/" + total, 0, g.getHeight() - 32);
|
||||
g.drawString("seg." + (this.current_segment + 1) + "/" + this.path.len + " " + this.distance_to_next_point + "m", 0, g.getHeight() - 15);
|
||||
g.drawString(
|
||||
"seg." +
|
||||
(this.current_segment + 1) +
|
||||
"/" +
|
||||
this.path.len +
|
||||
" " +
|
||||
this.distance_to_next_point +
|
||||
"m",
|
||||
0,
|
||||
g.getHeight() - 15
|
||||
);
|
||||
}
|
||||
display_map() {
|
||||
// don't display all segments, only those neighbouring current segment
|
||||
|
@ -163,9 +207,9 @@ class Status {
|
|||
let half_height = g.getHeight() / 2;
|
||||
let previous_x = null;
|
||||
let previous_y = null;
|
||||
for (let i = start ; i < end ; i++) {
|
||||
let tx = (points[2*i] - cx) * 40000.0;
|
||||
let ty = (points[2*i+1] - cy) * 40000.0;
|
||||
for (let i = start; i < end; i++) {
|
||||
let tx = (points[2 * i] - cx) * 40000.0;
|
||||
let ty = (points[2 * i + 1] - cy) * 40000.0;
|
||||
let rotated_x = tx * cos - ty * sin;
|
||||
let rotated_y = tx * sin + ty * cos;
|
||||
let x = half_width - Math.round(rotated_x); // x is inverted
|
||||
|
@ -178,7 +222,7 @@ class Status {
|
|||
}
|
||||
g.drawLine(previous_x, previous_y, x, y);
|
||||
|
||||
if (this.path.is_waypoint(i-1)) {
|
||||
if (this.path.is_waypoint(i - 1)) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 6);
|
||||
g.setColor(g.theme.bg);
|
||||
|
@ -188,14 +232,13 @@ class Status {
|
|||
g.fillCircle(previous_x, previous_y, 4);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(previous_x, previous_y, 3);
|
||||
|
||||
}
|
||||
|
||||
previous_x = x;
|
||||
previous_y = y;
|
||||
}
|
||||
|
||||
if (this.path.is_waypoint(end-1)) {
|
||||
if (this.path.is_waypoint(end - 1)) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 6);
|
||||
g.setColor(g.theme.bg);
|
||||
|
@ -223,13 +266,13 @@ class Path {
|
|||
let key = header[0];
|
||||
let version = header[1];
|
||||
let points_number = header[2];
|
||||
if ((key != code_key)||(version>file_version)) {
|
||||
if (key != code_key || version > file_version) {
|
||||
E.showMessage("Invalid gpc file");
|
||||
load();
|
||||
}
|
||||
|
||||
// path points
|
||||
this.points = Float64Array(buffer, offset, points_number*2);
|
||||
this.points = Float64Array(buffer, offset, points_number * 2);
|
||||
offset += 8 * points_number * 2;
|
||||
|
||||
// path waypoints
|
||||
|
@ -239,14 +282,22 @@ class Path {
|
|||
|
||||
// interest points
|
||||
let interests_number = header[3];
|
||||
this.interests_coordinates = Float64Array(buffer, offset, interests_number * 2);
|
||||
this.interests_coordinates = Float64Array(
|
||||
buffer,
|
||||
offset,
|
||||
interests_number * 2
|
||||
);
|
||||
offset += 8 * interests_number * 2;
|
||||
this.interests_types = Uint8Array(buffer, offset, interests_number);
|
||||
offset += interests_number;
|
||||
|
||||
// interests on path
|
||||
let interests_on_path_number = header[4];
|
||||
this.interests_on_path = Uint16Array(buffer, offset, interests_on_path_number);
|
||||
this.interests_on_path = Uint16Array(
|
||||
buffer,
|
||||
offset,
|
||||
interests_on_path_number
|
||||
);
|
||||
offset += 2 * interests_on_path_number;
|
||||
let starts_length = Math.ceil(interests_on_path_number / 5.0);
|
||||
this.interests_starts = Uint16Array(buffer, offset, starts_length);
|
||||
|
@ -254,7 +305,7 @@ class Path {
|
|||
}
|
||||
|
||||
is_waypoint(point_index) {
|
||||
return (this.waypoints[Math.floor(point_index / 8)] & (point_index % 8));
|
||||
return this.waypoints[Math.floor(point_index / 8)] & point_index % 8;
|
||||
}
|
||||
|
||||
// execute op on all segments.
|
||||
|
@ -296,17 +347,21 @@ class Path {
|
|||
// we are going to compute two min distances, one for each direction.
|
||||
let indices = [0, 0];
|
||||
let mins = [Number.MAX_VALUE, Number.MAX_VALUE];
|
||||
this.on_segments(function(p1, p2, i) {
|
||||
this.on_segments(
|
||||
function (p1, p2, i) {
|
||||
// we use the dot product to figure out if oriented correctly
|
||||
let distance = point.fake_distance_to_segment(p1, p2);
|
||||
let diff = p2.minus(p1);
|
||||
let dot = cos_direction * diff.lon + sin_direction * diff.lat;
|
||||
let orientation = + (dot < 0); // index 0 is good orientation
|
||||
let orientation = +(dot < 0); // index 0 is good orientation
|
||||
if (distance <= mins[orientation]) {
|
||||
mins[orientation] = distance;
|
||||
indices[orientation] = i - 1;
|
||||
}
|
||||
}, start, end);
|
||||
},
|
||||
start,
|
||||
end
|
||||
);
|
||||
// by default correct orientation (0) wins
|
||||
// but if other one is really closer, return other one
|
||||
if (mins[1] < mins[0] / 10.0) {
|
||||
|
@ -327,11 +382,13 @@ class Point {
|
|||
}
|
||||
coordinates(current_position, cos_direction, sin_direction) {
|
||||
let translated = this.minus(current_position).times(40000.0);
|
||||
let rotated_x = translated.lon * cos_direction - translated.lat * sin_direction;
|
||||
let rotated_y = translated.lon * sin_direction + translated.lat * cos_direction;
|
||||
let rotated_x =
|
||||
translated.lon * cos_direction - translated.lat * sin_direction;
|
||||
let rotated_y =
|
||||
translated.lon * sin_direction + translated.lat * cos_direction;
|
||||
return [
|
||||
g.getWidth() / 2 - Math.round(rotated_x), // x is inverted
|
||||
g.getHeight() / 2 + Math.round(rotated_y)
|
||||
g.getHeight() / 2 + Math.round(rotated_y),
|
||||
];
|
||||
}
|
||||
minus(other_point) {
|
||||
|
@ -344,7 +401,7 @@ class Point {
|
|||
}
|
||||
length_squared(other_point) {
|
||||
let d = this.minus(other_point);
|
||||
return (d.lon * d.lon + d.lat * d.lat);
|
||||
return d.lon * d.lon + d.lat * d.lat;
|
||||
}
|
||||
times(scalar) {
|
||||
return new Point(this.lon * scalar, this.lat * scalar);
|
||||
|
@ -355,14 +412,17 @@ class Point {
|
|||
distance(other_point) {
|
||||
//see https://www.movable-type.co.uk/scripts/latlong.html
|
||||
const R = 6371e3; // metres
|
||||
const phi1 = this.lat * Math.PI / 180;
|
||||
const phi2 = other_point.lat * Math.PI / 180;
|
||||
const deltaphi = (other_point.lat - this.lat) * Math.PI / 180;
|
||||
const deltalambda = (other_point.lon - this.lon) * Math.PI / 180;
|
||||
const phi1 = (this.lat * Math.PI) / 180;
|
||||
const phi2 = (other_point.lat * Math.PI) / 180;
|
||||
const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180;
|
||||
const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180;
|
||||
|
||||
const a = Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) +
|
||||
Math.cos(phi1) * Math.cos(phi2) *
|
||||
Math.sin(deltalambda / 2) * Math.sin(deltalambda / 2);
|
||||
const a =
|
||||
Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) +
|
||||
Math.cos(phi1) *
|
||||
Math.cos(phi2) *
|
||||
Math.sin(deltalambda / 2) *
|
||||
Math.sin(deltalambda / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c; // in metres
|
||||
|
@ -381,20 +441,18 @@ class Point {
|
|||
// We find projection of point p onto the line.
|
||||
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
|
||||
// We clamp t from [0,1] to handle points outside the segment vw.
|
||||
let t = Math.max(0, Math.min(1, (this.minus(v)).dot(w.minus(v)) / l2));
|
||||
let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2));
|
||||
|
||||
let projection = v.plus((w.minus(v)).times(t)); // Projection falls on the segment
|
||||
let projection = v.plus(w.minus(v).times(t)); // Projection falls on the segment
|
||||
return this.fake_distance(projection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
|
||||
let fake_gps_point = 0.0;
|
||||
function simulate_gps(status) {
|
||||
if (fake_gps_point > status.path.len -1) {
|
||||
if (fake_gps_point > status.path.len - 1) {
|
||||
return;
|
||||
}
|
||||
let point_index = Math.floor(fake_gps_point);
|
||||
|
@ -405,7 +463,7 @@ function simulate_gps(status) {
|
|||
let p2 = status.path.point(point_index + 1);
|
||||
|
||||
let alpha = fake_gps_point - point_index;
|
||||
let pos = p1.times(1-alpha).plus(p2.times(alpha));
|
||||
let pos = p1.times(1 - alpha).plus(p2.times(alpha));
|
||||
let old_pos = status.position;
|
||||
|
||||
fake_gps_point += 0.2; // advance simulation
|
||||
|
@ -415,18 +473,18 @@ function simulate_gps(status) {
|
|||
|
||||
function drawMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'choose trace' }
|
||||
"": { title: "choose trace" },
|
||||
};
|
||||
var files = require("Storage").list(".gpc");
|
||||
for (var i=0; i<files.length; ++i) {
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
menu[files[i]] = start.bind(null, files[i]);
|
||||
}
|
||||
menu['Exit'] = function() { load(); };
|
||||
menu["Exit"] = function () {
|
||||
load();
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function start(fn) {
|
||||
console.log("loading", fn);
|
||||
|
||||
|
@ -447,18 +505,18 @@ function start(fn) {
|
|||
|
||||
let frame = 0;
|
||||
let old_points = []; // remember the at most 3 previous points
|
||||
let set_coordinates = function(data) {
|
||||
let set_coordinates = function (data) {
|
||||
frame += 1;
|
||||
let valid_coordinates = !isNaN(data.lat) && !isNaN(data.lon);
|
||||
if (valid_coordinates) {
|
||||
// we try to figure out direction by looking at previous points
|
||||
// instead of the gps course which is not very nice.
|
||||
let direction = data.course * Math.PI / 180.0;
|
||||
let direction = (data.course * Math.PI) / 180.0;
|
||||
let position = new Point(data.lon, data.lat);
|
||||
if (old_points.length == 0) {
|
||||
old_points.push(position);
|
||||
} else {
|
||||
let last_point = old_points[old_points.length-1];
|
||||
let last_point = old_points[old_points.length - 1];
|
||||
if (last_point.x != position.x || last_point.y != position.y) {
|
||||
if (old_points.length == 4) {
|
||||
old_points.shift();
|
||||
|
@ -469,9 +527,9 @@ function start(fn) {
|
|||
if (old_points.length == 4) {
|
||||
// let's just take average angle of 3 previous segments
|
||||
let angles_sum = 0;
|
||||
for(let i = 0 ; i < 3 ; i++) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let p1 = old_points[i];
|
||||
let p2 = old_points[i+1];
|
||||
let p2 = old_points[i + 1];
|
||||
let diff = p2.minus(p1);
|
||||
let angle = Math.atan2(diff.lat, diff.lon);
|
||||
angles_sum += angle;
|
||||
|
@ -482,16 +540,18 @@ function start(fn) {
|
|||
}
|
||||
}
|
||||
let gps_status_color;
|
||||
if ((frame % 2 == 0)||valid_coordinates) {
|
||||
if (frame % 2 == 0 || valid_coordinates) {
|
||||
gps_status_color = g.theme.bg;
|
||||
} else {
|
||||
gps_status_color = g.theme.fg;
|
||||
}
|
||||
g.setColor(gps_status_color).setFont("6x8:2").drawString("gps", g.getWidth() - 40, 30);
|
||||
}
|
||||
g.setColor(gps_status_color)
|
||||
.setFont("6x8:2")
|
||||
.drawString("gps", g.getWidth() - 40, 30);
|
||||
};
|
||||
|
||||
Bangle.setGPSPower(true, "gipy");
|
||||
Bangle.on('GPS', set_coordinates);
|
||||
Bangle.on("GPS", set_coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,4 +565,3 @@ if (files.length <= 1) {
|
|||
} else {
|
||||
drawMenu();
|
||||
}
|
||||
|
||||
|
|
|
@ -650,6 +650,7 @@ fn position_interests_along_path(
|
|||
#[tokio::main]
|
||||
async fn main() {
|
||||
let input_file = std::env::args().nth(1).unwrap_or("m.gpx".to_string());
|
||||
let osm_file = std::env::args().nth(2);
|
||||
eprintln!("input is {}", input_file);
|
||||
let mut segmented_points = points(&input_file);
|
||||
let p = segmented_points
|
||||
|
@ -675,7 +676,11 @@ async fn main() {
|
|||
}
|
||||
rp.extend(segmented_points.last().and_then(|l| l.last()).copied());
|
||||
|
||||
let mut interests = parse_osm_data("ardeche.osm.pbf");
|
||||
let mut interests = if let Some(osm) = osm_file {
|
||||
parse_osm_data(osm)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// let mut interests = parse_osm_data("isere.osm.pbf");
|
||||
let buckets = position_interests_along_path(&mut interests, &rp, 0.001, 5, 3);
|
||||
// let i = get_openstreetmap_data(&rp).await;
|
||||
|
|
Loading…
Reference in New Issue