1
0
Fork 0

Merge branch 'master' of github.com:espruino/BangleApps

master
Gordon Williams 2022-11-11 15:02:52 +00:00
commit f51899ecb1
18 changed files with 1977 additions and 1 deletions

View File

@ -3,4 +3,5 @@ apps/banglerun/rollup.config.js
apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js
apps/qrcode/qr-scanner.umd.min.js
apps/gipy/pkg/gpconv.js
*.test.js

65
apps/gipy/ChangeLog Normal file
View File

@ -0,0 +1,65 @@
0.01: Initial code
0.05:
* We now buzz before reaching a waypoint.
* Display is only updated when not locked.
* We detect leaving path and finding path again.
* We display remaining distance to next point.
0.06:
* Special display for points with steep turns.
* Buzz on points with steep turns and unlock.
* Losing gps is now displayed.
0.07:
* We now use orientation to detect current segment
when segments overlap going in both directions.
* File format is now versioned.
0.08:
* Don't use gps course anymore but figure it from previous positions.
* Bugfix: path colors are back.
* Always buzz when reaching waypoint even if unlocked.
0.09:
* We now display interest points.
* Menu to choose which file to load.
0.10:
* Display performances enhancement.
* Waypoints information is embedded in file and extracted from comments on
points.
* Bugfix in map display (last segment was missing + wrong colors).
* Waypoint detections using OSM + sharp angles
* New algorith for direction detection
0.11:
* Better fonts (more free space, still readable).
* Display direction to nearest point when lost.
* Display average speed.
* Turn off gps when locked and between points
0.12:
* Bugfix in speed computation.
* Bugfix in current segment detection.
* Bugfix : lost direction.
* Larger fonts.
* Detecting next point correctly when going back.
0.13:
* Bugfix in lost direction.
* Buzzing 100m ahead instead of 50m.
* Detect sharp turns.
* Display instant speed.
* New instant speed algorithm.
* Bugfix for remaining distance when going back.
0.14:
* Detect starting distance to compute a good average speed.
* Settings
* Account for breaks in average speed.
0.15:
* Record traveled distance to get a good average speed.
* Breaks (low speed) will not count in average speed.
* Bugfix in average speed.

109
apps/gipy/README.md Normal file
View File

@ -0,0 +1,109 @@
# Gipy
Gipy allows you to follow gpx traces on your watch.
![Screenshot](screenshot1.png)
It is for now meant for bicycling and not hiking
(it uses your movement to figure out your orientation
and walking is too slow).
It is untested on Banglejs1. If you can try it, you would be welcome.
This software is not perfect but surprisingly useful.
## Features
It provides the following features :
- display the path with current position from gps
- detects and buzzes if you leave the path
- buzzes before sharp turns
- buzzes before nodes with comments
(for example when you need to turn in https://mapstogpx.com/)
- display instant / average speed
- display distance to next node
- display additional data from openstreetmap :
- water points
- toilets
- artwork
- bakeries
optionally it can also:
- try to turn off gps between crossroads to save battery
## Usage
### Preparing the file
You first need to have a trace file in *gpx* format.
Usually I download from [komoot](https://www.komoot.com/) or I export
from google maps using [mapstogpx](https://mapstogpx.com/).
Note that *mapstogpx* has a super nice feature in its advanced settings.
You can turn on 'next turn info' and be warned by the watch when you need to turn.
Once you have your gpx file you need to convert it to *gpc* which is my custom file format.
They are smaller than gpx and reduce the number of computations left to be done on the watch.
Just click the disk icon and select your gpx file.
This will request additional information from openstreetmap.
Your path will be displayed in svg.
### Starting Gipy
Once you start gipy you will have a menu for selecting your trace (if more than one).
Choose the one you want and here you go :
![Screenshot](screenshot2.png)
On your screen you can see :
- yourself (the big black dot)
- the path (the top of the screen is in front of you)
- if needed a projection of yourself on the path (small black dot)
- extremities of segments as white dots
- turning points as doubled white dots
- some text on the left (from top to bottom) :
* current time
* left distance till end of current segment
* distance from start of path / path length
* average speed / instant speed
- interest points from openstreetmap as color dots :
* red : bakery
* deep blue : water point
* cyan : toilets (often doubles as water point)
* green : artwork
- a *turn* indicator on the top right when you reach a turning point
- a *gps* indicator (blinking) on the top right if you lose gps signal
- a *lost* indicator on the top right if you stray too far away from path
- a black segment extending from you when you are lost, indicating the rough direction of where to go
### Settings
Few settings for now (feel free to suggest me more) :
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
- max speed : used to compute how long to turn the gps off
### Caveats
It is good to use but you should know :
- the gps might take a long time to start initially (see the assisted gps update app).
- gps signal is noisy : there is therefore a small delay for instant speed. sometimes you may jump somewhere else.
- your gpx trace has been decimated and approximated : the **REAL PATH** might be **A FEW METERS AWAY**
- sometimes the watch will tell you that you are lost but you are in fact on the path.
- battery saving by turning off gps is not very well tested (disabled by default).
- buzzing does not always work: when there is a high load on the watch, the buzzes might just never happen :-(.
- buzzes are not strong enough to be always easily noticed.
- be careful when **GOING DOWNHILL AT VERY HIGH SPEED**. I already missed a few turning points and by the time I realized it,
I had to go back uphill by quite a distance.
## Creator
Feel free to give me feedback : is it useful for you ? what other features would you like ?
frederic.wagner@imag.fr

25
apps/gipy/TODO Normal file
View File

@ -0,0 +1,25 @@
* bugs
- when exactly on turn, distance to next point is still often 50m
-----> it does not buzz very often on turns
- when going backwards we have a tendencing to get a wrong current_segment
* additional features
- config screen
- are we on foot (and should use compass)
- we need to buzz 200m before sharp turns (or even better, 30seconds)
(and look at more than next point)
- display distance to next water/toilet ?
- dynamic map rescale
- display scale (100m)
- compress path ?
* misc
- code is becoming messy

1
apps/gipy/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBiIA/AE8VqoAGCy1RiN3CyYuBi93uIXJIBV3AAIuMBY4XjQ5YXPRAIAEOwIABPBC4LF54wGF6IwFC5jWGIwxIJC4xJFgDuJJAxJFC6TEIJBzEHGCIYPGA5JQC44YPGBBJKY4gwRfQL4DGCL4GGCAXPGAxGBAAJIMGAwWCGCoWGC55HHJB5HIC8pGDSChfXC5AWIL5ynOC45GJC4h3IIyYwCFxwADgB1SC44uSC4guSAH4Ab"))

766
apps/gipy/app.js Normal file
View File

@ -0,0 +1,766 @@
let simulated = false;
let file_version = 3;
let code_key = 47490;
var settings = Object.assign(
{
keep_gps_alive: true,
max_speed: 35,
},
require("Storage").readJSON("gipy.json", true) || {}
);
let interests_colors = [
0xf800, // Bakery, red
0x001f, // DrinkingWater, blue
0x07ff, // Toilets, cyan
0x07e0, // Artwork, green
];
function binary_search(array, x) {
let start = 0,
end = array.length - 1;
while (start <= end) {
let mid = Math.floor((start + end) / 2);
if (array[mid] < x) start = mid + 1;
else end = mid - 1;
}
return start;
}
class Status {
constructor(path) {
this.path = path;
this.on_path = false; // are we on the path or lost ?
this.position = null; // where we are
this.adjusted_cos_direction = null; // cos of where we look at
this.adjusted_sin_direction = null; // sin of where we look at
this.current_segment = null; // which segment is closest
this.reaching = null; // which waypoint are we reaching ?
this.distance_to_next_point = null; // how far are we from next point ?
this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
this.paused_since = getTime();
let r = [0];
// let's do a reversed prefix computations on all distances:
// loop on all segments in reversed order
let previous_point = null;
for (let i = this.path.len - 1; i >= 0; i--) {
let point = this.path.point(i);
if (previous_point !== null) {
r.unshift(r[0] + point.distance(previous_point));
}
previous_point = point;
}
this.remaining_distances = r; // how much distance remains at start of each segment
this.starting_time = this.paused_since; // time we start
this.advanced_distance = 0.0;
this.gps_coordinates_counter = 0; // how many coordinates did we receive
this.old_points = [];
this.old_times = [];
}
new_position_reached(position) {
// we try to figure out direction by looking at previous points
// instead of the gps course which is not very nice.
this.gps_coordinates_counter += 1;
let now = getTime();
this.old_points.push(position);
this.old_times.push(now);
if (this.old_points.length == 1) {
return null;
}
let last_point = this.old_points[this.old_points.length - 1];
let oldest_point = this.old_points[0];
// every 7 points we count the distance
if (this.gps_coordinates_counter % 7 == 0) {
let distance = last_point.distance(oldest_point);
if (distance < 150.0) {
// to avoid gps glitches
this.advanced_distance += distance;
}
}
if (this.old_points.length == 8) {
let p1 = this.old_points[0]
.plus(this.old_points[1])
.plus(this.old_points[2])
.plus(this.old_points[3])
.times(1 / 4);
let p2 = this.old_points[4]
.plus(this.old_points[5])
.plus(this.old_points[6])
.plus(this.old_points[7])
.times(1 / 4);
let t1 = (this.old_times[1] + this.old_times[2]) / 2;
let t2 = (this.old_times[5] + this.old_times[6]) / 2;
this.instant_speed = p1.distance(p2) / (t2 - t1);
this.old_points.shift();
this.old_times.shift();
} else {
this.instant_speed =
oldest_point.distance(last_point) / (now - this.old_times[0]);
// update paused time if we are too slow
if (this.instant_speed < 2) {
if (this.paused_since === null) {
this.paused_since = now;
}
} else {
if (this.paused_since !== null) {
this.paused_time += now - this.paused_since;
this.paused_since = null;
}
}
}
// let's just take angle of segment between newest point and a point a bit before
let previous_index = this.old_points.length - 3;
if (previous_index < 0) {
previous_index = 0;
}
let diff = position.minus(this.old_points[previous_index]);
let angle = Math.atan2(diff.lat, diff.lon);
return angle;
}
update_position(new_position, maybe_direction) {
let direction = this.new_position_reached(new_position);
if (direction === null) {
if (maybe_direction === null) {
return;
} else {
direction = maybe_direction;
}
}
this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0);
this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0);
cos_direction = Math.cos(direction);
sin_direction = Math.sin(direction);
this.position = new_position;
// detect segment we are on now
let res = this.path.nearest_segment(
this.position,
Math.max(0, this.current_segment - 1),
Math.min(this.current_segment + 2, this.path.len - 1),
cos_direction,
sin_direction
);
let orientation = res[0];
let next_segment = res[1];
if (this.is_lost(next_segment)) {
// it did not work, try anywhere
res = this.path.nearest_segment(
this.position,
0,
this.path.len - 1,
cos_direction,
sin_direction
);
orientation = res[0];
next_segment = res[1];
}
// 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 (lost) {
Bangle.buzz(); // we lost path
setTimeout(() => Bangle.buzz(), 500);
setTimeout(() => Bangle.buzz(), 1000);
setTimeout(() => Bangle.buzz(), 1500);
}
this.on_path = !lost;
}
this.current_segment = next_segment;
// check if we are nearing the next point on our path and alert the user
let next_point = this.current_segment + (1 - orientation);
this.distance_to_next_point = Math.ceil(
this.position.distance(this.path.point(next_point))
);
// disable gps when far from next point and locked
if (Bangle.isLocked() && !settings.keep_gps_alive) {
let time_to_next_point =
(this.distance_to_next_point * 3.6) / settings.max_speed;
if (time_to_next_point > 60) {
Bangle.setGPSPower(false, "gipy");
setTimeout(function () {
Bangle.setGPSPower(true, "gipy");
}, time_to_next_point);
}
}
if (this.reaching != next_point && this.distance_to_next_point <= 100) {
this.reaching = next_point;
let reaching_waypoint = this.path.is_waypoint(next_point);
if (reaching_waypoint) {
Bangle.buzz();
setTimeout(() => Bangle.buzz(), 500);
setTimeout(() => Bangle.buzz(), 1000);
setTimeout(() => Bangle.buzz(), 1500);
if (Bangle.isLocked()) {
Bangle.setLocked(false);
}
}
}
// re-display
this.display(orientation);
}
remaining_distance(orientation) {
let remaining_in_correct_orientation =
this.remaining_distances[this.current_segment + 1] +
this.position.distance(this.path.point(this.current_segment + 1));
if (orientation == 0) {
return remaining_in_correct_orientation;
} else {
return this.remaining_distances[0] - remaining_in_correct_orientation;
}
}
is_lost(segment) {
let distance_to_nearest = this.position.distance_to_segment(
this.path.point(segment),
this.path.point(segment + 1)
);
return distance_to_nearest > 50;
}
display(orientation) {
g.clear();
this.display_map();
this.display_interest_points();
this.display_stats(orientation);
Bangle.drawWidgets();
}
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
);
// 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 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.adjusted_cos_direction,
this.adjusted_sin_direction
);
g.setColor(color).fillCircle(c[0], c[1], 5);
}
}
display_stats(orientation) {
let remaining_distance = this.remaining_distance(orientation);
let rounded_distance = Math.round(remaining_distance / 100) / 10;
let total = Math.round(this.remaining_distances[0] / 100) / 10;
let now = new Date();
let minutes = now.getMinutes().toString();
if (minutes.length < 2) {
minutes = "0" + minutes;
}
let hours = now.getHours().toString();
g.setFont("6x8:2")
.setFontAlign(-1, -1, 0)
.setColor(g.theme.fg)
.drawString(hours + ":" + minutes, 0, 30);
g.setFont("6x8:2").drawString(
"" + this.distance_to_next_point + "m",
0,
g.getHeight() - 49
);
let point_time = this.old_times[this.old_times.length - 1];
let done_in = point_time - this.starting_time - this.paused_time;
let approximate_speed = Math.round(
(this.advanced_distance * 3.6) / done_in
);
let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
g.setFont("6x8:2")
.setFontAlign(-1, -1, 0)
.drawString(
"" + approximate_speed + "km/h (in." + approximate_instant_speed + ")",
0,
g.getHeight() - 15
);
g.setFont("6x8:2").drawString(
"" + rounded_distance + "/" + total,
0,
g.getHeight() - 32
);
if (this.distance_to_next_point <= 100) {
if (this.path.is_waypoint(this.reaching)) {
g.setColor(0.0, 1.0, 0.0)
.setFont("6x15")
.drawString("turn", g.getWidth() - 50, 30);
}
}
if (!this.on_path) {
g.setColor(1.0, 0.0, 0.0)
.setFont("6x15")
.drawString("lost", g.getWidth() - 55, 35);
}
}
display_map() {
// don't display all segments, only those neighbouring current segment
// this is most likely to be the correct display
// while lowering the cost a lot
//
// note that all code is inlined here to speed things up from 400ms to 200ms
let start = Math.max(this.current_segment - 4, 0);
let end = Math.min(this.current_segment + 6, this.path.len);
let pos = this.position;
let cos = this.adjusted_cos_direction;
let sin = this.adjusted_sin_direction;
let points = this.path.points;
let cx = pos.lon;
let cy = pos.lat;
let half_width = g.getWidth() / 2;
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;
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
let y = half_height + Math.round(rotated_y);
if (previous_x !== null) {
if (i == this.current_segment + 1) {
g.setColor(0.0, 1.0, 0.0);
} else {
g.setColor(1.0, 0.0, 0.0);
}
g.drawLine(previous_x, previous_y, x, y);
if (this.path.is_waypoint(i - 1)) {
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 6);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 5);
}
g.setColor(g.theme.fg);
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)) {
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 6);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 5);
}
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 4);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 3);
// now display ourselves
g.setColor(g.theme.fgH);
g.fillCircle(half_width, half_height, 5);
// display old points for direction debug
// for (let i = 0; i < this.old_points.length; i++) {
// let tx = (this.old_points[i].lon - cx) * 40000.0;
// let ty = (this.old_points[i].lat - 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
// let y = half_height + Math.round(rotated_y);
// g.setColor((i + 1) / 4.0, 0.0, 0.0);
// g.fillCircle(x, y, 3);
// }
// display current-segment's projection for debug
let projection = pos.closest_segment_point(
this.path.point(this.current_segment),
this.path.point(this.current_segment + 1)
);
let tx = (projection.lon - cx) * 40000.0;
let ty = (projection.lat - 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
let y = half_height + Math.round(rotated_y);
g.setColor(g.theme.fg);
g.fillCircle(x, y, 4);
// display direction to next point if lost
if (!this.on_path) {
let next_point = this.path.point(this.current_segment + 1);
let diff = next_point.minus(this.position);
let angle = Math.atan2(diff.lat, diff.lon);
let tx = Math.cos(angle) * 50.0;
let ty = Math.sin(angle) * 50.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
let y = half_height + Math.round(rotated_y);
g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
}
}
}
function load_gpc(filename) {
let buffer = require("Storage").readArrayBuffer(filename);
let offset = 0;
// header
let header = Uint16Array(buffer, offset, 5);
offset += 5 * 2;
let key = header[0];
let version = header[1];
let points_number = header[2];
if (key != code_key || version > file_version) {
E.showMessage("Invalid gpc file");
load();
}
// path points
let points = Float64Array(buffer, offset, points_number * 2);
offset += 8 * points_number * 2;
// path waypoints
let waypoints_len = Math.ceil(points_number / 8.0);
let waypoints = Uint8Array(buffer, offset, waypoints_len);
offset += waypoints_len;
// interest points
let interests_number = header[3];
let interests_coordinates = Float64Array(
buffer,
offset,
interests_number * 2
);
offset += 8 * interests_number * 2;
let interests_types = Uint8Array(buffer, offset, interests_number);
offset += interests_number;
// interests on path
let interests_on_path_number = header[4];
let 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);
let interests_starts = Uint16Array(buffer, offset, starts_length);
offset += 2 * starts_length;
return [
points,
waypoints,
interests_coordinates,
interests_types,
interests_on_path,
interests_starts,
];
}
class Path {
constructor(arrays) {
this.points = arrays[0];
this.waypoints = arrays[1];
this.interests_coordinates = arrays[2];
this.interests_types = arrays[3];
this.interests_on_path = arrays[4];
this.interests_starts = arrays[5];
}
is_waypoint(point_index) {
let i = Math.floor(point_index / 8);
let subindex = point_index % 8;
let r = this.waypoints[i] & (1 << subindex);
return r != 0;
}
// execute op on all segments.
// start is index of first wanted segment
// end is 1 after index of last wanted segment
on_segments(op, start, end) {
let previous_point = null;
for (let i = start; i < end + 1; i++) {
let point = new Point(this.points[2 * i], this.points[2 * i + 1]);
if (previous_point !== null) {
op(previous_point, point, i);
}
previous_point = point;
}
}
// return point at given index
point(index) {
let lon = this.points[2 * index];
let lat = this.points[2 * index + 1];
return new Point(lon, lat);
}
interest_point(index) {
let lon = this.interests_coordinates[2 * index];
let lat = this.interests_coordinates[2 * index + 1];
return new Point(lon, lat);
}
interest_color(index) {
return interests_colors[this.interests_types[index]];
}
// return index of segment which is nearest from point.
// we need a direction because we need there is an ambiguity
// for overlapping segments which are taken once to go and once to come back.
// (in the other direction).
nearest_segment(point, start, end, cos_direction, sin_direction) {
// 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) {
// we use the dot product to figure out if oriented correctly
// let distance = point.fake_distance_to_segment(p1, p2);
let projection = point.closest_segment_point(p1, p2);
let distance = point.fake_distance(projection);
// let d = projection.minus(point).times(40000.0);
// let rotated_x = d.lon * acos - d.lat * asin;
// let rotated_y = d.lon * asin + d.lat * acos;
// let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted
// let y = g.getHeight() / 2 + Math.round(rotated_y);
//
let diff = p2.minus(p1);
let dot = cos_direction * diff.lon + sin_direction * diff.lat;
let orientation = +(dot < 0); // index 0 is good orientation
// g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle(
// x,
// y,
// 10
// );
if (distance <= mins[orientation]) {
mins[orientation] = distance;
indices[orientation] = i - 1;
}
},
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) {
return [1, indices[1]];
} else {
return [0, indices[0]];
}
}
get len() {
return this.points.length / 2;
}
}
class Point {
constructor(lon, lat) {
this.lon = lon;
this.lat = lat;
}
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;
return [
g.getWidth() / 2 - Math.round(rotated_x), // x is inverted
g.getHeight() / 2 + Math.round(rotated_y),
];
}
minus(other_point) {
let xdiff = this.lon - other_point.lon;
let ydiff = this.lat - other_point.lat;
return new Point(xdiff, ydiff);
}
plus(other_point) {
return new Point(this.lon + other_point.lon, this.lat + other_point.lat);
}
length_squared(other_point) {
let d = this.minus(other_point);
return d.lon * d.lon + d.lat * d.lat;
}
times(scalar) {
return new Point(this.lon * scalar, this.lat * scalar);
}
dot(other_point) {
return this.lon * other_point.lon + this.lat * other_point.lat;
}
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 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 meters
}
fake_distance(other_point) {
return Math.sqrt(this.length_squared(other_point));
}
closest_segment_point(v, w) {
// from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// Return minimum distance between line segment vw and point p
let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt
if (l2 == 0.0) {
return v; // v == w case
}
// Consider the line extending the segment, parameterized as v + t (w - v).
// 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));
return v.plus(w.minus(v).times(t)); // Projection falls on the segment
}
distance_to_segment(v, w) {
let projection = this.closest_segment_point(v, w);
return this.distance(projection);
}
fake_distance_to_segment(v, w) {
let projection = this.closest_segment_point(v, w);
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) {
return;
}
let point_index = Math.floor(fake_gps_point);
if (point_index >= status.path.len) {
return;
}
//let p1 = status.path.point(0);
//let n = status.path.len;
//let p2 = status.path.point(n - 1);
let p1 = status.path.point(point_index);
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 old_pos = status.position;
fake_gps_point += 0.05; // advance simulation
status.update_position(pos, null);
}
function drawMenu() {
const menu = {
"": { title: "choose trace" },
};
var files = require("Storage").list(".gpc");
for (var i = 0; i < files.length; ++i) {
menu[files[i]] = start.bind(null, files[i]);
}
menu["Exit"] = function () {
load();
};
E.showMenu(menu);
}
function start(fn) {
E.showMenu();
console.log("loading", fn);
// let path = new Path(load_gpx("test.gpx"));
let path = new Path(load_gpc(fn));
let status = new Status(path);
if (simulated) {
status.position = new Point(status.path.point(0));
setInterval(simulate_gps, 500, status);
} else {
// let's display start while waiting for gps signal
let p1 = status.path.point(0);
let p2 = status.path.point(1);
let diff = p2.minus(p1);
let direction = Math.atan2(diff.lat, diff.lon);
Bangle.setLocked(false);
status.update_position(p1, direction);
let frame = 0;
let set_coordinates = function (data) {
frame += 1;
// 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere
let valid_coordinates =
!isNaN(data.lat) &&
!isNaN(data.lon) &&
(data.lat != 0.0 || data.lon != 0.0);
if (valid_coordinates) {
status.update_position(new Point(data.lon, data.lat), null);
}
let gps_status_color;
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);
};
Bangle.setGPSPower(true, "gipy");
Bangle.on("GPS", set_coordinates);
Bangle.on("lock", function (on) {
if (!on) {
Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking
}
});
}
}
let files = require("Storage").list(".gpc");
if (files.length <= 1) {
if (files.length == 0) {
load();
} else {
start(files[0]);
}
} else {
drawMenu();
}

BIN
apps/gipy/gipy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

196
apps/gipy/interface.html Normal file
View File

@ -0,0 +1,196 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<style>
svg { width:95% }
</style>
</head>
<body>
<p>Please select a gpx file to be converted to gpc and loaded.</p>
gpx file : <input type="file" is="gpx_file" id="fileInput" accept=".gpx">
<br>
gpc filename : <input type="text" id="gpc_file" name="gpc_file" maxlength="24">.gpc (max 24 characters)
<br>
<input type="checkbox" id="osm" name="osm" checked>
<label for="osm">fetch interests from openstreetmap</label>
<table>
<tr>
<th><bold>OpenstreetMap <a href="https://wiki.openstreetmap.org/wiki/Tags">NODE Tags</a></bold></th>
</tr>
<tr>
<th>color</th><th>key</th><th>value</th>
</tr>
<tr>
<th style="color:red">red</th><th><input type="text" id="key1" name="key1" value="shop"></th><th><input type="text" id="value1" name="value1" value="bakery"></th>
</tr>
<tr>
<th style="color:blue">blue</th><th><input type="text" id="key2" name="key2" value="amenity"></th><th><input type="text" id="value2" name="value2" value="drinking_water"></th>
</tr>
<tr>
<th style="color:cyan">cyan</th><th><input type="text" id="key3" name="key3" value="amenity"></th><th><input type="text" id="value3" name="value3" value="toilets"></th>
</tr>
<tr>
<th style="color:green">green</th><th><input type="text" id="key4" name="key4" value="tourism"></th><th><input type="text" id="value4" name="value4" value="artwork"></th>
</tr>
</table>
<p>nice tags could be :
shop/bicycle, amenity/bank, shop/supermarket, leisure/picnic_table, tourism/information, amenity/pharmacy
</p>
<input type="button" id="convert" name="convert" value="Convert" disabled>
<input type="button" id="upload" name="upload" value="Upload" disabled>
<div id="status"></div>
<div id="map"></div>
<script src="../../core/lib/interface.js"></script>
<script>
function onInit() {
}
</script>
<script type="module">
function vec_to_string(vec) {
let final_string = '';
for (let i = 0 ; i < vec.length ; i++) {
final_string += String.fromCharCode(vec[i]);
}
return final_string;
}
import init, { convert_gpx_strings, convert_gpx_strings_no_osm, get_gpc, get_svg } from "./pkg/gpconv.js";
console.log("imported wasm");
let osm_checkbox = document.querySelector("input[name=osm]");
let with_osm = true;
osm_checkbox.addEventListener('change', function() {
if (this.checked) {
with_osm = true;
document.getElementById('key1').disabled = false;
document.getElementById('key2').disabled = false;
document.getElementById('key3').disabled = false;
document.getElementById('key4').disabled = false;
document.getElementById('value1').disabled = false;
document.getElementById('value2').disabled = false;
document.getElementById('value3').disabled = false;
document.getElementById('value4').disabled = false;
} else {
with_osm = false;
document.getElementById('key1').disabled = true;
document.getElementById('key2').disabled = true;
document.getElementById('key3').disabled = true;
document.getElementById('key4').disabled = true;
document.getElementById('value1').disabled = true;
document.getElementById('value2').disabled = true;
document.getElementById('value3').disabled = true;
document.getElementById('value4').disabled = true;
}
});
let status = document.getElementById("status");
let gpx_content = null;
let gpc_filename = null;
let gpc_content = null;
document
.getElementById("fileInput")
.addEventListener("change", function selectedFileChanged() {
document.getElementById('convert').disabled = true;
document.getElementById('upload').disabled = true;
if (this.files.length === 0) {
console.log("No file selected.");
return;
}
status.innerHTML = "reading file";
let gpx_filename = this.files[0].name;
if (gpc_filename === null || gpc_filename == "") {
if (gpx_filename.length <= 28) {
gpc_filename = gpx_filename.slice(0, gpx_filename.length - 4);
document.getElementById('gpc_file').value = gpc_filename;
}
}
const reader = new FileReader();
reader.onload = function fileReadCompleted() {
console.log("reading file completed");
status.innerHTML = "file reading completed";
gpx_content = reader.result;
document.getElementById('convert').disabled = false;
};
reader.readAsText(this.files[0]);
});
document
.getElementById("convert")
.addEventListener('click', function() {
console.log("starting conversion");
document.getElementById('convert').disabled = true;
document.getElementById('upload').disabled = true;
status.innerHTML = "please wait, converting file";
init().then(() => {
let gpc_svg;
if (with_osm) {
let key1 = document.getElementById('key1').value;
let key2 = document.getElementById('key2').value;
let key3 = document.getElementById('key3').value;
let key4 = document.getElementById('key4').value;
let value1 = document.getElementById('value1').value;
let value2 = document.getElementById('value2').value;
let value3 = document.getElementById('value3').value;
let value4 = document.getElementById('value4').value;
gpc_svg = convert_gpx_strings(gpx_content, key1, value1, key2, value2, key3, value3, key4, value4);
} else {
gpc_svg = convert_gpx_strings_no_osm(gpx_content);
}
gpc_svg.then(gs => {
status.innerHTML = "file converted";
let svg = get_svg(gs);
let svg_string = vec_to_string(svg);
let img = document.getElementById("map");
img.innerHTML = svg_string;
gpc_content = get_gpc(gs);
if (gpc_filename !== null) {
document.getElementById('upload').disabled = false;
}
});
});
});
document
.getElementById("gpc_file")
.addEventListener('change', function() {
gpc_filename = document.getElementById("gpc_file").value;
if (gpc_filename == "") {
document.getElementById("upload").disabled = true;
} else {
if (gpc_content !== null) {
document.getElementById("upload").disabled = false;
}
}
});
document
.getElementById("upload")
.addEventListener('click', function() {
status.innerHTML = "uploading file";
console.log("uploading");
let gpc_string = vec_to_string(gpc_content);
Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => {
status.innerHTML = `${gpc_filename}.gpc uploaded`;
console.log("DONE");
});
});
</script>
</body>
</html>

23
apps/gipy/metadata.json Normal file
View File

@ -0,0 +1,23 @@
{
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
"version": "0.15",
"description": "Follow gpx files",
"allow_emulator":false,
"icon": "gipy.png",
"type": "app",
"tags": "tool,outdoors,gps",
"screenshots": [],
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"gipy.app.js","url":"app.js"},
{"name":"gipy.settings.js","url":"settings.js"},
{"name":"gipy.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"gipy.json"}
]
}

75
apps/gipy/pkg/gpconv.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_gpc(gpcsvg: GpcSvg): Uint8Array;
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_svg(gpcsvg: GpcSvg): Uint8Array;
/**
* @param {string} input_str
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings_no_osm(input_str: string): Promise<GpcSvg>;
/**
* @param {string} input_str
* @param {string} key1
* @param {string} value1
* @param {string} key2
* @param {string} value2
* @param {string} key3
* @param {string} value3
* @param {string} key4
* @param {string} value4
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings(input_str: string, key1: string, value1: string, key2: string, value2: string, key3: string, value3: string, key4: string, value4: string): Promise<GpcSvg>;
/**
*/
export class GpcSvg {
free(): void;
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_gpcsvg_free: (a: number) => void;
readonly get_gpc: (a: number, b: number) => void;
readonly get_svg: (a: number, b: number) => void;
readonly convert_gpx_strings_no_osm: (a: number, b: number) => number;
readonly convert_gpx_strings: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

645
apps/gipy/pkg/gpconv.js Normal file
View File

@ -0,0 +1,645 @@
let wasm;
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let WASM_VECTOR_LEN = 0;
let cachedUint8Memory0 = new Uint8Array();
function getUint8Memory0() {
if (cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
const cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachedInt32Memory0 = new Int32Array();
function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_24(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
if (!(instance instanceof klass)) {
throw new Error(`expected instance of ${klass.name}`);
}
return instance.ptr;
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_gpc(gpcsvg) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
_assertClass(gpcsvg, GpcSvg);
wasm.get_gpc(retptr, gpcsvg.ptr);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_svg(gpcsvg) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
_assertClass(gpcsvg, GpcSvg);
wasm.get_svg(retptr, gpcsvg.ptr);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {string} input_str
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings_no_osm(input_str) {
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.convert_gpx_strings_no_osm(ptr0, len0);
return takeObject(ret);
}
/**
* @param {string} input_str
* @param {string} key1
* @param {string} value1
* @param {string} key2
* @param {string} value2
* @param {string} key3
* @param {string} value3
* @param {string} key4
* @param {string} value4
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings(input_str, key1, value1, key2, value2, key3, value3, key4, value4) {
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ptr3 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len3 = WASM_VECTOR_LEN;
const ptr4 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len4 = WASM_VECTOR_LEN;
const ptr5 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len5 = WASM_VECTOR_LEN;
const ptr6 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len6 = WASM_VECTOR_LEN;
const ptr7 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len7 = WASM_VECTOR_LEN;
const ptr8 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len8 = WASM_VECTOR_LEN;
const ret = wasm.convert_gpx_strings(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7, ptr8, len8);
return takeObject(ret);
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
*/
export class GpcSvg {
static __wrap(ptr) {
const obj = Object.create(GpcSvg.prototype);
obj.ptr = ptr;
return obj;
}
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_gpcsvg_free(ptr);
}
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function getImports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_gpcsvg_new = function(arg0) {
const ret = GpcSvg.__wrap(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
const ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
const ret = fetch(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
const ret = false;
return ret;
};
imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) {
const ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Response_eaa426220848a39e = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Response;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_url_74285ddf2747cb3d = function(arg0, arg1) {
const ret = getObject(arg1).url;
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_status_c4ef3dd591e63435 = function(arg0) {
const ret = getObject(arg0).status;
return ret;
};
imports.wbg.__wbg_headers_fd64ad685cf22e5d = function(arg0) {
const ret = getObject(arg0).headers;
return addHeapObject(ret);
};
imports.wbg.__wbg_text_1169d752cc697903 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).text();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
const ret = new Headers();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_append_de37df908812970d = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbindgen_is_object = function(arg0) {
const val = getObject(arg0);
const ret = typeof(val) === 'object' && val !== null;
return ret;
};
imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_next_579e583d33566a86 = function(arg0) {
const ret = getObject(arg0).next;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_is_function = function(arg0) {
const ret = typeof(getObject(arg0)) === 'function';
return ret;
};
imports.wbg.__wbg_value_1ccc36bc03462d71 = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_iterator_6f9d4f28845f426c = function() {
const ret = Symbol.iterator;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_0b9bfdd97583284e = function() {
const ret = new Object();
return addHeapObject(ret);
};
imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () {
const ret = self.self;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () {
const ret = window.window;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () {
const ret = globalThis.globalThis;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () {
const ret = global.global;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbindgen_is_undefined = function(arg0) {
const ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_call_168da88779e35f61 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_next_aaef7c8aa5e212ac = function() { return handleError(function (arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_done_1b73b0672e15f234 = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_new_9962f939219f1820 = function(arg0, arg1) {
try {
var state0 = {a: arg0, b: arg1};
var cb0 = (arg0, arg1) => {
const a = state0.a;
state0.a = 0;
try {
return __wbg_adapter_69(a, state0.b, arg0, arg1);
} finally {
state0.a = a;
}
};
const ret = new Promise(cb0);
return addHeapObject(ret);
} finally {
state0.a = state0.b = 0;
}
};
imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) {
const ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) {
const ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) {
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function(arg0, arg1, arg2) {
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
const ret = JSON.stringify(getObject(arg0));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_has_8359f114ce042f5a = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.has(getObject(arg0), getObject(arg1));
return ret;
}, arguments) };
imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) {
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
const ret = debugString(getObject(arg1));
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_memory = function() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
return addHeapObject(ret);
};
return imports;
}
function initMemory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = new Int32Array();
cachedUint8Memory0 = new Uint8Array();
return wasm;
}
function initSync(module) {
const imports = getImports();
initMemory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return finalizeInit(instance, module);
}
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('gpconv_bg.wasm', import.meta.url);
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
initMemory(imports);
const { instance, module } = await load(await input, imports);
return finalizeInit(instance, module);
}
export { initSync }
export default init;

Binary file not shown.

16
apps/gipy/pkg/gpconv_bg.wasm.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function __wbg_gpcsvg_free(a: number): void;
export function get_gpc(a: number, b: number): void;
export function get_svg(a: number, b: number): void;
export function convert_gpx_strings_no_osm(a: number, b: number): number;
export function convert_gpx_strings(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void;

View File

@ -0,0 +1,12 @@
{
"name": "gpconv",
"version": "0.1.0",
"files": [
"gpconv_bg.wasm",
"gpconv.js",
"gpconv.d.ts"
],
"module": "gpconv.js",
"types": "gpconv.d.ts",
"sideEffects": false
}

BIN
apps/gipy/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
apps/gipy/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

38
apps/gipy/settings.js Normal file
View File

@ -0,0 +1,38 @@
(function (back) {
var FILE = "gipy.json";
// Load settings
var settings = Object.assign(
{
keep_gps_alive: false,
max_speed: 35,
},
require("Storage").readJSON(FILE, true) || {}
);
function writeSettings() {
require("Storage").writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"": { title: "Gipy" },
"< Back": () => back(),
"keep gps alive": {
value: !!settings.keep_gps_alive, // !! converts undefined to false
format: (v) => (v ? "Yes" : "No"),
onchange: (v) => {
settings.keep_gps_alive = v;
writeSettings();
},
},
"max speed": {
value: 35 | settings.max_speed, // 0| converts undefined to 0
min: 0,
max: 130,
onchange: (v) => {
settings.max_speed = v;
writeSettings();
},
},
});
});

View File

@ -15,5 +15,9 @@
"strict": true,
"typeRoots": ["./typescript/types"]
},
"include": ["./**/*"]
"include": ["./**/*"],
"exclude": [
"**/gpconv.d.ts",
"**/gpconv_bg.wasm.d.ts"
]
}