diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index e832918ea..2835488e2 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -117,7 +117,8 @@ * Bugfix in nearest segment detection 0.23: - * New display algorithm : way faster, larger roads + * New display algorithm : way faster, larger roads, zooming out is now ok + * You will need to re-generate your maps because the new algorithm uses a different scale * Better path simplification * Waypoints autodetection using the map * New option: sleep between waypoints diff --git a/apps/gipy/TODO b/apps/gipy/TODO index d6ee7fb4e..e5fe68240 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -1,6 +1,7 @@ urgent TODO: +- prefetch tiles ? - update documentation to reflect new display ? - add an image for the arrow ? diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 1b0fee805..69d5e2a94 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -7,11 +7,11 @@ let status; let initial_options = Bangle.getOptions(); let interests_colors = [ - [1,1,1], // Waypoints, white - [1,0,0], // Bakery, red - [0,0,1], // DrinkingWater, blue - [0,1,1], // Toilets, cyan - [0,1,0], // Artwork, green + [1, 1, 1], // Waypoints, white + [1, 0, 0], // Bakery, red + [0, 0, 1], // DrinkingWater, blue + [0, 1, 1], // Toilets, cyan + [0, 1, 0], // Artwork, green ]; let Y_OFFSET = 20; @@ -43,13 +43,13 @@ let powersaving = settings.powersave_by_default; // let profile_start_times = []; // function start_profiling() { -// profile_start_times.push(getTime()); +// profile_start_times.push(getTime()); // } // function end_profiling(label) { -// let end_time = getTime(); -// let elapsed = end_time - profile_start_times.pop(); -// console.log("profile:", label, "took", elapsed); +// let end_time = getTime(); +// let elapsed = end_time - profile_start_times.pop(); +// console.log("profile:", label, "took", elapsed); // } // return the index of the largest element of the array which is <= x @@ -152,29 +152,8 @@ class TilesOffsets { } } -// this function is not inlined to avoid array declaration in jit -function center_points(points, scaled_current_x, scaled_current_y) { - return g.transformVertices(points, [ - 1, - 0, - 0, - 1, - -scaled_current_x, - -scaled_current_y, - ]); -} - -// this function is not inlined to avoid array declaration in jit -function rotate_points(points, c, s) { - let center_x = g.getWidth() / 2; - let center_y = g.getHeight() / 2 + Y_OFFSET; - - return g.transformVertices(points, [-c, s, s, c, center_x, center_y]); -} - class Map { constructor(buffer, offset, filename) { - this.points_cache = []; // don't refetch points all the time // header let color_array = Uint8Array(buffer, offset, 3); this.color = [ @@ -338,7 +317,6 @@ class Interests { this.binary_interests[i] = binary_interests[i]; } offset += end; - this.points_cache = []; return [this, offset]; } @@ -388,7 +366,7 @@ class Status { this.reaching = null; // which waypoint are we reaching ? this.distance_to_next_point = null; // how far are we from next point ? this.projected_point = null; - this.images_cache = []; + this.reset_images_cache(); let width = g.getWidth(); let height = g.getHeight(); @@ -415,6 +393,28 @@ class Status { this.old_points = []; // record previous points but only when enough distance between them this.old_times = []; // the corresponding times } + reset_images_cache() { + let tiles_per_diagonals = this.zoomed_in ? 3 : 5; + let screen_width = g.getWidth(); + let screen_height = g.getHeight(); + this.images_cache = []; + + let img_side = + Math.ceil( + Math.sqrt(screen_width * screen_width + screen_height * screen_height) / + tiles_per_diagonals + ) + 6; // three extra pixels on each side to allow thick lines + + E.defrag(); + let limit = tiles_per_diagonals * (tiles_per_diagonals + 1); + + for (let i = 0; i < limit; i++) { + let img = Graphics.createArrayBuffer(img_side, img_side, 4, { + msb: true, + }); + this.images_cache.push({ image: img, x: -1, y: -1 }); + } + } activate() { if (!powersaving) { return; @@ -472,11 +472,14 @@ class Status { let oldest_point = this.old_points[0]; let distance_to_oldest = oldest_point.distance(position); + let time_to_oldest = now - this.old_times[0]; // every 3 points we count the distance if (this.gps_coordinates_counter % 3 == 0) { - if (distance_to_oldest < 150.0) { - // to avoid gps glitches + if (time_to_oldest > 6 || distance_to_oldest < 150.0) { + // to avoid gps glitches (sometimes the gps signal will make you jump around) + // however if gps disconnects (time_to_oldest becomes large) we still count the distance + // when it re-activates this.advanced_distance += distance_to_oldest; } } @@ -711,44 +714,75 @@ class Status { } tile_image(absolute_tile_x, absolute_tile_y) { - let cached_img = this.images_cache.find((i) => { + // in the cache old images are front and recently used ones are back + let cached_img_index = this.images_cache.findIndex((i) => { return i.x == absolute_tile_x && i.y == absolute_tile_y; }); - if (cached_img === undefined) { - let img = this.compute_tile_image(absolute_tile_x, absolute_tile_y); - let limit = this.zoomed_in ? 12 : 30; - if (this.images_cache.length > limit) { - this.images_cache.shift(); - } + if (cached_img_index == -1) { + // console.log("loading", absolute_tile_x, absolute_tile_y); + let old_image = this.images_cache.shift(); + this.compute_tile_image(old_image.image, absolute_tile_x, absolute_tile_y); this.images_cache.push({ - image: img, + image: old_image.image, x: absolute_tile_x, y: absolute_tile_y, }); - return img; + return old_image.image; } else { + let cached_img = this.images_cache.splice(cached_img_index, 1)[0]; + this.images_cache.push(cached_img); return cached_img.image; } } - compute_tile_image(absolute_tile_x, absolute_tile_y) { - let screen_width = g.getWidth(); - let screen_height = g.getHeight(); - let tiles_per_diagonals = this.zoomed_in ? 3 : 5; - let img_side = Math.ceil( - Math.sqrt(screen_width * screen_width + screen_height * screen_height) / - tiles_per_diagonals - ) + 6; // three extra pixels on each side to allow thick lines - - let img = Graphics.createArrayBuffer(img_side, img_side, 4, { msb: true }); - img.transparent = img.toColor(1,1,1); - img.setBgColor(1,1,1).clear(); + compute_tile_image(img, absolute_tile_x, absolute_tile_y) { + img.transparent = img.toColor(1, 1, 1); + img.setBgColor(1, 1, 1).clear(); this.maps.forEach((m) => { m.add_to_tile_image(img, absolute_tile_x, absolute_tile_y); }); this.interests.add_to_tile_image(img, absolute_tile_x, absolute_tile_y); - return img; + } + + display_map() { + + // start_profiling(); + let displayed_x = this.displayed_position.lon; + let displayed_y = this.displayed_position.lat; + let tile_x_coord = displayed_x / this.maps[0].side; + let tile_y_coord = displayed_y / this.maps[0].side; + let absolute_tile_x = Math.floor(tile_x_coord); + let absolute_tile_y = Math.floor(tile_y_coord); + + let tiles_per_diagonals = this.zoomed_in ? 3 : 5; + let diagonal = Math.ceil( + Math.sqrt(g.getWidth() * g.getWidth() + g.getHeight() * g.getHeight()) / + tiles_per_diagonals + ); + let angle = this.direction - Math.PI / 2; + let cos_direction = Math.cos(angle); + let sin_direction = Math.sin(angle); + let d = Math.floor(tiles_per_diagonals / 2); + + for (let x = -d; x <= d; x++) { + for (let y = -d; y <= d; y++) { + let img = this.tile_image(absolute_tile_x + x, absolute_tile_y + y); + + let screen_x = + (absolute_tile_x + x + 0.5 - tile_x_coord) * diagonal + 3; + let screen_y = + -(absolute_tile_y + y + 0.5 - tile_y_coord) * diagonal - 3; + + let rotated_x = screen_x * cos_direction - screen_y * sin_direction; + let rotated_y = screen_x * sin_direction + screen_y * cos_direction; + let final_x = g.getWidth() / 2 + rotated_x; + let final_y = g.getHeight() / 2 + Y_OFFSET + rotated_y; + + g.drawImage(img, final_x, final_y, { rotate: angle }); + } + } + // end_profiling("map display"); } display() { if (displaying || in_menu) { @@ -758,46 +792,17 @@ class Status { displaying = true; g.clear(); if (this.screen == MAP) { - - let displayed_x = this.displayed_position.lon; - let displayed_y = this.displayed_position.lat; - let tile_x_coord = displayed_x / this.maps[0].side; - let tile_y_coord = displayed_y / this.maps[0].side; - let absolute_tile_x = Math.floor(tile_x_coord); - let absolute_tile_y = Math.floor(tile_y_coord); - - let tiles_per_diagonals = this.zoomed_in ? 3 : 5; - let diagonal = Math.ceil( - Math.sqrt(g.getWidth() * g.getWidth() + g.getHeight() * g.getHeight()) / - tiles_per_diagonals - ); - let angle = this.direction - Math.PI / 2; - let cos_direction = Math.cos(angle); - let sin_direction = Math.sin(angle); - let d = Math.floor(tiles_per_diagonals / 2); - - for (let x = -d; x <= d; x++) { - for (let y = -d; y <= d; y++) { - let img = this.tile_image(absolute_tile_x + x, absolute_tile_y + y); - - let screen_x = (absolute_tile_x + x + 0.5 - tile_x_coord) * diagonal + 3; - let screen_y = -(absolute_tile_y + y + 0.5 - tile_y_coord) * diagonal - 3; - - let rotated_x = screen_x * cos_direction - screen_y * sin_direction; - let rotated_y = screen_x * sin_direction + screen_y * cos_direction; - let final_x = g.getWidth() / 2 + rotated_x; - let final_y = g.getHeight() / 2 + Y_OFFSET + rotated_y; - - g.drawImage(img, final_x, final_y, { rotate: angle }); - } - } - + this.display_map(); if (this.position !== null) { + // start_profiling(); this.display_path(); + // end_profiling("path display"); } + // start_profiling(); this.display_direction(); this.display_stats(); + // end_profiling("direction and stats display"); } else { let current_position = 0; if (this.current_segment !== null) { @@ -1189,8 +1194,6 @@ function load_gps(filename) { class Path { constructor(buffer, offset) { - // let p = Uint16Array(buffer, offset, 1); - // console.log(p); let points_number = Uint16Array(buffer, offset, 1)[0]; offset += 2; @@ -1408,7 +1411,7 @@ function start_gipy(path, maps, interests, heights) { value: status.zoomed_in, format: (v) => (v ? "In" : "Out"), onchange: (v) => { - status.images_cache = []; + status.reset_images_cache(); status.zoomed_in = v; }, }, @@ -1486,51 +1489,52 @@ function start_gipy(path, maps, interests, heights) { }); if (simulated) { - status.starting_time = getTime(); - // let's keep the screen on in simulations - Bangle.setLCDTimeout(0); - Bangle.setLCDPower(1); - Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen + // status.starting_time = getTime(); + // // let's keep the screen on in simulations + // Bangle.setLCDTimeout(0); + // Bangle.setLCDPower(1); + // Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen - function simulate_gps(status) { - if (status.path === null) { - let map = status.maps[0]; - let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); - let p2 = new Point( - map.start_coordinates[0] + map.side * map.grid_size[0], - map.start_coordinates[1] + map.side * map.grid_size[1] - ); - let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); - if (fake_gps_point < 1) { - fake_gps_point += 0.05; - } - status.update_position(pos); - } else { - if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { - return; - } - let point_index = Math.floor(fake_gps_point); - if (point_index >= status.path.len / 2 - 1) { - return; - } - let p1 = status.path.point(2 * point_index); // use these to approximately follow path - let p2 = status.path.point(2 * (point_index + 1)); - //let p1 = status.path.point(point_index); // use these to strictly follow path - //let p2 = status.path.point(point_index + 1); + // function simulate_gps(status) { + // if (status.path === null) { + // let map = status.maps[0]; + // let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); + // let p2 = new Point( + // map.start_coordinates[0] + map.side * map.grid_size[0], + // map.start_coordinates[1] + map.side * map.grid_size[1] + // ); + // let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); + // if (fake_gps_point < 1) { + // fake_gps_point += 0.05; + // } + // status.update_position(pos); + // } else { + // if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { + // return; + // } + // let point_index = Math.floor(fake_gps_point); + // if (point_index >= status.path.len / 2 - 1) { + // return; + // } + // let p1 = status.path.point(2 * point_index); // use these to approximately follow path + // let p2 = status.path.point(2 * (point_index + 1)); + // //let p1 = status.path.point(point_index); // use these to strictly follow path + // //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 alpha = fake_gps_point - point_index; + // let pos = p1.times(1 - alpha).plus(p2.times(alpha)); - if (go_backwards) { - fake_gps_point -= 0.2; // advance simulation - } else { - fake_gps_point += 0.2; // advance simulation - } - status.update_position(pos); - } - } + // if (go_backwards) { + // fake_gps_point -= 0.2; // advance simulation + // } else { + // fake_gps_point += 0.2; // advance simulation + // } + // console.log(fake_gps_point); + // status.update_position(pos); + // } + // } - setInterval(simulate_gps, 500, status); + // setInterval(simulate_gps, 500, status); } else { status.activate();