From a5ccaa1c34481750c56003de3f294e31b27acae7 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Fri, 28 Jul 2023 07:46:26 +0200 Subject: [PATCH 01/54] sokoban: initial release --- apps/sokoban/ChangeLog | 1 + apps/sokoban/Microban.txt | 1822 ++++++++++++++++++++++++++++++++++++ apps/sokoban/README.md | 20 + apps/sokoban/TODO | 2 + apps/sokoban/app-icon.js | 1 + apps/sokoban/app.js | 464 +++++++++ apps/sokoban/metadata.json | 21 + apps/sokoban/soko.png | Bin 0 -> 3664 bytes apps/sokoban/sokoban.png | Bin 0 -> 863 bytes 9 files changed, 2331 insertions(+) create mode 100644 apps/sokoban/ChangeLog create mode 100644 apps/sokoban/Microban.txt create mode 100644 apps/sokoban/README.md create mode 100644 apps/sokoban/TODO create mode 100644 apps/sokoban/app-icon.js create mode 100644 apps/sokoban/app.js create mode 100644 apps/sokoban/metadata.json create mode 100644 apps/sokoban/soko.png create mode 100644 apps/sokoban/sokoban.png diff --git a/apps/sokoban/ChangeLog b/apps/sokoban/ChangeLog new file mode 100644 index 000000000..9fa2c8172 --- /dev/null +++ b/apps/sokoban/ChangeLog @@ -0,0 +1 @@ +0.01: Initial code diff --git a/apps/sokoban/Microban.txt b/apps/sokoban/Microban.txt new file mode 100644 index 000000000..96146080c --- /dev/null +++ b/apps/sokoban/Microban.txt @@ -0,0 +1,1822 @@ +; 1 + +#### +# .# +# ### +#*@ # +# $ # +# ### +#### + +; 2 + +###### +# # +# #@ # +# $* # +# .* # +# # +###### + +; 3 + + #### +### #### +# $ # +# # #$ # +# . .#@ # +######### + +; 4 + +######## +# # +# .**$@# +# # +##### # + #### + +; 5 + + ####### + # # + # .$. # +## $@$ # +# .$. # +# # +######## + +; 6 + +###### ##### +# ### # +# $$ #@# +# $ #... # +# ######## +##### + +; 7 + +####### +# # +# .$. # +# $.$ # +# .$. # +# $.$ # +# @ # +####### + +; 8 + + ###### + # ..@# + # $$ # + ## ### + # # + # # +#### # +# ## +# # # +# # # +### # + ##### + +; 9 + +##### +#. ## +#@$$ # +## # + ## # + ##.# + ### + +; 10 + + ##### + #. # + #.# # +#######.# # +# @ $ $ $ # +# # # # ### +# # +######### + +; 11 + + ###### + # # + # ##@## +### # $ # +# ..# $ # +# # +# ###### +#### + +; 12 + +##### +# ## +# $ # +## $ #### + ###@. # + # .# # + # # + ####### + +; 13 + +#### +#. ## +#.@ # +#. $# +##$ ### + # $ # + # # + # ### + #### + +; 14 + +####### +# # +# # # # +#. $*@# +# ### +##### + +; 15 + + ### +######@## +# .* # +# # # +#####$# # + # # + ##### + +; 16 + + #### + # #### + # ## +## ## # +#. .# @$## +# # $$ # +# .# # +########## + +; 17 + +##### +# @ # +#...# +#$$$## +# # +# # +###### + +; 18 + +####### +# # +#. . # +# ## ## +# $ # +###$ # + #@ # + # # + #### + +; 19 + +######## +# .. # +# @$$ # +##### ## + # # + # # + # # + #### + +; 20 + +####### +# ### +# @$$..# +#### ## # + # # + # #### + # # + #### + +; 21 + +#### +# #### +# . . # +# $$#@# +## # + ###### + +; 22 + +##### +# ### +#. . # +# # # +## # # + #@$$ # + # # + # ### + #### + +; 23 + +####### +# * # +# # +## # ## + #$@.# + # # + ##### + +; 24 + +# ##### + # # +###$$@# +# ### +# # +# . . # +####### + +; 25 + + #### + # ### + # $$ # +##... # +# @$ # +# ### +##### + +; 26 + + ##### + # @ # + # # +###$ # +# ...# +# $$ # +### # + #### + +; 27 + +###### +# .# +# ## ## +# $$@# +# # # +#. ### +##### + +; 28 + +##### +# # +# @ # +# $$### +##. . # + # # + ###### + +; 29 + + ##### + # ## + # # + ###### # +## #. # +# $ $ @ ## +# ######.# +# # +########## + +; 30 + +#### +# ### +# $$ # +#... # +# @$ # +# ## +##### + +; 31 + + #### + ## # +##@$.## +# $$ # +# . . # +### # + ##### + +; 32 + + #### +## ### +# # +#.**$@# +# ### +## # + #### + +; 33 + +####### +#. # # +# $ # +#. $#@# +# $ # +#. # # +####### + +; 34 + + #### +### #### +# # +#@$***. # +# # +######### + +; 35 + + #### + ## # + #. $# + #.$ # + #.$ # + #.$ # + #. $## + # @# + ## # + ##### + +; 36 + +#### +# ############ +# $ $ $ $ $ @ # +# ..... # +############### + +; 37 + + ### +##### #.# +# ###.# +# $ #.# +# $ $ # +#####@# # + # # + ##### + +; 38 + +########## +# # +# ##.### # +# # $$ . # +# . @$## # +##### # + ###### + +; 39 + +##### +# #### +# # # .# +# $ ### +### #$. # +# #@ # +# # ###### +# # +##### + +; 40 + + ##### + # # +## ## +# $$$ # +# .+. # +####### + +; 41 + +####### +# # +#@$$$ ## +# #...# +## ## + ###### + +; 42 + + #### + # # + #@ # +####$.# +# $.# +# # $.# +# ## +###### + +; 43 + + #### + # @# + # # +###### .# +# $ .# +# $$# .# +# #### +### # + #### + +; 44 'Duh!' + +##### +#@$.# +##### + +; 45 + +###### +#... # +# $ # +# #$## +# $ # +# @ # +###### + +; 46 + + ###### +## # +# ## # +# # $ # +# * .# +## #@## + # # + ##### + +; 47 + + ####### +### # +# $ $ # +# ### ##### +# @ . . # +# ### # +##### ##### + +; 48 + +###### +# @ # +# # ## +# .# ## +# .$$$ # +# .# # +#### # + ##### + +; 49 + +###### +# @ # +# $# # +# $ # +# $ ## +### #### + # # # + #... # + # # + ####### + +; 50 + + #### +### ##### +# $ @..# +# $ # # +### #### # + # # + ######## + +; 51 + +#### +# ### +# ### +# $*@ # +### .# # + # # + ###### + +; 52 + + #### +### @# +# $ # +# *.# +# *.# +# $ # +### # + #### + +; 53 + + ##### +##. .## +# * * # +# # # +# $ $ # +## @ ## + ##### + +; 54 + + ###### + # # + ##### . # +### ###. # +# $ $ . ## +# @$$ # . # +## ##### + ###### + +; 55 + +######## +# @ # # +# # +#####$ # + # ### + ## #$ ..# + ## # ### + #### + +; 56 + +##### +# ### +# $ # +##* . # + # @# + ###### + +; 57 + + #### + # # + #@ # + # # +### #### +# * # +# $ # +#####. # + #### + +; 58 + +#### +# #### +#.*$ # +# .$# # +## @ # + # ## + ##### + +; 59 + +############ +# # +# ####### @## +# # # +# # $ # # +# $$ ##### # +### # # ...# + #### # # + ###### + +; 60 + + ######### + # # +##@##### # +# # # # +# # $.# +# ##$##.# +##$## #.# +# $ #.# +# # ### +######## + +; 61 + +######## +# # +# #### # +# #...@# +# ###$### +# # # +# $$ $ # +#### ## + #.### + ### + +; 62 + + ########## +#### ## # +# $$$....$@# +# ### # +# #### #### +##### + +; 63 + +##### #### +# ##### .# +# $ ######## +### #### .$ @ # + # # # #### # + #### #### ##### + +; 64 + + ###### +## # +# $ # +# $$ # +### .##### + ##.# @ # + #. $ # + #. #### + #### + +; 65 + + ###### + # # + # $ # + ####$ # +## $ $ # +#....# ## +# @ # +## # # + ######## + +; 66 + + ### + #@# + ###$### +## . ## +# # # # +# # # # +# # # # +# # # # +# # # # +## $ $ ## + ##. .## + # # + # # + ##### + +; 67 + +##### +# ## +# # # +#@$*.## +## . # + # $# # + ## # + ##### + +; 68 + + #### + # ###### +## $ # +# .# $ # +# .#$##### +# .@ # +###### + +; 69 + +#### #### +# #### # +# # # # +# # $## +# . .#$ # +#@ ## # $ # +# . # # +########### + +; 70 + +##### +# @ #### +# # +# $ $$ # +##$## # +# #### +# .. # +##.. # + ### # + #### + +; 71 + +########### +# # ### +# $@$ # . .# +# ## ### ## # +# # # # +# # # # # +# ######### # +# # +############# + +; 72 + + #### + ## ##### + # $ @ # + # $# # +#### ##### +# # # +# $ # +# ..# # +# .#### +# ## +#### + +; 73 + +#### +# ##### +# $$ $ # +# # +## ## ## +#...#@# +# ### ## +# # +# # # +######## + +; 74 + + #### + # ####### + #$ @# .# +## #$$ .# +# $ ##..# +# # ##### +### # + ##### + +; 75 + + ####### +## ....## +# ###### +# $ $ @# +### $ $ # + ### # + ###### + +; 76 + + ##### +## # +# ##### +# #.# # +#@ #.# $ # +# #.# ## +# # # +## ##$$# + ## # + # #### + #### + +; 77 + +########## +# @ .... # +# ####$## +## # $ $ # + # $ # + # ###### + ##### + +; 78 + + ####### +## ## +# $ $ # +# $ $ $ # +## ### #### + #@ .....# + ## ### + ####### + +; 79 + + ######### + # # # +## $#$# # +# .$.@ # +# .# # +########## + +; 80 + +#### +# ####### +# . ## .# +# $# .# +## ## # .# + # # # + #### # # + # @$ ### + # $$ # + # # + ###### + +; 81 + + ##### + # # + # . # +## * # +# *## +# @## +## $ # + # # + ##### + +; 82 + +##### +# ### +# . ## +##*#$ # +# .# $ # +# @## ## +# # +####### + +; 83 + +###### +# ## +# $ $ ## +## $$ # + # # # + # ## ## + # . .# + # @. .# + # #### + #### + +; 84 + +######## +# ... # +# ### ## +# # $ # +## #@$ # + # # $ # + # ### ##### + # # + # ### # + ##### ##### + +; 85 + + #### + ####### # + # $ # + # $ $ # + # ######## +## # . # +# # # # +# @ . ## +## # # # + # . # + ####### + +; 86 + + #### + ### ## + ## $ # +## $ # # +# @#$$ # +# .. ### +# ..### +##### + +; 87 + + #### +###### # +# # +# ... .# +##$###### +# $ # +# $### +## $ # + ## @ # + ###### + +; 88 + + #### + # ### # + # # # + # # # # + # #$ #.# + # # # # # + # #$ #.# # + # # # # +####$ #.# # +# @ # # +# # ## # +######## + +; 89 + +########## +# ## # +# $ $@# # +#### # $ # + #.# ## + # #.# $# + # #. # + # #. # + ###### + +; 90 + + ######## + # @ # + # $ $ # +### ## ### +# $..$ # +# .. # +########## + +; 91 + +########### +# .## # +# $$@..$$ # +# ##. # +########### + +; 92 + + #### + # # ##### + # # # # + # ######.# # +#### $ . # +# $$# ###.# # +# # # # # +######### #@ ## + # # + #### + +; 93 + + ######### +## # ## +# # # +# $ # $ # +# *.* # +####.@.#### +# *.* # +# $ # $ # +# # # +## # ## + ######### + +; 94 + +######### +# @ # # +# $ $ # +##$### ## +# ... # +# # # +###### # + #### + +; 95 + +######## +#@ # +# .$$. # +# $..$ # +# $..$ # +# .$$. # +# # +######## + +; 96 + + ###### + # # + # # +##### # +# #.##### +# $@$ # +#####.# # + ## ## ## + # $.# + # ### + ##### + +; 97 + + #### + # ######## +#### $ $.....# +# $ ###### +#@### ### +# $ # +# $ # # +## # # + # # + ###### + +; 98 + +##### +# ## #### +# $ ### .# +# $ $ .# +## $#####.# #### +# $ # # .### # +# # # .# @ # +### # # # + #### ## ## + ####### + +; 99 + + ##### + # # +####### ####### # # +# # # # # +# @ #### # #### +# # ....## #### # +# ##### ## $$ $ $ # +###### # # + # ########## + #### + +; 100 + +####### +# @# # +#.$ # +#. # $## +#.$# # +#. # $ # +# # # +######## + +; 101 'Lockdown' + + ##### + # # + # # ####### + # * # # + ## ## # # + # #* # +### # # # ### +# *#$+ # +# # ## ## +# # * # +####### # # + # # + ##### + +; 102 + +########### +#....# # +# # $$ # +# @ ## # +# ##$ # +###### $ # + # # + ###### + +; 103 + + ##### + # . ## +### $ # +# . $#@# +# #$ . # +# $ ### +## . # + ##### + +; 104 + + ##### +##### # +# $ # +# $#$#@# +### # # + # ... # + ### ## + # # + #### + +; 105 + + #### #### +## ### ## +# # # # +# *. .* # +###$ $### + # @ # +###$ $### +# *. .* # +# # # # +## ### ## + #### #### + +; 106 + + ######## + # # + #@ $ # +## ###$ # +# .....### +# $ $ $ # +###### # # + # # + ##### + +; 107 + +######## +# # +# $*** # +# * * # +# * * # +# ***. # +# @# +######## + +; 108 + +#### ##### +# ### # ## +# # #$ $ # +#..# ##### # # +# @ # $ $ # +#..# ## +## ######### + ##### + +; 109 + + ####### +# # # +# # # # # + # @ $ # +### ### # +# ### # +# $ ##.# +## $ #.# + ## $ .# +# ## $#.# +## ## #.# +### # # +### ##### + +; 110 + + #### + # # + # $#### +###. . # +# $ # $ # +# . .### +####$ # + # @# + #### + +; 111 + +###### +# #### +# ...# +# ...# +###### # + # # # + # $$ ## + # @$ # + # $$ # + ## $# # + # # + ###### + +; 112 + + ##### +## #### +# $$$ # +# # $ # +# $## ## +### #. # + # # # + ##### ### + # # ## + # @....# + # # + # # # + ######## + +; 113 + + ##### + ## # +### # # +# . # +# ## ##### +# . . # ## +# # @ $ ### +#####. # $ # + #### $ # + ## $ ## + # ## + # # + #### + +; 114 + +###### +# ### +# # $ # +# $ @ # +## ## ##### +# #......# +# $ $ $ $ # +## ###### + ##### + +; 115 + + ##### +##### #### +# # # +# #..... # +## ## # ### + #$$@$$$ # + # ### + ####### + +; 116 + + ##### + ### # +####.....# +# @$$$$$ # +# # ## +##### # + ##### + +; 117 + + #### #### + # ### ## + # @ # +##..### # +# # # +#...#$ # # +# ## $$ $ # +# $ ### +#### ### + #### + +; 118 + + ##### +## ## +# $ ## +# $ $ ## +###$# . ## + # # . # + ## ##. # + # @ . ## + # # # + ######## + +; 119 + + ###### + # ## + ## ## # + # $$ # # + # @$ # # + # # # +#### # # +# ... ## +# ## +####### + +; 120 + + #### +####### # +# $ ## +# $##### # +# @# # # +## ##.. # +# # ..#### +# $ ### +# $### +# # +#### + +; 121 + + ###### + # . # +##$.# # +# * # +# ..### +##$ # ##### +## ## # # +# #### # # +# @ $ $ # +## # # + ########## + +; 122 + +##### +# ### +# #$ # +# $ # +# $ $ # +# $# # +# @### +## ######## +# ...# +# # +########..# + #### + +; 123 + +######## +# # +# $ $$ ######## +##### @##. . # + #$ # . # + # #. . ## + #$# ## # # + # # + # ### ## + # # #### + #### + +; 124 + +############## +# # # +# $@$$ # . ..# +## ## ### ## # + # # # # + # # # # # + # ######### # + # # + ############# + +; 125 + + ##### + # ## + # $ # +######## #@## +# . # $ $ # +# $# # +#...##### # +##### ##### + +; 126 + + ########### +##....... # +# $$$$$$$@ # +# # # # ## +# # # # +# ####### +##### + +; 127 + +## #### +#### #### + # $ $. # +## # .$ # +# ##.### +# $ . # +# @ # # +# ###### +#### + +; 128 + + ######### +### # # +# * $ . . # +# $ ## ## +####*# # + # @ ### + # ### + ##### + +; 129 + + ######### +### @ # # +# * $ *.. # +# $ # # +####*# ### + # ## + # ### + ##### + +; 130 + +##### ##### +# ####.. # +# $$$ # +# $# .. # +### @# ## # + # ## # + ########## + +; 131 + +##### +# # +# . # +#.@.### +##.# # +# $ # +# $ # +##$$ # + # ### + # # + #### + +; 132 + +#### +# @### +#.* ##### +#..#$$ $ # +## # + # # ## # + # ##### + ##### + +; 133 + + ####### + # . .### + # . . . # +### #### # +# @$ $ # +# $$ $ # +#### ### + ##### + +; 134 + + #### +######### # +# ## $ # +# $ ## # +### #. .# ## + # #. .#$## + # # # # + # @ $ # + # ####### + #### + +; 135 + +####### +# ##### +# $$#@##..# +# # # +# $ # # # +#### $ ..# + ######## + +; 136 + + ####### + # # +## ###$## +#.$ @ # +# .. #$ # +#.## $ # +# #### +###### + +; 137 + + #### + ## ### +#### # $ # +# #### $ $ # +# ..# #$ # +# # @ ### +## #..# ### + # ## # # + # # + ######## + +; 138 + + #### +### # +# ### +# # . .# +# @ ...#### +# # # # ## +# # $$ # +##### $ $ # + ##$ # ## + # # + ###### + +; 139 + + #### +## #### +# ...# +# ...# +# # ## +# #@ #### #### +##### $ ### # + # ##$ $ # + ### $$ # + # $ ## ### + # ###### + ###### + +; 140 + +######## ##### +# # ### # +# ## $ # +#.# @ ## $ ## +#.# # $ ## +#.# $ ## +#. ## ##### +## # + ###### + +; 141 + + ######## + # # . # + # .*.# + # # * # +####$##.## +# $ # +# $ ## $ # +# @# # +########## + +; 142 + + #### + # # + # #### +###$.$ # +# .@. # +# $.$### +#### # + # # + #### + +; 143 + +#### +# #### +# $ # +# .# # +# $# ## +# . # +#### # + # # + ### ### + # $ # +## #$# ## +# $ @ $ # +# ..#.. # +### ### + ##### + +; 144 + + #### + ### ##### + # $$ # # + # $ . .$$## + # .. #. $ # +### #** . # +# . **# ### +# $ .# .. # +##$$.@. $ # + # # $$ # + ##### ### + #### + +; 145 + + ##### + # @ # + ## ## +###.$$$.### +# $...$ # +# $.#.$ # +# $...$ # +###.$$$.### + ## ## + # # + ##### + +; 146 + + ####### +## . ## +# .$$$. # +# $. .$ # +#.$ @ $.# +# $. .$ # +# .$$$. # +## . ## + ####### + +; 147 + + ##### +######## # +#. . @#.# +# ### # +## $ # # + # $ ##### + # $# # + ## # # + # ## + ##### + +; 148 'from (Original 18)' + +########### +# . # # +# #. @ # +# #..# ####### +## ## $$ $ $ # + ## # + ############# + +; 149 'from (Boxxle 43)' + + #### +## ### +#@$ # +### $ # + # ###### + # $....# + # # #### + ## # # + # $# # + # # + # ### + #### + +; 150 'from (Original 47)' + + #### + ##### # + # $####### +## ## ..# ...# +# $ $$#$ @ # +# ### # +####### # #### + #### + +; 151 'from (Original 47)' + + #### + # # + ### # +## $ # +# # # +# #$$ ###### +# # # .# +# $ @ .# +### ####..# + #### #### + +; 152 + +###### #### +# # # +#.## #$## # +# # # # +#$ # ### # # +# # # # # +# # #### # # # +#. @ $ * . # +############### + +; 153 + +############# +#.# @# # # +#.#$$ # $ # +#.# # $# # +#.# $# # $## +#.# # $# # +#.# $# # $# +#.. # $ # +#.. # # # +############ + +; 154 'Take the long way home.' + + ############################ + # # + # ######################## # + # # # # + # # #################### # # + # # # # # # + # # # ################ # # # + # # # # # # # # + # # # # ############ # # # # + # # # # # # # # # + # # # # # ############ # # # + # # # # # # # # + # # # # ################ # # + # # # # # # +##$# # #################### # +#. @ # # +############################# + +; 155 'The Dungeon' + + ###### #### +#####*# ################# ## +# ### # +# ######## #### ## # +### #### # #### #### ## +#*# # .# # # # # # # +#*# # # # ## # ## ## # +### ### ### # ## # ## ## + # # #*# # # # # + # # ### ##### #### # # + ##### ##### ####### ###### + # # # #**# # +## # # #**# ####### ## # +# ######### # ##### ### +# # # $ #*# +# ######### ### @##### #*# +##### #### #### ###### diff --git a/apps/sokoban/README.md b/apps/sokoban/README.md new file mode 100644 index 000000000..36097d66f --- /dev/null +++ b/apps/sokoban/README.md @@ -0,0 +1,20 @@ +# Sokoban + +Classic Sokoban game. + +Tap screen at bottom/top/left/right to push boxes into their destinations. +Swipe to undo. + +![Screenshot](soko.png) + +You play the yellow disk (rice hat seen from above). +Each level has a set of crates (brown if incorrectly placed or blue if correctly placed) +and a set of placeholders (empty blue squares). Simply push all crates into their placeholders. +Remember you can push but never pull. + +## Creator + +Levels are the [Microban](http://www.abelmartin.com/rj/sokobanJS/Skinner/David%20W.%20Skinner%20-%20Sokoban.htm) levels +by David W. Skinner. + +frederic.wagner@imag.fr diff --git a/apps/sokoban/TODO b/apps/sokoban/TODO new file mode 100644 index 000000000..dcad68d38 --- /dev/null +++ b/apps/sokoban/TODO @@ -0,0 +1,2 @@ +- background +- win screen + final win screen diff --git a/apps/sokoban/app-icon.js b/apps/sokoban/app-icon.js new file mode 100644 index 000000000..e8a1d4b0f --- /dev/null +++ b/apps/sokoban/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/ABMN7oXV7vd6AuVAAIXaAwYAEC75fGC6KPFC58BiMQJxAXLiIABDAcBigXRAAIFDC5pGBAAwvOCQYbDiBfOLwgYBO54qER6QXDexIXJRggXRIxIXpb4wXMLwYvTdIinSC4gYFC5xiIC54YDC54SBIQZHRC4gcFC5hyEC4KPPLIrZGC5IWCLwgXPUApeGC5KfGC6DnGIwwXLB4gXQgI/FAwy/MAH4A/ABgA==")) diff --git a/apps/sokoban/app.js b/apps/sokoban/app.js new file mode 100644 index 000000000..90b24ed11 --- /dev/null +++ b/apps/sokoban/app.js @@ -0,0 +1,464 @@ +// basic shapes +const SPACE = 0; +const WALL = 1; +const PLAYER = 2; +const BOX = 3; +const HOLE = 4; +const FILLED = 5; + +// basic directions +const LEFT = 0; +const UP = 1; +const DOWN = 2; +const RIGHT = 3; + +function go(line, column, direction) { + let destination_line = line; + let destination_column = column; + if (direction == LEFT) { + destination_column -= 1; + } else if (direction == RIGHT) { + destination_column += 1; + } else if (direction == UP) { + destination_line -= 1; + } else { + // direction is down + destination_line += 1; + } + return [destination_line, destination_column]; +} + +Bangle.setOptions({ + lockTimeout: 60000, + backlightTimeout: 60000, +}); + +let s = require("Storage"); + +// parse the levels a bit more to figure offsets delimiting next map. +function next_map_offsets(filename, start_offset) { + let raw_maps = s.readArrayBuffer(filename); + let offsets = []; + // this is a very dumb parser : map starts three chars after the end of a line with a ';' + // and ends two chars before next ';' + let comment_line = true; + for (let i = start_offset; i < raw_maps.length; i++) { + if (raw_maps[i] == 59) { // ';' + if (offsets.length != 0) { + offsets.push(i - 2); + return offsets; + } + comment_line = true; + } else if (raw_maps[i] == 10) { // '\n' + if (comment_line) { + comment_line = false; + offsets.push(i + 3); + } + } + } + return offsets; +} + +let config = s.readJSON("sokoban.json", true); +if (config === undefined) { + let initial_offsets = next_map_offsets("Microban.txt", 0); + config = { + levels_sets: ["Microban.txt"], // all known files containing levels + levels_set: 0, // which set are we using ? + current_maps: [0], // what is current map on each set ? + offsets: [initial_offsets], // known offsets for each levels set (binary positions of maps in each file) + }; + s.writeJSON("sokoban.json", config); +} + +let map = null; +let in_menu = false; +let history = null; // store history to allow undos + + +function load_map(filename, start_offset, end_offset, name) { + console.log("loading map in", filename, "between", start_offset, "and", end_offset); + let raw_map = new Uint8Array(s.readArrayBuffer(filename), start_offset, end_offset - start_offset); + let dimensions = map_dimensions(raw_map); + history = []; + return new Map(dimensions, raw_map, filename, name); +} + +function load_current_map() { + let current_set = config.levels_set; + let offsets = config.offsets[current_set]; + let set_filename = config.levels_sets[current_set]; + let set_name = set_filename.substring(0, set_filename.length - 4); // remove '.txt' + let current_map = config.current_maps[current_set]; + map = load_map(set_filename, offsets[2 * current_map], offsets[2 * current_map + 1], set_name + " " + (current_map + 1)); + map.display(); +} + +function next_map() { + let current_set = config.levels_set; + let current_map = config.current_maps[current_set]; + let offsets = config.offsets[current_set]; + if (2 * (current_map + 1) >= offsets.length) { + // we parse some new offsets + let new_offsets = next_map_offsets(config.levels_sets[current_set], offsets[offsets.length - 1] + 2); // +2 since we need to start at ';' (we did -2 from ';' in previous parser call) + if (new_offsets.length == 0) { + E.showAlert("You Win", "All levels completed").then(function() { + load(); + }); + } else { + config.offsets[current_set].push(new_offsets[0]); + config.offsets[current_set].push(new_offsets[1]); + } + } + config.current_maps[current_set]++; + s.writeJSON("sokoban.json", config); + load_current_map(); +} + +function previous_map() { + let current_set = config.levels_set; + let current_map = config.current_maps[current_set]; + if (current_map > 0) { + current_map--; + config.current_maps[current_set] = current_map; + s.writeJSON("sokoban.json", config); + load_current_map(); + } +} + +function map_dimensions(raw_map) { + let line_start = 0; + let width = 0; + let height = 0; + for (let i = 0; i < raw_map.length; i++) { + if (raw_map[i] == 10) { + height += 1; + let line_width = i - line_start; + if (i > 0 && raw_map[i - 1] == 13) { + line_width -= 1; // remove \r + } + width = Math.max(line_width, width); + line_start = i + 1; + } + } + return [width, height]; +} + +class Map { + constructor(dimensions, raw_map, filename, name) { + this.filename = filename; + this.name = name; + this.width = dimensions[0]; + this.height = dimensions[1]; + this.remaining_holes = 0; + // start by creating an empty map + this.m = []; + for (let i = 0; i < this.height; i++) { + let line = new Uint8Array(this.width); + for (let j = 0; j < this.width; j++) { + line[j] = SPACE; + } + this.m.push(line); + } + // now fill with raw_map's content + let current_line = 0; + let line_start = 0; + for (let i = 0; i < raw_map.length; i++) { + if (raw_map[i] == 32) { + this.m[current_line][i - line_start] = SPACE; + } else if (raw_map[i] == 43) { + // '+' + this.remaining_holes += 1; + this.m[current_line][i - line_start] = HOLE; + this.player_column = i - line_start; + this.player_line = current_line; + } else if (raw_map[i] == 10) { + current_line += 1; + line_start = i + 1; + } else if (raw_map[i] == 35) { + this.m[current_line][i - line_start] = WALL; + } else if (raw_map[i] == 36) { + this.m[current_line][i - line_start] = BOX; + } else if (raw_map[i] == 46) { + this.remaining_holes += 1; + this.m[current_line][i - line_start] = HOLE; + } else if (raw_map[i] == 64) { + this.m[current_line][i - line_start] = SPACE; + this.player_column = i - line_start; + this.player_line = current_line; + } else if (raw_map[i] == 42) { + this.m[current_line][i - line_start] = FILLED; + } else if (raw_map[i] != 13) { + console.log("warning unknown map content", raw_map[i]); + } + } + this.steps = 0; + this.calibrate(); + } + // compute scale + calibrate() { + let r = Bangle.appRect; + let rwidth = 1 + r.x2 - r.x; + let rheight = 1 + r.y2 - r.y; + let cell_width = Math.floor(rwidth / this.width); + let cell_height = Math.floor(rheight / this.height); + let cell_scale = Math.min(cell_width, cell_height); // we want square cells + let real_width = this.width * cell_scale; + let real_height = this.height * cell_scale; + let sx = r.x + Math.ceil((rwidth - real_width) / 2); + let sy = r.y + Math.ceil((rheight - real_height) / 2); + this.sx = sx; + this.sy = sy; + this.cell_scale = cell_scale; + } + undo(direction, pushing) { + this.steps -= 1; + + let previous_position = go(this.player_line, this.player_column, 3 - direction); + let previous_line = previous_position[0]; + let previous_column = previous_position[1]; + + if (pushing) { + // put the box back on current player position + let currently_on = this.m[this.player_line][this.player_column]; + if (currently_on == HOLE) { + this.remaining_holes -= 1; + this.m[this.player_line][this.player_column] = FILLED; + } else { + this.m[this.player_line][this.player_column] = BOX; + } + // now, remove the box from its current position + let current_box_position = go(this.player_line, this.player_column, direction); + let box_line = current_box_position[0]; + let box_column = current_box_position[1]; + let box_on = this.m[box_line][box_column]; + if (box_on == FILLED) { + this.remaining_holes += 1; + this.m[box_line][box_column] = HOLE; + } else { + this.m[box_line][box_column] = SPACE; + } + this.display_cell(box_line, box_column); + } + // cancel player display + this.display_cell(this.player_line, this.player_column); + // re-display player at previous position + this.player_line = previous_line; + this.player_column = previous_column; + this.display_player(); + } + move(direction) { + let destination_position = go(this.player_line, this.player_column, direction); + let destination_line = destination_position[0]; + let destination_column = destination_position[1]; + let destination = this.m[destination_line][destination_column]; + let pushing = false; + if (destination == BOX || destination == SPACE || destination == HOLE || destination == FILLED) { + if (destination == BOX || destination == FILLED) { + pushing = true; + let after_line = 2 * destination_line - this.player_line; + let after_column = 2 * destination_column - this.player_column; + let after = this.m[after_line][after_column]; + let will_remain = SPACE; + if (destination == FILLED) { + will_remain = HOLE; + } + if (after == SPACE) { + if (will_remain == HOLE) { + this.remaining_holes += 1; + } + this.m[destination_line][destination_column] = will_remain; + this.m[after_line][after_column] = BOX; + } else if (after == HOLE) { + this.m[destination_line][destination_column] = will_remain; + this.m[after_line][after_column] = FILLED; + if (will_remain == SPACE) { + this.remaining_holes -= 1; + } + if (this.remaining_holes == 0) { + in_menu = true; + this.steps += 1; + E.showAlert("" + this.steps + "steps", "You Win").then(function() { + in_menu = false; + next_map(); + }); + return; + } + } else { + return; + } + this.display_cell(after_line, after_column); + this.display_cell(destination_line, destination_column); + } + history.push([direction, pushing]); + this.display_cell(this.player_line, this.player_column); + this.steps += 1; + this.player_line = destination_line; + this.player_column = destination_column; + this.display_player(); + // this.display(); + } + } + display_player() { + sx = this.sx; + sy = this.sy; + cell_scale = this.cell_scale; + g.setColor(0.8, 0.8, 0).fillCircle(sx + (0.5 + this.player_column) * cell_scale, sy + (0.5 + this.player_line) * cell_scale, cell_scale / 2 - 1); // -1 because otherwise it overfills + } + display_cell(line, column) { + sx = this.sx; + sy = this.sy; + cell_scale = this.cell_scale; + let shape = this.m[line][column]; + if (shape == WALL) { + if (cell_scale < 10) { + g.setColor(1, 0, 0).fillRect(sx + column * cell_scale, sy + line * cell_scale, sx + (column + 1) * cell_scale, sy + (line + 1) * cell_scale); + } else { + g.setColor(0.5, 0.5, 0.5).fillRect(sx + column * cell_scale, sy + line * cell_scale, sx + (column + 1) * cell_scale, sy + (line + 1) * cell_scale); + g.setColor(1, 0, 0).fillRect(sx + column * cell_scale, sy + (line + 0.15) * cell_scale, sx + (column + 0.35) * cell_scale, sy + (line + 0.45) * cell_scale); + g.fillRect(sx + (column + 0.55) * cell_scale, sy + (line + 0.15) * cell_scale, sx + (column + 1) * cell_scale, sy + (line + 0.45) * cell_scale); + g.fillRect(sx + column * cell_scale, sy + (line + 0.65) * cell_scale, sx + (column + 0.65) * cell_scale, sy + (line + 0.95) * cell_scale); + g.fillRect(sx + (column + 0.85) * cell_scale, sy + (line + 0.65) * cell_scale, sx + (column + 1) * cell_scale, sy + (line + 0.95) * cell_scale); + } + } else if (shape == BOX) { + let border = Math.floor((cell_scale - 2) / 4); + if (border > 0) { + g.setColor(0.6, 0.4, 0.3).fillRect(sx + column * cell_scale + 1, sy + line * cell_scale + 1, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + g.setColor(0.7, 0.5, 0.5).fillRect(sx + column * cell_scale + 1 + border, sy + line * cell_scale + 1 + border, sx + (column + 1) * cell_scale - 1 - border, sy + (line + 1) * cell_scale - 1 - border); + } else { + g.setColor(0.7, 0.5, 0.5).fillRect(sx + column * cell_scale + 1, sy + line * cell_scale + 1, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + } + } else if (shape == HOLE) { + g.setColor(1, 1, 1).fillRect(sx + column * cell_scale, sy + line * cell_scale, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + g.setColor(0, 0, 1).drawRect(sx + column * cell_scale, sy + line * cell_scale, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + } else if (shape == FILLED) { + let border = Math.floor((cell_scale - 2) / 4); + if (border > 0) { + g.setColor(0.6, 0.4, 0.3).fillRect(sx + column * cell_scale + 1, sy + line * cell_scale + 1, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + g.setColor(0, 0, 1).fillRect(sx + column * cell_scale + 1 + border, sy + line * cell_scale + 1 + border, sx + (column + 1) * cell_scale - 1 - border, sy + (line + 1) * cell_scale - 1 - border); + } else { + g.setColor(0, 0, 1).fillRect(sx + column * cell_scale + 1 + border, sy + line * cell_scale + 1 + border, sx + (column + 1) * cell_scale - 1 - border, sy + (line + 1) * cell_scale - 1 - border); + + } + } else if (shape == SPACE) { + g.setColor(1, 1, 1).fillRect(sx + column * cell_scale, sy + line * cell_scale, sx + (column + 1) * cell_scale - 1, sy + (line + 1) * cell_scale - 1); + } + + } + display() { + g.clear(); + for (let line = 0; line < this.height; line++) { + for (let column = 0; column < this.width; column++) { + this.display_cell(line, column); + } + } + this.display_player(); + g.setColor(0, 0, 0).setFont("6x8:2") + .setFontAlign(0, -1, 0) + .drawString(map.name, g.getWidth() / 2, 0); + } +} + + +Bangle.on('touch', function(button, xy) { + if (in_menu) { + return; + } + let half_width = g.getWidth() / 2; + let half_height = g.getHeight() / 2; + let directions_amplitudes = [0, 0, 0, 0]; + directions_amplitudes[LEFT] = half_width - xy.x; + directions_amplitudes[RIGHT] = xy.x - half_width; + directions_amplitudes[UP] = half_height - xy.y; + directions_amplitudes[DOWN] = xy.y - half_height; + + let max_direction; + let second_max_direction; + if (directions_amplitudes[0] > directions_amplitudes[1]) { + max_direction = 0; + second_max_direction = 1; + } else { + max_direction = 1; + second_max_direction = 0; + } + for (let direction = 2; direction < 4; direction++) { + if (directions_amplitudes[direction] > directions_amplitudes[max_direction]) { + second_max_direction = max_direction; + max_direction = direction; + } else if (directions_amplitudes[direction] >= directions_amplitudes[second_max_direction]) { + second_max_direction = direction; + } + } + if (directions_amplitudes[max_direction] - directions_amplitudes[second_max_direction] > 10) { + // if there is little possible confusions between two candidate moves let's move. + // basically we forbid diagonals of 10 pixels wide + map.move(max_direction); + } + +}); + +Bangle.on('swipe', function(directionLR, directionUD) { + if (in_menu) { + return; + } + let last_move = history.pop(); + if (last_move !== undefined) { + map.undo(last_move[0], last_move[1]); + } +}); + +setWatch( + function() { + if (in_menu) { + return; + } + in_menu = true; + const menu = { + "": { + title: "choose action" + }, + "restart": function() { + E.showMenu(); + load_current_map(); + in_menu = false; + }, + "current map": { + value: config.current_maps[config.levels_set] + 1, + min: 1, + max: config.offsets[config.levels_set].length / 2, + onchange: (v) => { + config.current_maps[config.levels_set] = v - 1; + load_current_map(); + s.writeJSON("sokoban.json", config); + } + }, + "next map": function() { + E.showMenu(); + next_map(); + in_menu = false; + }, + "previous map": function() { + E.showMenu(); + previous_map(); + in_menu = false; + }, + "back to game": function() { + E.showMenu(); + g.clear(); + map.display(); + in_menu = false; + }, + }; + E.showMenu(menu); + }, + BTN1, { + repeat: true + } +); + + +Bangle.setLocked(false); + +current_map = config.current_map; +offsets = config.offsets; +load_current_map(); \ No newline at end of file diff --git a/apps/sokoban/metadata.json b/apps/sokoban/metadata.json new file mode 100644 index 000000000..191cea1e0 --- /dev/null +++ b/apps/sokoban/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "sokoban", + "name": "Sokoban", + "shortName": "Sokoban", + "version": "0.01", + "description": "Classic Sokoban game (microban levels).", + "allow_emulator":false, + "icon": "sokoban.png", + "type": "app", + "tags": "game", + "screenshots": [{"url":"soko.png"}], + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"sokoban.app.js","url":"app.js"}, + {"name":"Microban.txt", "url":"Microban.txt"}, + {"name":"sokoban.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"sokoban.json"} + ] +} diff --git a/apps/sokoban/soko.png b/apps/sokoban/soko.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf0ae772d6f72cd3ddf6bd15fe812dbd9cb1f13 GIT binary patch literal 3664 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 CS<8?B literal 0 HcmV?d00001 diff --git a/apps/sokoban/sokoban.png b/apps/sokoban/sokoban.png new file mode 100644 index 0000000000000000000000000000000000000000..849b92d0114751cc03d97636e077d4e62c849373 GIT binary patch literal 863 zcmV-l1EBngP)-=LruQHqFqP{czKPwG_>nxyEVo|?W% zD^**9pobFjrXzwhd2d@4z4Xw6;>DukrAbAx6+GCg;0FpGL7&#cK z2I;xTBU^xMbTm{A(sPl#EkG`kcYZIb|LgGd1a$uZ)Q9Z>!gW;v@CPCr9o4guiMgk< z@ty;K>e>WILm+8NAWZ|(OGwh*Qof;k1&~NwSS}>L5|VZ60HiTM5}?!y>w7K9^XuX% zIxdj!2}zUJdlTLO2+3$?yk&eAk_e=>OGs`Ll7@w(Y4e<8r_+@Om~$QeQpD&P7hu0m zihtbj;sgNsR&m3qU!ZmZklqwCS|C3#<}|BJO~qqCNbNw;9hEc{S3O&dc}l58AUO<@ z2aVGL%3CEtl*db_Ux8X5)`1JXpH&u4Bg_-($JeG|xCM$PaAKpeYkEg5IBOf9QE+#w`=C~{@!_5}b)t<{oot`wY*Qp=!|V<72i;XJ!AO~s(l%FxyGGV=(;tAO9=FDq6`=7Hue9s50I9#V zwRdST1B`zq%$K^~4NXM>npLI-ke^umct#*6ofdDx^p4qi^D#0|it%Lw0OS!%uzq!u z1_9|}!Fwrfk0$^E`Mt^C$77OqyII^QRwh?_SgP@5189DIA literal 0 HcmV?d00001 From ebc95fda6920da806064eefea5ea0da782d261c8 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Thu, 17 Aug 2023 09:11:23 +0200 Subject: [PATCH 02/54] sokoban: renamed level file --- apps/sokoban/{Microban.txt => Microban.sok} | 0 apps/sokoban/app.js | 6 +++--- apps/sokoban/metadata.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename apps/sokoban/{Microban.txt => Microban.sok} (100%) diff --git a/apps/sokoban/Microban.txt b/apps/sokoban/Microban.sok similarity index 100% rename from apps/sokoban/Microban.txt rename to apps/sokoban/Microban.sok diff --git a/apps/sokoban/app.js b/apps/sokoban/app.js index 90b24ed11..b7d89d6ba 100644 --- a/apps/sokoban/app.js +++ b/apps/sokoban/app.js @@ -61,9 +61,9 @@ function next_map_offsets(filename, start_offset) { let config = s.readJSON("sokoban.json", true); if (config === undefined) { - let initial_offsets = next_map_offsets("Microban.txt", 0); + let initial_offsets = next_map_offsets("Microban.sok", 0); config = { - levels_sets: ["Microban.txt"], // all known files containing levels + levels_sets: ["Microban.sok"], // all known files containing levels levels_set: 0, // which set are we using ? current_maps: [0], // what is current map on each set ? offsets: [initial_offsets], // known offsets for each levels set (binary positions of maps in each file) @@ -461,4 +461,4 @@ Bangle.setLocked(false); current_map = config.current_map; offsets = config.offsets; -load_current_map(); \ No newline at end of file +load_current_map(); diff --git a/apps/sokoban/metadata.json b/apps/sokoban/metadata.json index 191cea1e0..7a4d5bc50 100644 --- a/apps/sokoban/metadata.json +++ b/apps/sokoban/metadata.json @@ -13,7 +13,7 @@ "readme": "README.md", "storage": [ {"name":"sokoban.app.js","url":"app.js"}, - {"name":"Microban.txt", "url":"Microban.txt"}, + {"name":"Microban.sok", "url":"Microban.sok"}, {"name":"sokoban.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"sokoban.json"} From ff8b799427684e9dc456388b072b00acd8e3231c Mon Sep 17 00:00:00 2001 From: David Peer Date: Fri, 18 Aug 2023 17:51:14 +0200 Subject: [PATCH 03/54] Added option to show weather --- apps/edgeclk/ChangeLog | 1 + apps/edgeclk/README.md | 2 ++ apps/edgeclk/app.js | 33 ++++++++++++++++++++++++++++++--- apps/edgeclk/metadata.json | 4 ++-- apps/edgeclk/screenshot4.png | Bin 0 -> 2665 bytes apps/edgeclk/settings.js | 13 +++++++++++++ 6 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 apps/edgeclk/screenshot4.png diff --git a/apps/edgeclk/ChangeLog b/apps/edgeclk/ChangeLog index da75dfbae..b96d7207d 100644 --- a/apps/edgeclk/ChangeLog +++ b/apps/edgeclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial release. 0.02: Fix reset of progress bars on midnight. Fix display of 100k+ steps. +0.03: Added option to display weather. diff --git a/apps/edgeclk/README.md b/apps/edgeclk/README.md index 535a5e9df..cf2d0dd76 100644 --- a/apps/edgeclk/README.md +++ b/apps/edgeclk/README.md @@ -3,6 +3,7 @@ ![Screenshot](screenshot.png) ![Screenshot](screenshot2.png) ![Screenshot](screenshot3.png) +![Screenshot](screenshot4.png) Tinxx presents you a clock with as many straight edges as possible to allow for a crisp look and perfect readability. It comes with a custom font to display weekday, date, time, and steps. Also displays battery percentage while charging. @@ -15,6 +16,7 @@ The appearance is highly configurable. In the settings menu you can: - Switch between 24h and 12h clock. - Hide or display seconds.* - Show AM/PM in place of the seconds. +- Show weather temperature and icon in place of the seconds. - Set the daily step goal. - En- or disable the individual progress bars. - Set if your week should start with Monday or Sunday (for week progress bar). diff --git a/apps/edgeclk/app.js b/apps/edgeclk/app.js index 9f28e2588..5bfa77b09 100644 --- a/apps/edgeclk/app.js +++ b/apps/edgeclk/app.js @@ -7,7 +7,8 @@ monthFirst: true, twentyFourH: true, showAmPm: false, - showSeconds: true, + showSeconds: false, + showWeather: true, stepGoal: 10000, stepBar: true, weekBar: true, @@ -15,7 +16,6 @@ dayBar: true, }, require('Storage').readJSON('edgeclk.settings.json', true) || {}); - /* Runtime Variables ------------------------------------------------------------------------------*/ @@ -51,6 +51,30 @@ } else { drawSteps(stepsOnlyCount); } + + drawWeather(); + }; + + const drawWeather = function () { + if (!settings.showWeather){ + return; + } + + g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9) + g.setFontAlign(1, 1); // right bottom + + try{ + const weather = require('weather'); + const w = weather.get(); + let temp = parseInt(w.temp-273.15); + temp = temp < 0 ? '\\' + String(temp*-1) : String(temp); + + g.drawString(temp, g.getWidth()-40, g.getHeight() - 1, true); + + weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 15); + } catch(e) { + g.drawString("ERR", g.getWidth()-3, g.getHeight() - 1, true); + } }; const drawDate = function (date) { @@ -135,7 +159,8 @@ g.setFontAlign(-1, 1); // left bottom const steps = Bangle.getHealthStatus('day').steps; - g.drawString((steps < 100000 ? steps.toString() : ((steps / 1000).toFixed(0) + 'K')).padEnd(5, '_'), + const toKSteps = settings.showWeather ? 1000 : 100000; + g.drawString((steps < toKSteps ? steps.toString() : ((steps / 1000).toFixed(0) + 'K')).padEnd(5, '_'), iconSize[0] + 6, g.getHeight() - 1, true); if (onlyCount === true) { @@ -229,12 +254,14 @@ // However, to save power while on battery only step count will get updated. // This will update icon and progress bar as well: if (!charging) drawSteps(); + drawWeather(); }; const onHealth = function () { if (!lcdPower || charging) return; // This will update progress bar and icon: drawSteps(); + drawWeather(); }; const onLock = function (locked) { diff --git a/apps/edgeclk/metadata.json b/apps/edgeclk/metadata.json index 3f72be77a..0d53cd008 100644 --- a/apps/edgeclk/metadata.json +++ b/apps/edgeclk/metadata.json @@ -2,11 +2,11 @@ "id": "edgeclk", "name": "Edge Clock", "shortName": "Edge Clock", - "version": "0.02", + "version": "0.03", "description": "Crisp clock with perfect readability.", "readme": "README.md", "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot2.png"}, {"url":"screenshot3.png"}], + "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot2.png"}, {"url":"screenshot3.png"}, {"url":"screenshot4.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/edgeclk/screenshot4.png b/apps/edgeclk/screenshot4.png new file mode 100644 index 0000000000000000000000000000000000000000..66ec85c898aa6f87081bad5d75059dbc528cd2fa GIT binary patch literal 2665 zcmYjTc~sKb7k|Kj4r-cdnWUCFQ(;bJrdBScscmY?;v}ieWM-IKiW>%B`?Z=&=2B+n zIA$6em`u3^d|O;9jhb=Ijgitq6wOd^YRRqr7^GH+SU*o!z&B0Hi;f?gn8kQA+p=cT!%yrE8F0ufN zWfpZ+C}3Wi)J1n473)yZa$d;#pcjtp9$9KJkR#X}i%r;cvRH!EhD#&r-ln|wnaa;} ziTM$>K4cT>j{ulVg4Medg-N;7a;$7{mt$+uZC+%P@cD?1BA~-6SYO|kD80FgDa{`T0G%QL;I_B{Pssu6=^gsRe^G11PMK)v@)7Nf7V+85~jBKUd!TP=Yko(TQ1zmN4uk zT?9j6qXrq?ypN5oy6gF=kbW*Vh(A9Ts0w0&iBA;;we}!%SA3#;9?|n8L#xWE-c65`MGAar(d-od#pQ}ev#nO*1!P-`er_nxihF;KL3Z* zce+fdRbtG+AKf{OE&tlyFi6>&7p!~`7jy2YqC#m2^KoZ!gW(nUEd8okdabVgvP{vZ z8>2qp*V)Biu1BWGBm0xt!{x%OvAAC2Y?GnBo-n2w<{aP|euv^ias;)z$&Q@Y?}lrh ze@X5Z_OJd^YSd=y_=R}B*+?1^+DO;y4CBApSgjjcmq(y#LQn}MkVc|A_dSI+7A zd-yr;#={FkpetvcC_@qH9&R7)HLf0MUyOc!Gh>-WYww`tn5pt zlQHCURfX!odD~*EN`SD9cS)D{z5)PyY)kb{^?Tcy@<*Bn8r1nZTzXid?)86BBwIXe zmUP-6^SAI#01ltqYnY*qSQ_n*i_iC;@EIuH$^>&XOS>5qobi0SHrfVTjXc$HLKcU1 z@F4|bHD#~#|CiY`OIf`ja_$y7+Rr$41z1H$jG~S(Y3TRXPVMH9icbO+4346!*W3khHqwpb63-Y+L3TZM=StAkMzsY$04Exl#%J|k9 zfh-YtoDOu>*tl=g^a*X$w;iaJe8d-yC#3P@25d^<(3mVl4jmT zs#f*~*E+apo>snFrNqC|zmqVavMc_8n!OvW5MkTC(^D?gc%H}&Tv2;bWkJ)29NS-np&Ce?sJ)h**>6UeJR@ELJlnnH6Xi(4dfmscJ^pTdRZ zFH~@9cMr*!{Yu^xhk0g0k84PQm5DBV{|<+WKSy`oX-3g}qE|NhCy))Em4An%)#~k9 z_*xnW`qz?}_Qg0+VO_@WudjECygZ{e$5R?SVWe~n7Ki0fsAfCoOtE%_P8X7Al+t! zK7xL2Hr%HC(K9s{3GUp;dd5>v@7@*LSclZp`&Em$7YioOXxwrsgS%&CvDrkWwxAIS zQWB8fgDPvpTYLn%Ml+gY!e$o?VNBgJdEZfK@ zb{33>ceEg@&LbZiu(0R33iN$<0R=qWNYmX%IWo6477bZA5yMx&4__K)UAx7yJsHo) zi)eWu)E_P>*PAdUHZ^t^|4hQ(+PSk|eLF?)D4ft_H$|Wam?^DZ*uQufwTO)gdG6!ojz)-=HnPex7)zRhir{jD10{^8ck+3GzqkVPKEjVK=uYC* zQxbbA>k@)PDlXUMo7c2#15ddPl^I{BB3A^A zyXnD!-{;kL-s|EkHKU7TK{a_HC(AAPr!oB@i+;mji+dv|l?@pMSpo8Vn%)dGFm%il5C;P&RD@3+;`6o&#n)KU`%v(D@Y@*sY zv->T$a^yNEjX3AbO0Trr)%IKhBZ|O&?5ps2Oh^h1R?X30UwLFrxk}49zN&vNdxRKX zN((-wMOql6gZK`qkro^=Ji#p3cw=I7a!6C=M+*%4IGTyLxVO?R=aY>}{dWWh-M#m7 IT&d^(3xcHytN;K2 literal 0 HcmV?d00001 diff --git a/apps/edgeclk/settings.js b/apps/edgeclk/settings.js index 205dc5170..d7eff58d5 100644 --- a/apps/edgeclk/settings.js +++ b/apps/edgeclk/settings.js @@ -11,6 +11,7 @@ stepGoal: 10000, stepBar: true, weekBar: true, + showWeather: false, mondayFirst: true, dayBar: true, }; @@ -57,6 +58,7 @@ settings.showAmPm = !settings.showAmPm; // TODO can this be visually changed? if (settings.showAmPm && settings.showSeconds) settings.showSeconds = false; + if (settings.showAmPm && settings.showWeather) settings.showWeather = false; save(); }, }, @@ -66,6 +68,17 @@ settings.showSeconds = !settings.showSeconds; // TODO can this be visually changed? if (settings.showSeconds && settings.showAmPm) settings.showAmPm = false; + if (settings.showSeconds && settings.showWeather) settings.showWeather = false; + save(); + }, + }, + 'Show Weather': { + value: settings.showWeather, + onchange: () => { + settings.showWeather = !settings.showWeather; + // TODO can this be visually changed? + if (settings.showWeather && settings.showAmPm) settings.showAmPm = false; + if (settings.showWeather && settings.showSeconds) settings.showSeconds = false; save(); }, }, From 03b12614c3c0aa07240caa1eb29d5ba64d7e5cbd Mon Sep 17 00:00:00 2001 From: David Peer Date: Fri, 18 Aug 2023 17:53:46 +0200 Subject: [PATCH 04/54] Changed default --- apps/edgeclk/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/edgeclk/app.js b/apps/edgeclk/app.js index 5bfa77b09..45dd0c4c2 100644 --- a/apps/edgeclk/app.js +++ b/apps/edgeclk/app.js @@ -7,8 +7,8 @@ monthFirst: true, twentyFourH: true, showAmPm: false, - showSeconds: false, - showWeather: true, + showSeconds: true, + showWeather: false, stepGoal: 10000, stepBar: true, weekBar: true, From 817607287c918f0cdfc74817f798406b01f8af57 Mon Sep 17 00:00:00 2001 From: David Peer Date: Fri, 18 Aug 2023 18:35:58 +0200 Subject: [PATCH 05/54] Ensure same default settings in app.js and settings.js --- apps/edgeclk/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/edgeclk/settings.js b/apps/edgeclk/settings.js index d7eff58d5..6f38e774c 100644 --- a/apps/edgeclk/settings.js +++ b/apps/edgeclk/settings.js @@ -8,10 +8,10 @@ twentyFourH: true, showAmPm: false, showSeconds: true, + showWeather: false, stepGoal: 10000, stepBar: true, weekBar: true, - showWeather: false, mondayFirst: true, dayBar: true, }; From a335acf17eb83f13fe5efb569ab560abc8e85e2a Mon Sep 17 00:00:00 2001 From: David Peer Date: Fri, 18 Aug 2023 18:37:51 +0200 Subject: [PATCH 06/54] Added contributors --- apps/edgeclk/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/edgeclk/README.md b/apps/edgeclk/README.md index cf2d0dd76..90f6443fc 100644 --- a/apps/edgeclk/README.md +++ b/apps/edgeclk/README.md @@ -24,3 +24,8 @@ The appearance is highly configurable. In the settings menu you can: *) Hiding seconds should further reduce power consumption as the draw interval is prolonged as well. The clock implements Fast Loading for faster switching to and fro. + +## Contributors + - [tinxx](https://github.com/tinxx) + - [peerdavid](https://github.com/peerdavid) + \ No newline at end of file From 2d7fdd88c2575915dd95f080d1f211cfa9b2e1af Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Fri, 18 Aug 2023 20:40:24 +0200 Subject: [PATCH 07/54] sched/calendar: Fix timezone handling on ical --- apps/calendar/interface.html | 9 +++++++-- apps/sched/interface.html | 11 ++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/calendar/interface.html b/apps/calendar/interface.html index 280a96c0b..ea64632f8 100644 --- a/apps/calendar/interface.html +++ b/apps/calendar/interface.html @@ -28,11 +28,16 @@ function readFile(input) { for(let i=0; i { - const jCalData = ICAL.parse(reader.result); + const icalText = reader.result.substring(reader.result.indexOf("BEGIN:VCALENDAR")); // remove html before data + const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); + const vtz = comp.getFirstSubcomponent('vtimezone'); + const tz = new ICAL.Timezone(vtz); + // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { - event = new ICAL.Event(vevent); + const event = new ICAL.Event(vevent); + event.startDate.zone = tz; holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists const holiday = eventToHoliday(event); diff --git a/apps/sched/interface.html b/apps/sched/interface.html index cd2c9c595..53b443371 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -16,14 +16,18 @@ function readFile(input) { for(let i=0; i { - const jCalData = ICAL.parse(reader.result); + const icalText = reader.result.substring(reader.result.indexOf("BEGIN:VCALENDAR")); // remove html before data + const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); + const vtz = comp.getFirstSubcomponent('vtimezone'); + const tz = new ICAL.Timezone(vtz); + // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { event = new ICAL.Event(vevent); const exists = alarms.some(alarm => alarm.id === event.uid); - const alarm = eventToAlarm(event, offsetMinutes*60*1000); + const alarm = eventToAlarm(event, tz, offsetMinutes*60*1000); renderAlarm(alarm, exists); if (exists) { @@ -68,7 +72,8 @@ function getAlarmDefaults() { }; } -function eventToAlarm(event, offsetMs) { +function eventToAlarm(event, tz, offsetMs) { + event.startDate.zone = tz; const dateOrig = event.startDate.toJSDate(); const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig; From 3158d945aaba454cf40c5901f96a94bed8fb39d7 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 18 Aug 2023 14:17:53 +0200 Subject: [PATCH 08/54] Add Space Weaver -- vector map application. This really needs more work (as documented in README), but this is already quite useful. Please check app.js file -- I used library for conversion between xyz and lat/lon, so its license applies. It seems to be compatible with bangle apps license. --- apps/spacew/README.md | 43 +++ apps/spacew/app-icon.js | 2 + apps/spacew/app.js | 620 ++++++++++++++++++++++++++++++++++ apps/spacew/app.png | Bin 0 -> 3263 bytes apps/spacew/metadata.json | 13 + apps/spacew/prep/minitar.js | 82 +++++ apps/spacew/prep/prepare.json | 18 + apps/spacew/prep/prepare.sh | 18 + apps/spacew/prep/split.js | 177 ++++++++++ apps/spacew/prep/stats.sh | 22 ++ 10 files changed, 995 insertions(+) create mode 100644 apps/spacew/README.md create mode 100644 apps/spacew/app-icon.js create mode 100644 apps/spacew/app.js create mode 100644 apps/spacew/app.png create mode 100644 apps/spacew/metadata.json create mode 100755 apps/spacew/prep/minitar.js create mode 100644 apps/spacew/prep/prepare.json create mode 100755 apps/spacew/prep/prepare.sh create mode 100755 apps/spacew/prep/split.js create mode 100755 apps/spacew/prep/stats.sh diff --git a/apps/spacew/README.md b/apps/spacew/README.md new file mode 100644 index 000000000..4f2ca3f00 --- /dev/null +++ b/apps/spacew/README.md @@ -0,0 +1,43 @@ +# Space Weaver ![](app.png) + +Vector map + +Written by: [Pavel Machek](https://github.com/pavelmachek) + +Space Weaver is application for displaying vector maps. It is +currently suitable for developers, and more work is needed. + +Maps can be created from openstreetmap extracts. Those are cut using +osmosis, then translated into geojson. Geojson is further processes to +add metadata such as colors, and to split it into xyz tiles, while +keeping geojson format. Tiles are then merged into single file, which +can be uploaded to the filesystem. Index at the end provides locations +of the tiles. + +## Preparing data + +Tools in spacew/prep can be used to prepare data. + +You'll need to edit prepare.sh to point it to suitable osm extract, +and you'll need to select area of interest. Start experiments with +small area. You may want to delete cstocs and provide custom +conversion to ascii. + +Details of which features are visible at what zoom levels can be +configured in split.js. This can greatly affect file sizes. Then +there's "meta.max_zoom = 17" setting, reduce it if file is too big. + +For initial experiments, configure things so that mtar file is around +500KB. (I had troubles with big files, both on hardware and to lesser +extent on simulator. In particular, mtar seemed to be corrupted after +emulator window was closed.) + +## Future Development + +Directories at the end of .mtar should be hashed, not linear searched. + +Geojson is not really suitable as it takes a lot of storage. + +It would be nice to support polygons. + +Web-based tool for preparing maps would be nice. \ No newline at end of file diff --git a/apps/spacew/app-icon.js b/apps/spacew/app-icon.js new file mode 100644 index 000000000..27b6e2662 --- /dev/null +++ b/apps/spacew/app-icon.js @@ -0,0 +1,2 @@ +require("heatshrink").decompress(atob("mEwwkE/8Ql//j//AAUD//yAgILBAAXzBQMxAoMwn4XKBIgXCmAXEh4XF+IJGC4XxAoMgl/zgX/nASBBgPwIoIEBmYBBI4ug1/6hX/zOf+UBEIMP+UC+eZAIPyhP/yAnB0Ef+QXBnM/GgUwh4ECwX/wYvCkIvB+BrBA4JsFAQMiL4gRBA4XxNYIlBBgQGBiJXEBQRnBiYoEiQXFgURT4YAB+QXBS4RTCJoQMBj4gBWQPwN4IKCNgLHDRAIlDEgIxBC4zHBJITACC4gMB+MfAIJCCRIU/GIIGCEoLyCBgQOCgZAEBAL5CC4UvC4oFBMIJ9CCAQMBPwbABKoYMBJ4KpBZQgKBVwnyh/wKoQMBVoUgn4XFmTGEgfxC4QKBCQRKBeAYtBkYXFXYIFBkTfCSgMfIIYbBdwTADJIIEBkYEDAYKyDC4J9DKoSFDiZMDGYKCDkbWEKoUzIQQREHQIFDifzBQYXGIIIMDkDwDN4IXFIIIXBJQMhEQqCCT4IWENoUCC4MvXwTjCiZqBEQIXGNoITBC4LRDEQMDHQbWEAAUDIYPzmabEEQIXDmYXGiUgFAMyLASQDXgPzj7uEQobNB+MxWYsgHQKSBEQqFCUYPwUwgKCHQUvEQqFCkAXCBQ0Qn/xmYXH+IXB+S+ESAUAEQMzHQqFCgEvmS+EBQUBl/wUw4MDmS+ESAcf+ExC44MCmS+ESAcPmAvI/8hh8iNY8wgcwaw4MCh8hNY/wC5kDTwKbHgThGEgsQQZMhdw61CgSmGAAUANRAkCgUTBZEQiRSHHga+HNYUCC5I8BXw4XCgIWJHgJTJ+IXJHAIXB+eTJoIJD+fyC4LABYQWZBQOYC4Mf+eS/85DgIJBxMygAFB+YUBC4YqBkAoBAIM5n4JCAgIvBwYBCNgyDKTRIXM+YXFA=")) + diff --git a/apps/spacew/app.js b/apps/spacew/app.js new file mode 100644 index 000000000..e438799f6 --- /dev/null +++ b/apps/spacew/app.js @@ -0,0 +1,620 @@ +/* original openstmap.js */ + +/* OpenStreetMap plotting module. + +Usage: + +var m = require("openstmap"); +// m.lat/lon are now the center of the loaded map +m.draw(); // draw centered on the middle of the loaded map + +// plot gps position on map +Bangle.on('GPS',function(f) { + if (!f.fix) return; + var p = m.latLonToXY(fix.lat, fix.lon); + g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2); +}); + +// recenter and redraw map! +function center() { + m.lat = fix.lat; + m.lon = fix.lon; + m.draw(); +} + +// you can even change the scale - eg 'm/scale *= 2' + +*/ + +var exports = {}; +var m = exports; +m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{ + let map = require("Storage").readJSON(f); + map.center = Bangle.project({lat:map.lat,lon:map.lon}); + return map; +}); +// we base our start position on the middle of the first map +if (m.maps[0] != undefined) { + m.map = m.maps[0]; + m.scale = m.map.scale; // current scale (based on first map) + m.lat = m.map.lat; // position of middle of screen + m.lon = m.map.lon; // position of middle of screen +} else { + m.scale = 20; + m.lat = 50; + m.lon = 14; +} + +exports.draw = function() { + var cx = g.getWidth()/2; + var cy = g.getHeight()/2; + var p = Bangle.project({lat:m.lat,lon:m.lon}); + m.maps.forEach((map,idx) => { + var d = map.scale/m.scale; + var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx; + var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy; + var o = {}; + var s = map.tilesize; + if (d!=1) { // if the two are different, add scaling + s *= d; + o.scale = d; + } + //console.log(ix,iy); + var tx = 0|(ix/s); + var ty = 0|(iy/s); + var ox = (tx*s)-ix; + var oy = (ty*s)-iy; + var img = require("Storage").read(map.fn); + // fix out of range so we don't have to iterate over them + if (tx<0) { + ox+=s*-tx; + tx=0; + } + if (ty<0) { + oy+=s*-ty; + ty=0; + } + var mx = g.getWidth(); + var my = g.getHeight(); + for (var x=ox,ttx=tx; x ac * expansion) && (x = ac * expansion); + (y > ac) && (y = ac); + //(x < 0) && (x = 0); + //(y < 0) && (y = 0); + return [x, y]; +} + +// Convert bbox to xyx bounds +// +// - `bbox` {Number} bbox in the form `[w, s, e, n]`. +// - `zoom` {Number} zoom. +// - `tms_style` {Boolean} whether to compute using tms-style. +// - `srs` {String} projection of input bbox (WGS84|900913). +// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties. +xyz = function(bbox, zoom, tms_style, srs) { + // If web mercator provided reproject to WGS84. + if (srs === '900913') { + bbox = this.convert(bbox, 'WGS84'); + } + + var ll = [bbox[0], bbox[1]]; // lower left + var ur = [bbox[2], bbox[3]]; // upper right + var px_ll = px(ll, zoom); + var px_ur = px(ur, zoom); + // Y = 0 for XYZ is the top hence minY uses px_ur[1]. + var size = 256; + var x = [ Math.floor(px_ll[0] / size), Math.floor((px_ur[0] - 1) / size) ]; + var y = [ Math.floor(px_ur[1] / size), Math.floor((px_ll[1] - 1) / size) ]; + var bounds = { + minX: Math.min.apply(Math, x) < 0 ? 0 : Math.min.apply(Math, x), + minY: Math.min.apply(Math, y) < 0 ? 0 : Math.min.apply(Math, y), + maxX: Math.max.apply(Math, x), + maxY: Math.max.apply(Math, y) + }; + if (tms_style) { + var tms = { + minY: (Math.pow(2, zoom) - 1) - bounds.maxY, + maxY: (Math.pow(2, zoom) - 1) - bounds.minY + }; + bounds.minY = tms.minY; + bounds.maxY = tms.maxY; + } + return bounds; +}; + +// Convert screen pixel value to lon lat +// +// - `px` {Array} `[x, y]` array of geographic coordinates. +// - `zoom` {Number} zoom level. +function ll(px, zoom) { + var size = 256 * Math.pow(2, zoom); + var bc = (size / 360); + var cc = (size / (2 * Math.PI)); + var zc = size / 2; + var g = (px[1] - zc) / -cc; + var lon = (px[0] - zc) / bc; + var R2D = 180 / Math.PI; + var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); + return [lon, lat]; +} + +// Convert tile xyz value to bbox of the form `[w, s, e, n]` +// +// - `x` {Number} x (longitude) number. +// - `y` {Number} y (latitude) number. +// - `zoom` {Number} zoom. +// - `tms_style` {Boolean} whether to compute using tms-style. +// - `srs` {String} projection for resulting bbox (WGS84|900913). +// - `return` {Array} bbox array of values in form `[w, s, e, n]`. +bbox = function(x, y, zoom, tms_style, srs) { + var size = 256; + + // Convert xyz into bbox with srs WGS84 + if (tms_style) { + y = (Math.pow(2, zoom) - 1) - y; + } + // Use +y to make sure it's a number to avoid inadvertent concatenation. + var ll_ = [x * size, (+y + 1) * size]; // lower left + // Use +x to make sure it's a number to avoid inadvertent concatenation. + var ur = [(+x + 1) * size, y * size]; // upper right + var bbox = ll(ll_, zoom).concat(ll(ur, zoom)); + + // If web mercator requested reproject to 900913. + if (srs === '900913') { + return this.convert(bbox, '900913'); + } else { + return bbox; + } +}; + +/* original openstmap_app.js */ + +//var m = require("openstmap"); +var HASWIDGETS = true; +var R; +var fix = {}; +var mapVisible = false; +var hasScrolled = false; +var settings = require("Storage").readJSON("openstmap.json",1)||{}; +var points; +var startDrag = 0; + +// Redraw the whole page +function redraw(qual) { + if (1) drawAll(qual); + g.setClipRect(R.x,R.y,R.x2,R.y2); + if (0) m.draw(); + drawPOI(); + drawMarker(); + // if track drawing is enabled... + if (settings.drawTrack) { + if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["gpsrec"].plotTrack(m); + } + if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["recorder"].plotTrack(m); + } + } + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); +} + +// Draw the POIs +function drawPOI() { + if (1) return; + /* var waypoints = require("waypoints").load(); FIXME */ g.setFont("Vector", 18); + waypoints.forEach((wp, idx) => { + var p = m.latLonToXY(wp.lat, wp.lon); + var sz = 2; + g.setColor(0,0,0); + g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz); + g.setColor(0,0,0); + g.drawString(wp.name, p.x, p.y); + print(wp.name); + }) +} + + + +// Draw the marker for where we are +function drawMarker() { + if (!fix.fix) return; + var p = m.latLonToXY(fix.lat, fix.lon); + g.setColor(1,0,0); + g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2); +} + +Bangle.on('GPS',function(f) { + fix=f; + if (HASWIDGETS) WIDGETS["sats"].draw(WIDGETS["sats"]); + if (mapVisible) drawMarker(); +}); +Bangle.setGPSPower(1, "app"); + +if (HASWIDGETS) { + Bangle.loadWidgets(); + WIDGETS["sats"] = { area:"tl", width:48, draw:w=>{ + var txt = (0|fix.satellites)+" Sats"; + if (!fix.fix) txt += "\nNO FIX"; + g.reset().setFont("6x8").setFontAlign(0,0) + .drawString(txt,w.x+24,w.y+12); + } + }; + Bangle.drawWidgets(); +} +R = Bangle.appRect; + +function showMap() { + mapVisible = true; + g.reset().clearRect(R); + redraw(0); + emptyMap(); +} + +function emptyMap() { + Bangle.setUI({mode:"custom",drag:e=>{ + if (e.b) { + if (!startDrag) + startDrag = getTime(); + g.setClipRect(R.x,R.y,R.x2,R.y2); + g.scroll(e.dx,e.dy); + m.scroll(e.dx,e.dy); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + hasScrolled = true; + print("Has scrolled"); + } else if (hasScrolled) { + delta = getTime() - startDrag; + startDrag = 0; + hasScrolled = false; + print("Done", delta, e.x, e.y); + qual = 0; + if (delta < 0.2) { + if (e.x < g.getWidth() / 2) { + if (e.y < g.getHeight() / 2) { + m.scale /= 2; + } else { + m.scale *= 2; + } + } else { + if (e.y < g.getHeight() / 2) { + qual = 2; + } else { + qual = 4; + } + } + } + g.reset().clearRect(R); + redraw(qual); + } + }, btn: btn=>{ + mapVisible = false; + var menu = {"":{title:"Map"}, + "< Back": ()=> showMap(), + /*LANG*/"Zoom In": () =>{ + m.scale /= 2; + showMap(); + }, + /*LANG*/"Zoom Out": () =>{ + m.scale *= 2; + showMap(); + }, + /*LANG*/"Draw Track": { + value : !!settings.drawTrack, + onchange : v => { settings.drawTrack=v; require("Storage").writeJSON("openstmap.json",settings); } + }, + /*LANG*/"Center Map": () =>{ + m.lat = m.map.lat; + m.lon = m.map.lon; + m.scale = m.map.scale; + showMap(); + }, + /*LANG*/"Benchmark": () =>{ + m.lat = 50.001; + m.lon = 14.759; + m.scale = 2; + g.reset().clearRect(R); + redraw(18); + print("Benchmark done (31 sec)"); + } + }; + if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ + m.lat = fix.lat; + m.lon = fix.lon; + showMap(); + }; + E.showMenu(menu); + }}); +} + +var gjson = null; + +function readTarFile(tar, f) { + const st = require('Storage'); + json_off = st.read(tar, 0, 16) * 1; + if (isNaN(json_off)) { + print("Don't have archive", tar); + return undefined; + } + while (1) { + json_len = st.read(tar, json_off, 6) * 1; + if (json_len == -1) + break; + json_off += 6; + json = st.read(tar, json_off, json_len); + //print("Have directory, ", json.length, "bytes"); + //print(json); + files = JSON.parse(json); + //print(files); + rec = files[f]; + if (rec) + return st.read(tar, rec.st, rec.si); + json_off += json_len; + } + return undefined; +} + +function loadVector(name) { + var t1 = getTime(); + print(".. Read", name); + //s = require("Storage").read(name); + var s = readTarFile("delme.mtar", name); + if (s == undefined) { + print("Don't have file", name); + return null; + } + var r = JSON.parse(s); + print(".... Read and parse took ", getTime()-t1); + return r; +} + +function drawPoint(a) { + lon = a.geometry.coordinates[0]; + lat = a.geometry.coordinates[1]; + + var p = m.latLonToXY(lat, lon); + var sz = 2; + if (a.properties["marker-color"]) { + g.setColor(a.properties["marker-color"]); + } + if (a.properties.marker_size == "small") + sz = 1; + if (a.properties.marker_size == "large") + sz = 4; + + g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz); + g.setColor(0,0,0); + g.setFont("Vector", 18).setFontAlign(-1,-1); + g.drawString(a.properties.name, p.x, p.y); + points ++; +} + +function drawLine(a, qual) { + lon = a.geometry.coordinates[0][0]; + lat = a.geometry.coordinates[0][1]; + i = 1; + step = 1; + len = a.geometry.coordinates.length; + step = step * qual; + var p1 = m.latLonToXY(lat, lon); + if (a.properties.stroke) { + g.setColor(a.properties.stroke); + } + while (i < len) { + lon = a.geometry.coordinates[i][0]; + lat = a.geometry.coordinates[i][1]; + var p2 = m.latLonToXY(lat, lon); + + //print(p1.x, p1.y, p2.x, p2.y); + g.drawLine(p1.x, p1.y, p2.x, p2.y); + if (i == len-1) + break; + i = i + step; + if (i>len) + i = len-1; + points ++; + p1 = p2; + g.flip(); + } +} + +function drawVector(gjson, qual) { + var d = gjson; + points = 0; + var t1 = getTime(); + + for (var a of d.features) { + if (a.type != "Feature") + print("Expecting feature"); + g.setColor(0,0,0); + // marker-size, marker-color, stroke + if (qual < 32 && a.geometry.type == "Point") + drawPoint(a); + if (qual < 8 && a.geometry.type == "LineString") + drawLine(a, qual); + } + print("....", points, "painted in", getTime()-t1, "sec"); +} + +function fname(lon, lat, zoom) { + var bbox = [lon, lat, lon, lat]; + var r = xyz(bbox, 13, false, "WGS84"); + //console.log('fname', r); + return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json'; +} + +function fnames(zoom) { + var bb = [m.lon, m.lat, m.lon, m.lat]; + var r = xyz(bb, zoom, false, "WGS84"); + while (1) { + var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84"); + var os = m.latLonToXY(bb2[3], bb2[0]); + if (os.x >= 0) + r.minX -= 1; + else if (os.y >= 0) + r.minY -= 1; + else break; + } + while (1) { + var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84"); + var os = m.latLonToXY(bb2[1], bb2[2]); + if (os.x <= g.getWidth()) + r.maxX += 1; + else if (os.y <= g.getHeight()) + r.maxY += 1; + else break; + } + print(".. paint range", r); + return r; +} + +function log2(x) { return Math.log(x) / Math.log(2); } + +function getZoom(qual) { + var z = 16-Math.round(log2(m.scale)); + z += qual; + z -= 0; + if (z < meta.min_zoom) + return meta.min_zoom; + if (z > meta.max_zoom) + return meta.max_zoom; + return z; +} + +function drawDebug(text, perc) { + g.setClipRect(0,0,R.x2,R.y); + g.reset(); + g.setColor(1,1,1).fillRect(0,0,R.x2,R.y); + g.setColor(1,0,0).fillRect(0,0,R.x2*perc,R.y); + g.setColor(0,0,0).setFont("Vector",15); + g.setFontAlign(0,0) + .drawString(text,80,10); + + g.setClipRect(R.x,R.y,R.x2,R.y2); + g.flip(); +} + +function drawAll(qual) { + var zoom = getZoom(qual); + var t1 = getTime(); + + drawDebug("Zoom "+zoom, 0); + + print("Draw all", m.scale, "->", zoom, "q", qual, "at", m.lat, m.lon); + var r = fnames(zoom); + var tiles = (r.maxY-r.minY+1) * (r.maxY-r.minY+1); + var num = 0; + drawDebug("Zoom "+zoom+" tiles "+tiles, 0); + for (y=r.minY; y<=r.maxY; y++) { + for (x=r.minX; x<=r.maxX; x++) { + + for (cnt=0; cnt<1000; cnt++) { + var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json'; + var gjson = loadVector(n); + if (!gjson) break; + drawVector(gjson, 1); + } + num++; + drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles); + } + } + g.flip(); + Bangle.drawWidgets(); + print("Load and paint in", getTime()-t1, "sec"); +} + +function initVector() { + var s = readTarFile("delme.mtar", "meta.json"); + meta = JSON.parse(s); + +} + +function introScreen() { + g.reset().clearRect(R); + g.setColor(0,0,0).setFont("Vector",25); + g.setFontAlign(0,0); + g.drawString("SpaceWeaver", 85,35); + g.setColor(0,0,0).setFont("Vector",18); + g.drawString("Vector maps", 85,55); + g.drawString("Zoom "+meta.min_zoom+".."+meta.max_zoom, 85,75); +} + + +m.scale = 76; +m.lat = 50.001; +m.lon = 14.759; + +initVector(); +introScreen(); +emptyMap(); diff --git a/apps/spacew/app.png b/apps/spacew/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0e52fa31684acc82bb0262f4fa05e31ef56151a3 GIT binary patch literal 3263 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 literal 0 HcmV?d00001 diff --git a/apps/spacew/metadata.json b/apps/spacew/metadata.json new file mode 100644 index 000000000..51bdb35b8 --- /dev/null +++ b/apps/spacew/metadata.json @@ -0,0 +1,13 @@ +{ "id": "spacew", + "name": "Space Weaver", + "version":"0.01", + "description": "Application for displaying vector maps", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "tags": "outdoors,gps,osm", + "storage": [ + {"name":"spacew.app.js","url":"app.js"}, + {"name":"spacew.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/spacew/prep/minitar.js b/apps/spacew/prep/minitar.js new file mode 100755 index 000000000..bf59e7a64 --- /dev/null +++ b/apps/spacew/prep/minitar.js @@ -0,0 +1,82 @@ +#!/usr/bin/nodejs + +var pc = 1; +var hack = 0; +const hs = require('./heatshrink.js'); + +if (pc) { + fs = require('fs'); + var print=console.log; +} else { + +} + +print("hello world"); + +function writeDir(json) { + json_str = JSON.stringify(json, "", " "); + dirent = '' + json_str.length; + while (dirent.length < 6) + dirent = dirent + ' '; + return dirent + json_str; +} + +function writeTar(tar, dir) { + var h_len = 16; + var cur = h_len; + files = fs.readdirSync(dir); + data = ''; + var directory = ''; + var json = {}; + for (f of files) { + d = fs.readFileSync(dir+f); + cs = d; + //cs = String.fromCharCode.apply(null, hs.compress(d)) + print("Processing", f, cur, d.length, cs.length); + //if (d.length == 42) continue; + data = data + cs; + var f_rec = {}; + f_rec.st = cur; + var len = d.length; + f_rec.si = len; + cur = cur + len; + json[f] = f_rec; + json_str = JSON.stringify(json, "", " "); + if (json_str.length < 16000) + continue; + directory += writeDir(json); + json = {}; + } + directory += writeDir(json); + directory += '-1 '; + + size = cur; + header = '' + size; + while (header.length < h_len) { + header = header+' '; + } + if (!hack) + fs.writeFileSync(tar, header+data+directory); + else + fs.writeFileSync(tar, directory); +} + +function readTarFile(tar, f) { + const st = require('Storage'); + json_off = st.read(tar, 0, 16) * 1; + print(json_off); + json = st.read(tar, json_off, -1); + //print(json); + files = JSON.parse(json); + //print(files); + rec = files[f]; + return st.read(tar, rec.st, rec.si); +} + +if (pc) + writeTar("delme.mtaz", "delme/"); +else { + print(readTarFile("delme.mtar", "ahoj")); + print(readTarFile("delme.mtar", "nazdar")); +} + diff --git a/apps/spacew/prep/prepare.json b/apps/spacew/prep/prepare.json new file mode 100644 index 000000000..33cb21a3c --- /dev/null +++ b/apps/spacew/prep/prepare.json @@ -0,0 +1,18 @@ +{ + "attributes": { + "type": false, + "id": false, + "version": false, + "changeset": false, + "timestamp": false, + "uid": false, + "user": false, + "way_nodes": false, + }, + "format_options": { + }, + "linear_tags": true, + "area_tags": false, + "exclude_tags": [], + "include_tags": [ "place", "name", "landuse", "highway" ] +} diff --git a/apps/spacew/prep/prepare.sh b/apps/spacew/prep/prepare.sh new file mode 100755 index 000000000..ac1db0019 --- /dev/null +++ b/apps/spacew/prep/prepare.sh @@ -0,0 +1,18 @@ +#!/bin/bash +if [ ".$1" == "-f" ]; then + I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf + #I=/data/gis/osm/dumps/zernovka.osm.bz2 + O=cr.geojson + rm delme.pbf $O + time osmium extract $I --bbox 14.7,49.9,14.8,50.1 -f pbf -o delme.pbf + time osmium export delme.pbf -c prepare.json -o $O + echo "Converting to ascii" + time cstocs utf8 ascii cr.geojson > cr_ascii.geojson + mv -f cr_ascii.geojson delme.json +fi +rm -r delme/; mkdir delme +./split.js +./minitar.js +ls -lS delme/*.json | head -20 +cat delme/* | wc -c +ls -l delme.mtar diff --git a/apps/spacew/prep/split.js b/apps/spacew/prep/split.js new file mode 100755 index 000000000..f25e8e338 --- /dev/null +++ b/apps/spacew/prep/split.js @@ -0,0 +1,177 @@ +#!/usr/bin/nodejs --max-old-space-size=5500 + +// npm install geojson-vt +// docs: https://github.com/mapbox/geojson-vt +// output format: https://github.com/mapbox/vector-tile-spec/ + +const fs = require('fs'); +const sphm = require('./sphericalmercator.js'); +var split = require('geojson-vt') + +// delme.json needs to be real file, symlink to geojson will not work +console.log("Loading json"); +var gjs = require("./delme.json"); + +function tileToLatLon(x, y, z, x_, y_) { + var [ w, s, e, n ] = merc.bbox(x, y, z); + var lon = (e - w) * (x_ / 4096) + w; + var lat = (n - s) * (1-(y_ / 4096)) + s; + //console.log("to ", lon, lat); + return [ lon, lat ]; +} + +function convGeom(tile, geom) { + var g = []; + for (i = 0; i< geom.length; i++) { + var x = geom[i][0]; + var y = geom[i][1]; + //console.log("Geometry: ", geom, geom.length, "X,y", x, y); + var pos = tileToLatLon(tile.x, tile.y, tile.z, x, y); + g.push(pos); + } + return g; +} + +function zoomPoint(tags) { + var z = 99; + + if (tags.place == "city") z = 4; + if (tags.place == "town") z = 8; + if (tags.place == "village") z = 10; + + return z; +} + +function paintPoint(tags) { + var p = {}; + + if (tags.place == "village") p["marker-color"] = "#ff0000"; + + return p; +} + +function zoomWay(tags) { + var z = 99; + + if (tags.highway == "motorway") z = 7; + if (tags.highway == "primary") z = 9; + if (tags.highway == "secondary") z = 13; + if (tags.highway == "tertiary") z = 14; + if (tags.highway == "unclassified") z = 16; + if (tags.highway == "residential") z = 17; + if (tags.highway == "track") z = 17; + if (tags.highway == "path") z = 17; + if (tags.highway == "footway") z = 17; + + return z; +} + +function paintWay(tags) { + var p = {}; + + if (tags.highway == "motorway" || tags.highway == "primary") /* ok */; + if (tags.highway == "secondary" || tags.highway == "tertiary") p.stroke = "#0000ff"; + if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") p.stroke = "#00ff00"; + if (tags.highway == "track") p.stroke = "#ff0000"; + if (tags.highway == "path" || tags.highway == "footway") p.stroke = "#800000"; + + return p; +} + +function writeFeatures(name, feat) +{ + var n = {}; + n.type = "FeatureCollection"; + n.features = feat; + + fs.writeFile(name+'.json', JSON.stringify(n), on_error); +} + +function toGjson(name, d, tile) { + var cnt = 0; + var feat = []; + for (var a of d) { + var f = {}; + var zoom = 99; + var p = {}; + f.properties = a.tags; + f.type = "Feature"; + f.geometry = {}; + if (a.type == 1) { + f.geometry.type = "Point"; + f.geometry.coordinates = convGeom(tile, a.geometry)[0]; + zoom = zoomPoint(a.tags); + p = paintPoint(a.tags); + } else if (a.type == 2) { + f.geometry.type = "LineString"; + f.geometry.coordinates = convGeom(tile, a.geometry[0]); + zoom = zoomWay(a.tags); + p = paintWay(a.tags); + } else { + //console.log("Unknown type", a.type); + } + //zoom -= 4; // Produces way nicer map, at expense of space. + if (tile.z < zoom) + continue; + f.properties = Object.assign({}, f.properties, p); + feat.push(f); + var s = JSON.stringify(feat); + if (s.length > 6000) { + console.log("tile too big, splitting", cnt); + writeFeatures(name+'-'+cnt++, feat); + feat = []; + } + } + writeFeatures(name+'-'+cnt, feat); + return n; +} + +function writeTile(name, d, tile) { + toGjson(name, d, tile) +} + +// By default, precomputes up to z30 +var merc = new sphm({ + size: 256, + antimeridian: true +}); + +//console.log(merc.ll([124, 123], 15)); +//console.log(merc.px([17734, 11102], 15)); +//console.log(merc.bbox(17734, 11102, 15)); +//return; + +console.log("Splitting data"); +var meta = {} +meta.min_zoom = 0; +meta.max_zoom = 17; // HERE + // = 16 ... split3 takes > 30 minutes + // = 13 ... 2 minutes +var index = split(gjs, Object.assign({ + maxZoom: meta.max_zoom, + indexMaxZoom: meta.max_zoom, + indexMaxPoints: 0, + tolerance: 30, +}), {}); +console.log("Producing output"); + +var output = {}; + +function on_error(e) { + if (e) { console.log(e); } +} + +var num = 0; +for (const id in index.tiles) { + const tile = index.tiles[id]; + const z = tile.z; + console.log(num++, ":", tile.x, tile.y, z); + var d = index.getTile(z, tile.x, tile.y).features; + //console.log(d); + var n = `delme/z${z}-${tile.x}-${tile.y}` ; + //output[n] = d; + //console.log(n); + writeTile(n, d, tile) +} + +fs.writeFile('delme/meta.json', JSON.stringify(meta), on_error); diff --git a/apps/spacew/prep/stats.sh b/apps/spacew/prep/stats.sh new file mode 100755 index 000000000..6c10ea1b0 --- /dev/null +++ b/apps/spacew/prep/stats.sh @@ -0,0 +1,22 @@ +#!/bin/bash +zoom() { + echo "Zoom $1" + cat delme/z$1-* | wc -c + echo "M..k..." +} + +echo "Total data" +cat delme/* | wc -c +echo "M..k..." +zoom 18 +zoom 17 +zoom 16 +zoom 15 +zoom 14 +zoom 13 +zoom 12 +zoom 11 +zoom 10 +echo "Zoom 1..9" +cat delme/z?-* | wc -c +echo "M..k..." From 1c96a66db92b2694830390afbfdbc166d39f8703 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 18 Aug 2023 22:49:30 +0200 Subject: [PATCH 09/54] Fix tabs-vs-spaces warning, remove extra debugging. --- apps/spacew/prep/minitar.js | 46 ++++++++++------------ apps/spacew/prep/split.js | 78 ++++++++++++++++--------------------- 2 files changed, 55 insertions(+), 69 deletions(-) diff --git a/apps/spacew/prep/minitar.js b/apps/spacew/prep/minitar.js index bf59e7a64..e07c47049 100755 --- a/apps/spacew/prep/minitar.js +++ b/apps/spacew/prep/minitar.js @@ -11,13 +11,11 @@ if (pc) { } -print("hello world"); - function writeDir(json) { json_str = JSON.stringify(json, "", " "); dirent = '' + json_str.length; while (dirent.length < 6) - dirent = dirent + ' '; + dirent = dirent + ' '; return dirent + json_str; } @@ -29,23 +27,23 @@ function writeTar(tar, dir) { var directory = ''; var json = {}; for (f of files) { - d = fs.readFileSync(dir+f); - cs = d; - //cs = String.fromCharCode.apply(null, hs.compress(d)) - print("Processing", f, cur, d.length, cs.length); - //if (d.length == 42) continue; - data = data + cs; - var f_rec = {}; - f_rec.st = cur; - var len = d.length; - f_rec.si = len; - cur = cur + len; - json[f] = f_rec; - json_str = JSON.stringify(json, "", " "); - if (json_str.length < 16000) - continue; - directory += writeDir(json); - json = {}; + d = fs.readFileSync(dir+f); + cs = d; + //cs = String.fromCharCode.apply(null, hs.compress(d)) + print("Processing", f, cur, d.length, cs.length); + //if (d.length == 42) continue; + data = data + cs; + var f_rec = {}; + f_rec.st = cur; + var len = d.length; + f_rec.si = len; + cur = cur + len; + json[f] = f_rec; + json_str = JSON.stringify(json, "", " "); + if (json_str.length < 16000) + continue; + directory += writeDir(json); + json = {}; } directory += writeDir(json); directory += '-1 '; @@ -53,12 +51,12 @@ function writeTar(tar, dir) { size = cur; header = '' + size; while (header.length < h_len) { - header = header+' '; + header = header+' '; } if (!hack) - fs.writeFileSync(tar, header+data+directory); + fs.writeFileSync(tar, header+data+directory); else - fs.writeFileSync(tar, directory); + fs.writeFileSync(tar, directory); } function readTarFile(tar, f) { @@ -66,9 +64,7 @@ function readTarFile(tar, f) { json_off = st.read(tar, 0, 16) * 1; print(json_off); json = st.read(tar, json_off, -1); - //print(json); files = JSON.parse(json); - //print(files); rec = files[f]; return st.read(tar, rec.st, rec.si); } diff --git a/apps/spacew/prep/split.js b/apps/spacew/prep/split.js index f25e8e338..3d6f81b63 100755 --- a/apps/spacew/prep/split.js +++ b/apps/spacew/prep/split.js @@ -16,18 +16,16 @@ function tileToLatLon(x, y, z, x_, y_) { var [ w, s, e, n ] = merc.bbox(x, y, z); var lon = (e - w) * (x_ / 4096) + w; var lat = (n - s) * (1-(y_ / 4096)) + s; - //console.log("to ", lon, lat); return [ lon, lat ]; } function convGeom(tile, geom) { var g = []; for (i = 0; i< geom.length; i++) { - var x = geom[i][0]; - var y = geom[i][1]; - //console.log("Geometry: ", geom, geom.length, "X,y", x, y); - var pos = tileToLatLon(tile.x, tile.y, tile.z, x, y); - g.push(pos); + var x = geom[i][0]; + var y = geom[i][1]; + var pos = tileToLatLon(tile.x, tile.y, tile.z, x, y); + g.push(pos); } return g; } @@ -91,36 +89,36 @@ function toGjson(name, d, tile) { var cnt = 0; var feat = []; for (var a of d) { - var f = {}; - var zoom = 99; - var p = {}; - f.properties = a.tags; - f.type = "Feature"; - f.geometry = {}; - if (a.type == 1) { - f.geometry.type = "Point"; - f.geometry.coordinates = convGeom(tile, a.geometry)[0]; - zoom = zoomPoint(a.tags); - p = paintPoint(a.tags); - } else if (a.type == 2) { - f.geometry.type = "LineString"; - f.geometry.coordinates = convGeom(tile, a.geometry[0]); - zoom = zoomWay(a.tags); - p = paintWay(a.tags); - } else { - //console.log("Unknown type", a.type); - } - //zoom -= 4; // Produces way nicer map, at expense of space. - if (tile.z < zoom) - continue; - f.properties = Object.assign({}, f.properties, p); - feat.push(f); - var s = JSON.stringify(feat); - if (s.length > 6000) { - console.log("tile too big, splitting", cnt); - writeFeatures(name+'-'+cnt++, feat); - feat = []; - } + var f = {}; + var zoom = 99; + var p = {}; + f.properties = a.tags; + f.type = "Feature"; + f.geometry = {}; + if (a.type == 1) { + f.geometry.type = "Point"; + f.geometry.coordinates = convGeom(tile, a.geometry)[0]; + zoom = zoomPoint(a.tags); + p = paintPoint(a.tags); + } else if (a.type == 2) { + f.geometry.type = "LineString"; + f.geometry.coordinates = convGeom(tile, a.geometry[0]); + zoom = zoomWay(a.tags); + p = paintWay(a.tags); + } else { + //console.log("Unknown type", a.type); + } + //zoom -= 4; // Produces way nicer map, at expense of space. + if (tile.z < zoom) + continue; + f.properties = Object.assign({}, f.properties, p); + feat.push(f); + var s = JSON.stringify(feat); + if (s.length > 6000) { + console.log("tile too big, splitting", cnt); + writeFeatures(name+'-'+cnt++, feat); + feat = []; + } } writeFeatures(name+'-'+cnt, feat); return n; @@ -136,11 +134,6 @@ var merc = new sphm({ antimeridian: true }); -//console.log(merc.ll([124, 123], 15)); -//console.log(merc.px([17734, 11102], 15)); -//console.log(merc.bbox(17734, 11102, 15)); -//return; - console.log("Splitting data"); var meta = {} meta.min_zoom = 0; @@ -167,10 +160,7 @@ for (const id in index.tiles) { const z = tile.z; console.log(num++, ":", tile.x, tile.y, z); var d = index.getTile(z, tile.x, tile.y).features; - //console.log(d); var n = `delme/z${z}-${tile.x}-${tile.y}` ; - //output[n] = d; - //console.log(n); writeTile(n, d, tile) } From 188aaa99f775f79438c2cd9a2edfc8dc134f5b58 Mon Sep 17 00:00:00 2001 From: David Peer Date: Sat, 19 Aug 2023 07:18:53 +0200 Subject: [PATCH 10/54] clear icon area in case weather condition changed --- apps/edgeclk/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/edgeclk/app.js b/apps/edgeclk/app.js index 45dd0c4c2..4bb1f4700 100644 --- a/apps/edgeclk/app.js +++ b/apps/edgeclk/app.js @@ -70,8 +70,11 @@ temp = temp < 0 ? '\\' + String(temp*-1) : String(temp); g.drawString(temp, g.getWidth()-40, g.getHeight() - 1, true); + + // clear icon area in case weather condition changed + g.clearRect(g.getWidth()-40, g.getHeight()-30, g.getWidth(), g.getHeight()); + weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 13); - weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 15); } catch(e) { g.drawString("ERR", g.getWidth()-3, g.getHeight() - 1, true); } From 0cec394aab823e23074566237c71ba15749535e5 Mon Sep 17 00:00:00 2001 From: David Peer Date: Sat, 19 Aug 2023 07:21:12 +0200 Subject: [PATCH 11/54] Minor design change --- apps/edgeclk/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/edgeclk/app.js b/apps/edgeclk/app.js index 4bb1f4700..603dbd921 100644 --- a/apps/edgeclk/app.js +++ b/apps/edgeclk/app.js @@ -73,7 +73,7 @@ // clear icon area in case weather condition changed g.clearRect(g.getWidth()-40, g.getHeight()-30, g.getWidth(), g.getHeight()); - weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 13); + weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 14); } catch(e) { g.drawString("ERR", g.getWidth()-3, g.getHeight() - 1, true); From 463a107c2809c37adb2882ffc5042e5cc8d92a5e Mon Sep 17 00:00:00 2001 From: David Peer Date: Sat, 19 Aug 2023 07:22:56 +0200 Subject: [PATCH 12/54] Show ? instead of err if weather is unknown. --- apps/edgeclk/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/edgeclk/app.js b/apps/edgeclk/app.js index 603dbd921..f9d5f803b 100644 --- a/apps/edgeclk/app.js +++ b/apps/edgeclk/app.js @@ -76,7 +76,7 @@ weather.drawIcon(w, g.getWidth()-20, g.getHeight()-15, 14); } catch(e) { - g.drawString("ERR", g.getWidth()-3, g.getHeight() - 1, true); + g.drawString("???", g.getWidth()-3, g.getHeight() - 1, true); } }; From c9ba8997b6917ce1ac1c10f28ca43ac7f9896ef8 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Thu, 17 Aug 2023 13:34:26 +0200 Subject: [PATCH 13/54] messagelist: add `debug()` statements Set `global.DEBUG_MESSAGELIST = true` to enable --- apps/messagelist/app.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/messagelist/app.js b/apps/messagelist/app.js index dfa7e43d4..207c70412 100644 --- a/apps/messagelist/app.js +++ b/apps/messagelist/app.js @@ -24,6 +24,9 @@ RIGHT = 1, LEFT = -1, // swipe directions UP = -1, DOWN = 1; // updown directions const Layout = require("Layout"); + const debug = function() { + if (global.DEBUG_MESSAGELIST) console.log.apply(console, ['messagelist:'].concat(arguments)); + } const settings = () => require("messagegui").settings(); const fontTiny = "6x8"; // fixed size, don't use this for important things @@ -45,6 +48,7 @@ /// List of all our messages let MESSAGES; const saveMessages = function() { + debug('saveMessages()'); const noSave = ["alarm", "call", "music"]; // assume these are outdated once we close the app noSave.forEach(id => remove({id: id})); require("messages").write(MESSAGES @@ -56,6 +60,7 @@ ); }; const uiRemove = function() { + debug('uiRemove()'); if (musicTimeout) clearTimeout(musicTimeout); layout = undefined; Bangle.removeListener("message", onMessage); @@ -85,6 +90,7 @@ } const setUI = function(options, cb) { + debug('setUI(', options, cb?'':cb) delete Bangle.uiRemove; // don't clear out things when switching UI within the app options = Object.assign({mode:"custom", remove: () => uiRemove()}, options); // If options={} assume we still want `remove` to be called when leaving via fast load (so we must have 'mode:custom') @@ -111,6 +117,7 @@ }; const onMessage = function(type, msg) { + debug(`onMessage(${type}`, msg); if (msg.handled) return; msg.handled = true; switch(type) { @@ -135,6 +142,7 @@ Bangle.on("message", onMessage); const onCall = function(msg) { + debug('onCall(', msg); if (msg.t==="remove") { call = undefined; return exitScreen("call"); @@ -145,6 +153,7 @@ showCall(); }; const onAlarm = function(msg) { + debug('onAlarm(', msg); if (msg.t==="remove") { alarm = undefined; return exitScreen("alarm"); @@ -155,6 +164,7 @@ }; let musicTimeout; const onMusic = function(msg) { + debug('onMusic(', msg); const hadMusic = !!music; if (musicTimeout) clearTimeout(musicTimeout); musicTimeout = undefined; @@ -184,6 +194,7 @@ } }; const onMap = function(msg) { + debug('onMap(', msg); const hadMap = !!map; if (msg.t==="remove") { map = undefined; @@ -196,6 +207,7 @@ else if (active==="main" && !hadMap) showMain(); // refresh menu: add "Map" entry }; const onText = function(msg) { + debug('onText(', msg); require("messages").apply(msg, MESSAGES); const mIdx = MESSAGES.findIndex(m => m.id===msg.id); if (!MESSAGES[mIdx]) if (back==="messages") back = undefined; @@ -237,6 +249,7 @@ }; const showMap = function() { + debug('showMap()'); setActive("map"); delete map.new; let m, distance, street, target, eta; @@ -319,6 +332,7 @@ else Bangle.musicControl(action); }; const showMusic = function() { + debug('showMusic()', music); if (active!==music) setActive("music"); if (!music) music = {track: "", artist: "", album: "", state: "pause"}; delete music.new; @@ -442,12 +456,14 @@ let layout; const clearStuff = function() { + debug('clearStuff()'); delete Bangle.appRect; layout = undefined; setUI(); g.reset().clearRect(Bangle.appRect); }; const setActive = function(screen, args) { + debug(`setActive(${screen}`, args); clearStuff(); if (active && screen!==active) back = active; if (screen==="messages") messageNum = args; @@ -476,6 +492,7 @@ } }; const showMain = function() { + debug('showMain()'); setActive("main"); let grid = {"": {title:/*LANG*/"Messages", align: 0, back: load}}; if (call) grid[/*LANG*/"Incoming Call"] = {icon: "Phone", cb: showCall}; @@ -640,6 +657,7 @@ }; const showSettings = function() { + debug('showSettings()'); setActive("settings"); eval(require("Storage").read("messagelist.settings.js"))(() => { setFont(); @@ -647,6 +665,7 @@ }); }; const showCall = function() { + debug('showCall()'); setActive("call"); delete call.new; Bangle.setLocked(false); @@ -722,6 +741,7 @@ }); }; const showAlarm = function() { + debug('showAlarm()'); // dismissing alarms doesn't seem to work, so this is simple */ setActive("alarm"); delete alarm.new; @@ -830,6 +850,7 @@ ); }; const showMessage = function(num, bottom) { + debug(`showMessage(${num}, ${!!bottom})`); if (num<0) num = 0; if (!num) num = 0; // no number: show first if (num>=MESSAGES.length) num = MESSAGES.length-1; @@ -1133,6 +1154,7 @@ * Stop auto-unload timeout and buzzing, remove listeners for this function */ const clearUnreadStuff = function() { + debug('clearUnreadStuff()'); require("messages").stopBuzz(); if (unreadTimeout) clearTimeout(unreadTimeout); unreadTimeout = undefined; @@ -1208,4 +1230,4 @@ // stop buzzing, auto-close timeout on input ["touch", "drag", "swipe"].forEach(l => Bangle.on(l, clearUnreadStuff)); (B2 ? [BTN1] : [BTN1, BTN2, BTN3]).forEach(b => watches.push(setWatch(clearUnreadStuff, b, false))); -} \ No newline at end of file +} From 4473acdbdeb962f1d1947ef9ca003e20cb865a10 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 18 Aug 2023 21:14:43 +0200 Subject: [PATCH 14/54] messagelist: Fix app crashing when new message arrives new Layout(...) calls Bangle.setUI(), causing uiRemove() to be called whenever a new messages arrives, instead of only when the app exits. This fixes that by unsetting Bangle.uiRemove before calling Layout, and restoring it afterwards. --- apps/messagelist/ChangeLog | 4 +++- apps/messagelist/app.js | 25 +++++++++++++++++++------ apps/messagelist/metadata.json | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/apps/messagelist/ChangeLog b/apps/messagelist/ChangeLog index 37854d8ae..899e29cb6 100644 --- a/apps/messagelist/ChangeLog +++ b/apps/messagelist/ChangeLog @@ -1,3 +1,5 @@ 0.01: New app! 0.02: Fix music updates while app is already open -0.03: Fix invalid use of Bangle.setUI \ No newline at end of file +0.03: Fix invalid use of Bangle.setUI +0.04: Fix app crashing when new message arrives + diff --git a/apps/messagelist/app.js b/apps/messagelist/app.js index 207c70412..3c140a0c4 100644 --- a/apps/messagelist/app.js +++ b/apps/messagelist/app.js @@ -96,6 +96,19 @@ // If options={} assume we still want `remove` to be called when leaving via fast load (so we must have 'mode:custom') Bangle.setUI(options, cb); }; + /** + * Same as calling `new Layout(layout, options)`, except Bangle.uiRemove is not called + * @param {object} layout + * @param {object} options + * @returns {Layout} + */ + const makeLayout = function(layout, options) { + const remove = Bangle.uiRemove; + delete Bangle.uiRemove; // don't clear out things when setting up new Layout + const result = new Layout(layout, options); + if (remove) Bangle.uiRemove = remove; + return result; + } const remove = function(msg) { if (msg.id==="call") call = undefined; @@ -267,7 +280,7 @@ } else { target = map.body; } - let layout = new Layout({ + let layout = makeLayout({ type: "v", c: [ {type: "txt", font: fontNormal, label: target, bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, pad: 2}, { @@ -369,7 +382,7 @@ else if (dur) info = dur; else info = {}; - layout = new Layout({ + layout = makeLayout({ type: "v", c: [ { type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ @@ -613,7 +626,7 @@ } l.c.push(row); } - layout = new Layout(l, {back: back}); + layout = makeLayout(l, {back: back}); layout.render(); if (B2) { @@ -697,7 +710,7 @@ ]; } - layout = new Layout({ + layout = makeLayout({ type: "v", c: [ { type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ @@ -751,7 +764,7 @@ const w = g.getWidth()-48, lines = g.setFont(fontNormal).wrapString(alarm.title, w), title = (lines.length>2) ? lines.slice(0, 2).join("\n")+"..." : lines.join("\n"); - layout = new Layout({ + layout = makeLayout({ type: "v", c: [ { type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ @@ -1114,7 +1127,7 @@ let imageCol = getImageColor(msg); if (g.setColor(imageCol).getColor()==hBg) imageCol = hCol; - layout = new Layout({ + layout = makeLayout({ type: "v", c: [ { type: "h", fillx: 1, bgCol: hBg, col: hCol, c: [ diff --git a/apps/messagelist/metadata.json b/apps/messagelist/metadata.json index 37fed5795..2f1abe11d 100644 --- a/apps/messagelist/metadata.json +++ b/apps/messagelist/metadata.json @@ -1,7 +1,7 @@ { "id": "messagelist", "name": "Message List", - "version": "0.03", + "version": "0.04", "description": "Display notifications from iOS and Gadgetbridge/Android as a list", "icon": "app.png", "type": "app", From b45bcab1815c7854266858bff2b650aea4c19bd8 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sat, 19 Aug 2023 16:28:04 +0200 Subject: [PATCH 15/54] astrocalc azimuth fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes #2651 azimuth value: 0° is south, so add 180° - Show all values in degrees --- apps/astrocalc/ChangeLog | 1 + apps/astrocalc/astrocalc-app.js | 24 +++++++++++++----------- apps/astrocalc/metadata.json | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/astrocalc/ChangeLog b/apps/astrocalc/ChangeLog index 95b9dbaf1..156cf17bf 100644 --- a/apps/astrocalc/ChangeLog +++ b/apps/astrocalc/ChangeLog @@ -3,3 +3,4 @@ 0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps 0.04: Compatibility with Bangle.js 2, get location from My Location 0.05: Enable widgets +0.06: Fix azimuth (bug #2651), only show degrees diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 2e732c37a..21e9a37ff 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -140,14 +140,15 @@ function drawData(title, obj, startX, startY) { function drawMoonPositionPage(gps, title) { const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon); const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0}; + const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north const pageData = { - Azimuth: pos.azimuth.toFixed(2), - Altitude: pos.altitude.toFixed(2), + Azimuth: parseInt(azimuth * 180 / Math.PI + 0.5) + '°', + Altitude: parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°', Distance: `${pos.distance.toFixed(0)} km`, - "Parallactic Ang": pos.parallacticAngle.toFixed(2), + "Parallactic Ang": parseInt(pos.parallacticAngle * 180 / Math.PI + 0.5) + '°', }; - const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI); + const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5); drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20); drawPoints(); @@ -189,12 +190,14 @@ function drawMoonTimesPage(gps, title) { // Draw the moon rise position const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon); - const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI); + const riseAzimuth = risePos.azimuth + Math.PI; // 0 is south, we want 0 to be north + const riseAzimuthDegrees = parseInt(riseAzimuth * 180 / Math.PI); drawPoint(riseAzimuthDegrees, 8, moonColor); // Draw the moon set position const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon); - const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI); + const setAzimuth = setPos.azimuth + Math.PI; // 0 is south, we want 0 to be north + const setAzimuthDegrees = parseInt(setAzimuth * 180 / Math.PI); drawPoint(setAzimuthDegrees, 8, moonColor); Bangle.setUI({mode: "custom", back: () => moonIndexPageMenu(gps)}); @@ -207,16 +210,15 @@ function drawSunShowPage(gps, key, date) { const mins = ("0" + date.getMinutes()).substr(-2); const secs = ("0" + date.getMinutes()).substr(-2); const time = `${hrs}:${mins}:${secs}`; + const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north - const azimuth = Number(pos.azimuth.toFixed(2)); - const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI); - const altitude = Number(pos.altitude.toFixed(2)); + const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5) + '°'; + const altitude = parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°'; const pageData = { Time: time, Altitude: altitude, - Azimumth: azimuth, - Degrees: azimuthDegrees + Azimuth: azimuthDegrees, }; drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5); diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index 1f4abb356..09dc53170 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -1,10 +1,10 @@ { "id": "astrocalc", "name": "Astrocalc", - "version": "0.05", + "version": "0.06", "description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app", "icon": "astrocalc.png", - "tags": "app,sun,moon,cycles,tool", + "tags": "app,sun,moon,cycles,tool,outdoors", "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, "dependencies": {"mylocation":"app"}, From 5947397938afd3c05bfcbef4b2301e6f1a95c815 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sat, 19 Aug 2023 16:32:44 +0200 Subject: [PATCH 16/54] astrocalc encoding --- apps/astrocalc/astrocalc-app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 21e9a37ff..5589a5703 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -143,10 +143,10 @@ function drawMoonPositionPage(gps, title) { const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north const pageData = { - Azimuth: parseInt(azimuth * 180 / Math.PI + 0.5) + '°', - Altitude: parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°', + Azimuth: parseInt(azimuth * 180 / Math.PI + 0.5) + '°', + Altitude: parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°', Distance: `${pos.distance.toFixed(0)} km`, - "Parallactic Ang": parseInt(pos.parallacticAngle * 180 / Math.PI + 0.5) + '°', + "Parallactic Ang": parseInt(pos.parallacticAngle * 180 / Math.PI + 0.5) + '°', }; const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5); @@ -212,8 +212,8 @@ function drawSunShowPage(gps, key, date) { const time = `${hrs}:${mins}:${secs}`; const azimuth = pos.azimuth + Math.PI; // 0 is south, we want 0 to be north - const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5) + '°'; - const altitude = parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°'; + const azimuthDegrees = parseInt(azimuth * 180 / Math.PI + 0.5) + '°'; + const altitude = parseInt(pos.altitude * 180 / Math.PI + 0.5) + '°'; const pageData = { Time: time, From e379e52dd49ee5a7b25f5a09fc565ad2b230d3fb Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Sun, 20 Aug 2023 17:34:49 +0200 Subject: [PATCH 17/54] sokoban: more renaming --- apps/sokoban/app.js | 4 ++-- apps/sokoban/metadata.json | 2 +- apps/sokoban/{Microban.sok => sokoban.microban.sok} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename apps/sokoban/{Microban.sok => sokoban.microban.sok} (100%) diff --git a/apps/sokoban/app.js b/apps/sokoban/app.js index b7d89d6ba..890156214 100644 --- a/apps/sokoban/app.js +++ b/apps/sokoban/app.js @@ -61,9 +61,9 @@ function next_map_offsets(filename, start_offset) { let config = s.readJSON("sokoban.json", true); if (config === undefined) { - let initial_offsets = next_map_offsets("Microban.sok", 0); + let initial_offsets = next_map_offsets("sokoban.microban.sok", 0); config = { - levels_sets: ["Microban.sok"], // all known files containing levels + levels_sets: ["sokoban.microban.sok"], // all known files containing levels levels_set: 0, // which set are we using ? current_maps: [0], // what is current map on each set ? offsets: [initial_offsets], // known offsets for each levels set (binary positions of maps in each file) diff --git a/apps/sokoban/metadata.json b/apps/sokoban/metadata.json index 7a4d5bc50..ef4a45f83 100644 --- a/apps/sokoban/metadata.json +++ b/apps/sokoban/metadata.json @@ -13,7 +13,7 @@ "readme": "README.md", "storage": [ {"name":"sokoban.app.js","url":"app.js"}, - {"name":"Microban.sok", "url":"Microban.sok"}, + {"name":"sokoban.microban.sok", "url":"sokoban.microban.sok"}, {"name":"sokoban.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"sokoban.json"} diff --git a/apps/sokoban/Microban.sok b/apps/sokoban/sokoban.microban.sok similarity index 100% rename from apps/sokoban/Microban.sok rename to apps/sokoban/sokoban.microban.sok From 6b51925109a316507cb9b79cd812d00dad569316 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Mon, 21 Aug 2023 15:09:34 +0200 Subject: [PATCH 18/54] gipy: jit is back --- apps/gipy/ChangeLog | 3 +++ apps/gipy/TODO | 12 +++++++++++- apps/gipy/app.js | 34 +++++++++++++++++++--------------- apps/gipy/metadata.json | 2 +- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 870ad0fdb..3ca699a4f 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -98,3 +98,6 @@ * New setting : power screen off between points to save battery * Color change for lost direction (now purple) * Adaptive screen powersaving + +0.21: + * Jit is back for display functions (10% speed increase) diff --git a/apps/gipy/TODO b/apps/gipy/TODO index b2a3c7ae1..614d2dda2 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -36,10 +36,20 @@ my conclusion is that: ************************** +JIT: array declaration in jit is buggy +(especially several declarations) + +************************** + ++ there is still a bug with negative remaining distances (when lost and nearest point is endpoint ?) ++ when lost we still get powersaving ++ try disabling gps for more powersaving + ++ add heights + + when you walk the direction still has a tendency to shift + put back foot only ways -+ try fiddling with jit + put back street names + put back shortest paths but with points cache this time and jit + how to display paths from shortest path ? diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 071ef8283..3a1ddcc61 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -139,6 +139,19 @@ 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 @@ -393,17 +406,12 @@ class Map { cos_direction, sin_direction ) { - // "jit"; - let center_x = g.getWidth() / 2; - let center_y = g.getHeight() / 2 + Y_OFFSET; - + "jit"; let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); let scaled_current_x = current_x * scale_factor; let scaled_current_y = current_y * scale_factor; - let recentered_points = g.transformVertices(points, [1, 0, 0, 1, -scaled_current_x, -scaled_current_y]); - let c = cos_direction; - let s = sin_direction; - let screen_points = g.transformVertices(recentered_points, [-c, s, s, c, center_x, center_y]); + let recentered_points = center_points(points, scaled_current_x, scaled_current_y); + let screen_points = rotate_points(recentered_points, cos_direction, sin_direction); for (let i = 0; i < screen_points.length; i += 4) { g.drawLine(screen_points[i], screen_points[i + 1], screen_points[i + 2], screen_points[i + 3]); @@ -419,17 +427,13 @@ class Map { cos_direction, sin_direction ) { - // "jit"; - let center_x = g.getWidth() / 2; - let center_y = g.getHeight() / 2 + Y_OFFSET; + "jit"; let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); let scaled_current_x = current_x * scale_factor; let scaled_current_y = current_y * scale_factor; - let recentered_points = g.transformVertices(points, [1, 0, 0, 1, -scaled_current_x, -scaled_current_y]); - let c = cos_direction; - let s = sin_direction; - let screen_points = g.transformVertices(recentered_points, [-c, s, s, c, center_x, center_y]); + let recentered_points = center_points(points, scaled_current_x, scaled_current_y); + let screen_points = rotate_points(recentered_points, cos_direction, sin_direction); for (let i = 0; i < screen_points.length; i += 4) { let final_x = screen_points[i]; diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json index 7dd4123f6..3526e1afa 100644 --- a/apps/gipy/metadata.json +++ b/apps/gipy/metadata.json @@ -2,7 +2,7 @@ "id": "gipy", "name": "Gipy", "shortName": "Gipy", - "version": "0.20", + "version": "0.21", "description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.", "allow_emulator":false, "icon": "gipy.png", From 3a6eb8b2adc41dd06cdffb6dd7fe9e07c3050493 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Mon, 21 Aug 2023 17:02:29 +0200 Subject: [PATCH 19/54] gipy: parsing heights --- apps/gipy/TODO | 3 +++ apps/gipy/app.js | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 614d2dda2..51ca38184 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -46,6 +46,9 @@ JIT: array declaration in jit is buggy + try disabling gps for more powersaving + add heights + -> have two views: zoomed in and zoomed out + ++ remove "lost" indicator and change position point's color instead + when you walk the direction still has a tendency to shift diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 3a1ddcc61..c608d182a 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -593,13 +593,14 @@ class Interests { } class Status { - constructor(path, maps, interests) { + constructor(path, maps, interests, heights) { this.path = path; this.default_options = true; // do we still have default options ? this.active = false; // should we have screen on this.last_activity = getTime(); this.maps = maps; this.interests = interests; + this.heights = heights; let half_screen_width = g.getWidth() / 2; let half_screen_height = g.getHeight() / 2; let half_screen_diagonal = Math.sqrt( @@ -1164,6 +1165,7 @@ function load_gps(filename) { let offset = 0; let path = null; + let heights = null; let maps = []; let interests = null; while (offset < file_size) { @@ -1186,6 +1188,11 @@ function load_gps(filename) { let res = new Interests(buffer, offset); interests = res[0]; offset = res[1]; + } else if (block_type == 4) { + console.log("loading heights"); + let heights_number = path.points.length / 2; + heights = Float64Array(buffer, offset, heights_number); + offset += 8 * heights_number; } else { console.log("todo : block type", block_type); } @@ -1197,10 +1204,10 @@ function load_gps(filename) { let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; E.showAlert(msg).then(function() { E.showAlert(); - start_gipy(path, maps, interests); + start_gipy(path, maps, interests, heights); }); } else { - start_gipy(path, maps, interests); + start_gipy(path, maps, interests, heights); } } @@ -1394,14 +1401,14 @@ function start(fn) { load_gps(fn); } -function start_gipy(path, maps, interests) { +function start_gipy(path, maps, interests, heights) { console.log("starting"); if (!simulated && settings.disable_bluetooth) { NRF.sleep(); // disable bluetooth completely } - status = new Status(path, maps, interests); + status = new Status(path, maps, interests, heights); setWatch( function() { From df46cd643e47f76cc752fac41e60ae8052d20faa Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 22 Aug 2023 14:37:44 +0200 Subject: [PATCH 20/54] gipy: elevation --- apps/gipy/README.md | 7 + apps/gipy/TODO | 3 - apps/gipy/app.js | 3011 +++++++++++++++++--------------- apps/gipy/pkg/gps.d.ts | 48 +- apps/gipy/pkg/gps.js | 735 +------- apps/gipy/pkg/gps_bg.wasm | Bin 748683 -> 743877 bytes apps/gipy/pkg/gps_bg.wasm.d.ts | 5 +- apps/gipy/pkg/package.json | 1 + 8 files changed, 1625 insertions(+), 2185 deletions(-) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index 03ca97753..ac65f8c3f 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -28,6 +28,7 @@ It provides the following features : - toilets - artwork - bakeries +- display elevation data if available in the trace ## Usage @@ -95,6 +96,12 @@ The distance to next point displayed corresponds to the length of the black segm If you click the button you'll reach a menu where you can currently zoom out to see more of the map (with a slower refresh rate), reverse the path direction and disable power saving (keeping backlight on). +### Elevation + +If you touch the screen you will switch between display modes. +The first one displays the map, the second one the nearby elevation and the last one the elevation +for the whole path. + ### Settings Few settings for now (feel free to suggest me more) : diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 51ca38184..ef1df3dc5 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -45,9 +45,6 @@ JIT: array declaration in jit is buggy + when lost we still get powersaving + try disabling gps for more powersaving -+ add heights - -> have two views: zoomed in and zoomed out - + remove "lost" indicator and change position point's color instead + when you walk the direction still has a tendency to shift diff --git a/apps/gipy/app.js b/apps/gipy/app.js index c608d182a..70c1aafd3 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -7,24 +7,31 @@ let powersaving = true; let status; let interests_colors = [ - 0xffff, // Waypoints, white - 0xf800, // Bakery, red - 0x001f, // DrinkingWater, blue - 0x07ff, // Toilets, cyan - 0x07e0, // Artwork, green + 0xffff, // Waypoints, white + 0xf800, // Bakery, red + 0x001f, // DrinkingWater, blue + 0x07ff, // Toilets, cyan + 0x07e0, // Artwork, green ]; let Y_OFFSET = 20; + +// some constants for screen types +let MAP = 0; +let HEIGHTS_ZOOMED_IN = 1; +let HEIGHTS_FULL = 2; + let s = require("Storage"); -var settings = Object.assign({ - lost_distance: 50, - brightness: 0.5, - buzz_on_turns: false, - disable_bluetooth: true, - power_lcd_off: false, - }, - s.readJSON("gipy.json", true) || {} +var settings = Object.assign( + { + lost_distance: 50, + brightness: 0.5, + buzz_on_turns: false, + disable_bluetooth: true, + power_lcd_off: false, + }, + s.readJSON("gipy.json", true) || {} ); // let profile_start_times = []; @@ -41,25 +48,25 @@ var settings = Object.assign({ // return the index of the largest element of the array which is <= x function binary_search(array, x) { - let start = 0, - end = array.length; + let start = 0, + end = array.length; - while (end - start >= 0) { - let mid = Math.floor((start + end) / 2); - if (array[mid] == x) { - return mid; - } else if (array[mid] < x) { - if (array[mid + 1] > x) { - return mid; - } - start = mid + 1; - } else end = mid - 1; - } - if (array[start] > x) { - return null; - } else { - return start; - } + while (end - start >= 0) { + let mid = Math.floor((start + end) / 2); + if (array[mid] == x) { + return mid; + } else if (array[mid] < x) { + if (array[mid + 1] > x) { + return mid; + } + start = mid + 1; + } else end = mid - 1; + } + if (array[start] > x) { + return null; + } else { + return start; + } } // return a string containing estimated time of arrival. @@ -67,81 +74,88 @@ function binary_search(array, x) { // remaining distance in km // hour, minutes is current time function compute_eta(hour, minutes, approximate_speed, remaining_distance) { - if (isNaN(approximate_speed) || approximate_speed < 0.1) { - return ""; - } - let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes - let eta_in_minutes = Math.round(hour * 60 + minutes + time_needed); - let eta_minutes = eta_in_minutes % 60; - let eta_hour = ((eta_in_minutes - eta_minutes) / 60) % 24; - if (eta_minutes < 10) { - return eta_hour.toString() + ":0" + eta_minutes; - } else { - return eta_hour.toString() + ":" + eta_minutes; - } + if (isNaN(approximate_speed) || approximate_speed < 0.1) { + return ""; + } + let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes + let eta_in_minutes = Math.round(hour * 60 + minutes + time_needed); + let eta_minutes = eta_in_minutes % 60; + let eta_hour = ((eta_in_minutes - eta_minutes) / 60) % 24; + if (eta_minutes < 10) { + return eta_hour.toString() + ":0" + eta_minutes; + } else { + return eta_hour.toString() + ":" + eta_minutes; + } } class TilesOffsets { - constructor(buffer, offset) { - let type_size = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - this.entry_size = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0]; - offset += 2; - this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number); - offset += 2 * non_empty_tiles_number; - if (type_size == 24) { - this.non_empty_tiles_ends = Uint24Array( - buffer, - offset, - non_empty_tiles_number - ); - offset += 3 * non_empty_tiles_number; - } else if (type_size == 16) { - this.non_empty_tiles_ends = Uint16Array( - buffer, - offset, - non_empty_tiles_number - ); - offset += 2 * non_empty_tiles_number; - } else { - throw "unknown size"; - } - return [this, offset]; + constructor(buffer, offset) { + let type_size = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + this.entry_size = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0]; + offset += 2; + this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number); + offset += 2 * non_empty_tiles_number; + if (type_size == 24) { + this.non_empty_tiles_ends = Uint24Array( + buffer, + offset, + non_empty_tiles_number + ); + offset += 3 * non_empty_tiles_number; + } else if (type_size == 16) { + this.non_empty_tiles_ends = Uint16Array( + buffer, + offset, + non_empty_tiles_number + ); + offset += 2 * non_empty_tiles_number; + } else { + throw "unknown size"; } - tile_start_offset(tile_index) { - if (tile_index <= this.non_empty_tiles[0]) { - return 0; - } else { - return this.tile_end_offset(tile_index - 1); - } + return [this, offset]; + } + tile_start_offset(tile_index) { + if (tile_index <= this.non_empty_tiles[0]) { + return 0; + } else { + return this.tile_end_offset(tile_index - 1); } - tile_end_offset(tile_index) { - let me_or_before = binary_search(this.non_empty_tiles, tile_index); - if (me_or_before === null) { - return 0; - } - if (me_or_before >= this.non_empty_tiles_ends.length) { - return ( - this.non_empty_tiles_ends[this.non_empty_tiles.length - 1] * - this.entry_size - ); - } else { - return this.non_empty_tiles_ends[me_or_before] * this.entry_size; - } + } + tile_end_offset(tile_index) { + let me_or_before = binary_search(this.non_empty_tiles, tile_index); + if (me_or_before === null) { + return 0; } - end_offset() { - return ( - this.non_empty_tiles_ends[this.non_empty_tiles_ends.length - 1] * - this.entry_size - ); + if (me_or_before >= this.non_empty_tiles_ends.length) { + return ( + this.non_empty_tiles_ends[this.non_empty_tiles.length - 1] * + this.entry_size + ); + } else { + return this.non_empty_tiles_ends[me_or_before] * this.entry_size; } + } + end_offset() { + return ( + this.non_empty_tiles_ends[this.non_empty_tiles_ends.length - 1] * + this.entry_size + ); + } } // 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]); + 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 @@ -153,1438 +167,1627 @@ function rotate_points(points, c, s) { } 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 = [ - color_array[0] / 255, - color_array[1] / 255, - color_array[2] / 255, - ]; - offset += 3; - this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile - offset += 2 * 4; - this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height - offset += 2 * 4; - this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates - offset += 2 * 8; - let side_array = Float64Array(buffer, offset, 1); // side of a tile - this.side = side_array[0]; - offset += 8; + constructor(buffer, offset, filename) { + this.points_cache = []; // don't refetch points all the time + // header + let color_array = Uint8Array(buffer, offset, 3); + this.color = [ + color_array[0] / 255, + color_array[1] / 255, + color_array[2] / 255, + ]; + offset += 3; + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile + offset += 2 * 4; + this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height + offset += 2 * 4; + this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates + offset += 2 * 8; + let side_array = Float64Array(buffer, offset, 1); // side of a tile + this.side = side_array[0]; + offset += 8; - // tiles offsets - let res = new TilesOffsets(buffer, offset); - this.tiles_offsets = res[0]; - offset = res[1]; + // tiles offsets + let res = new TilesOffsets(buffer, offset); + this.tiles_offsets = res[0]; + offset = res[1]; - // now, do binary ways - // since the file is so big we'll go line by line - let binary_lines = []; - for (let y = 0; y < this.grid_size[1]; y++) { - let first_tile_start = this.tiles_offsets.tile_start_offset( - y * this.grid_size[0] + // now, do binary ways + // since the file is so big we'll go line by line + let binary_lines = []; + for (let y = 0; y < this.grid_size[1]; y++) { + let first_tile_start = this.tiles_offsets.tile_start_offset( + y * this.grid_size[0] + ); + let last_tile_end = this.tiles_offsets.tile_start_offset( + (y + 1) * this.grid_size[0] + ); + let size = last_tile_end - first_tile_start; + let string = s.read(filename, offset + first_tile_start, size); + let array = Uint8Array(E.toArrayBuffer(string)); + binary_lines.push(array); + } + this.binary_lines = binary_lines; + offset += this.tiles_offsets.end_offset(); + + return [this, offset]; + + // now do streets data header + // let streets_header = E.toArrayBuffer(s.read(filename, offset, 8)); + // let streets_header_offset = 0; + // let full_streets_size = Uint32Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 4; + // let blocks_number = Uint16Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 2; + // let labels_string_size = Uint16Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 2; + // offset += streets_header_offset; + + // // continue with main streets labels + // main_streets_labels = s.read(filename, offset, labels_string_size); + // // this.main_streets_labels = main_streets_labels.split(/\r?\n/); + // this.main_streets_labels = main_streets_labels.split(/\n/); + // offset += labels_string_size; + + // // continue with blocks start offsets + // this.blocks_offsets = Uint32Array( + // E.toArrayBuffer(s.read(filename, offset, blocks_number * 4)) + // ); + // offset += blocks_number * 4; + + // // continue with compressed street blocks + // let encoded_blocks_size = + // full_streets_size - 4 - 2 - 2 - labels_string_size - blocks_number * 4; + // this.compressed_streets = Uint8Array( + // E.toArrayBuffer(s.read(filename, offset, encoded_blocks_size)) + // ); + // offset += encoded_blocks_size; + } + + display( + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + g.setColor(this.color[0], this.color[1], this.color[2]); + let local_x = displayed_x - this.start_coordinates[0]; + let local_y = displayed_y - this.start_coordinates[1]; + let tile_x = Math.floor(local_x / this.side); + let tile_y = Math.floor(local_y / this.side); + + let limit = 1; + if (!zoomed) { + limit = 2; + } + for (let y = tile_y - limit; y <= tile_y + limit; y++) { + if (y < 0 || y >= this.grid_size[1]) { + continue; + } + for (let x = tile_x - limit; x <= tile_x + limit; x++) { + if (x < 0 || x >= this.grid_size[0]) { + continue; + } + if ( + this.tile_is_on_screen( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ) + ) { + // let colors = [ + // [0, 0, 0], + // [0, 0, 1], + // [0, 1, 0], + // [0, 1, 1], + // [1, 0, 0], + // [1, 0, 1], + // [1, 1, 0], + // [1, 1, 0.5], + // [0.5, 0, 0.5], + // [0, 0.5, 0.5], + // ]; + if (this.color[0] == 1 && this.color[1] == 0 && this.color[2] == 0) { + this.display_thick_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction ); - let last_tile_end = this.tiles_offsets.tile_start_offset( - (y + 1) * this.grid_size[0] + } else { + this.display_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction ); - let size = last_tile_end - first_tile_start; - let string = s.read(filename, offset + first_tile_start, size); - let array = Uint8Array(E.toArrayBuffer(string)); - binary_lines.push(array); + } } - this.binary_lines = binary_lines; - offset += this.tiles_offsets.end_offset(); - - return [this, offset]; - - // now do streets data header - // let streets_header = E.toArrayBuffer(s.read(filename, offset, 8)); - // let streets_header_offset = 0; - // let full_streets_size = Uint32Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 4; - // let blocks_number = Uint16Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 2; - // let labels_string_size = Uint16Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 2; - // offset += streets_header_offset; - - // // continue with main streets labels - // main_streets_labels = s.read(filename, offset, labels_string_size); - // // this.main_streets_labels = main_streets_labels.split(/\r?\n/); - // this.main_streets_labels = main_streets_labels.split(/\n/); - // offset += labels_string_size; - - // // continue with blocks start offsets - // this.blocks_offsets = Uint32Array( - // E.toArrayBuffer(s.read(filename, offset, blocks_number * 4)) - // ); - // offset += blocks_number * 4; - - // // continue with compressed street blocks - // let encoded_blocks_size = - // full_streets_size - 4 - 2 - 2 - labels_string_size - blocks_number * 4; - // this.compressed_streets = Uint8Array( - // E.toArrayBuffer(s.read(filename, offset, encoded_blocks_size)) - // ); - // offset += encoded_blocks_size; + } } + } - display( - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - g.setColor(this.color[0], this.color[1], this.color[2]); - let local_x = displayed_x - this.start_coordinates[0]; - let local_y = displayed_y - this.start_coordinates[1]; - let tile_x = Math.floor(local_x / this.side); - let tile_y = Math.floor(local_y / this.side); + tile_is_on_screen( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + let width = g.getWidth(); + let height = g.getHeight(); + let center_x = width / 2; + let center_y = height / 2 + Y_OFFSET; + let side = this.side; + let tile_center_x = (tile_x + 0.5) * side; + let tile_center_y = (tile_y + 0.5) * side; + let scaled_center_x = (tile_center_x - current_x) * scale_factor; + let scaled_center_y = (tile_center_y - current_y) * scale_factor; + let rotated_center_x = + scaled_center_x * cos_direction - scaled_center_y * sin_direction; + let rotated_center_y = + scaled_center_x * sin_direction + scaled_center_y * cos_direction; + let on_screen_center_x = center_x - rotated_center_x; + let on_screen_center_y = center_y + rotated_center_y; - let limit = 1; - if (!zoomed) { - limit = 2; - } - for (let y = tile_y - limit; y <= tile_y + limit; y++) { - if (y < 0 || y >= this.grid_size[1]) { - continue; - } - for (let x = tile_x - limit; x <= tile_x + limit; x++) { - if (x < 0 || x >= this.grid_size[0]) { - continue; - } - if ( - this.tile_is_on_screen( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ) - ) { - // let colors = [ - // [0, 0, 0], - // [0, 0, 1], - // [0, 1, 0], - // [0, 1, 1], - // [1, 0, 0], - // [1, 0, 1], - // [1, 1, 0], - // [1, 1, 0.5], - // [0.5, 0, 0.5], - // [0, 0.5, 0.5], - // ]; - if (this.color[0] == 1 && this.color[1] == 0 && this.color[2] == 0) { - this.display_thick_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ); - } else { - this.display_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ); - } - } - } - } + let scaled_side = side * scale_factor * Math.sqrt(1 / 2); + + if (on_screen_center_x + scaled_side <= 0) { + return false; } - - tile_is_on_screen( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - let width = g.getWidth(); - let height = g.getHeight(); - let center_x = width / 2; - let center_y = height / 2 + Y_OFFSET; - let side = this.side; - let tile_center_x = (tile_x + 0.5) * side; - let tile_center_y = (tile_y + 0.5) * side; - let scaled_center_x = (tile_center_x - current_x) * scale_factor; - let scaled_center_y = (tile_center_y - current_y) * scale_factor; - let rotated_center_x = scaled_center_x * cos_direction - scaled_center_y * sin_direction; - let rotated_center_y = scaled_center_x * sin_direction + scaled_center_y * cos_direction; - let on_screen_center_x = center_x - rotated_center_x; - let on_screen_center_y = center_y + rotated_center_y; - - let scaled_side = side * scale_factor * Math.sqrt(1 / 2); - - if (on_screen_center_x + scaled_side <= 0) { - return false; - } - if (on_screen_center_x - scaled_side >= width) { - return false; - } - if (on_screen_center_y + scaled_side <= 0) { - return false; - } - if (on_screen_center_y - scaled_side >= height) { - return false; - } - return true; + if (on_screen_center_x - scaled_side >= width) { + return false; } - - tile_points(tile_num, tile_x, tile_y, scaled_side) { - let line_start_offset = this.tiles_offsets.tile_start_offset( - tile_y * this.grid_size[0] - ); - let offset = - this.tiles_offsets.tile_start_offset(tile_num) - line_start_offset; - let upper_limit = - this.tiles_offsets.tile_end_offset(tile_num) - line_start_offset; - - let line = this.binary_lines[tile_y]; - // we need to copy both for correct results and for performances - // let's precompute also. - let cached_tile = new Float64Array(upper_limit - offset); - for (let i = offset; i < upper_limit; i += 2) { - let x = (tile_x + line.buffer[i] / 255) * scaled_side; - let y = (tile_y + line.buffer[i + 1] / 255) * scaled_side; - cached_tile[i - offset] = x; - cached_tile[i + 1 - offset] = y; - } - return cached_tile; + if (on_screen_center_y + scaled_side <= 0) { + return false; } - - invalidate_caches() { - this.points_cache = []; + if (on_screen_center_y - scaled_side >= height) { + return false; } + return true; + } - fetch_points(tile_x, tile_y, scaled_side) { - let tile_num = tile_x + tile_y * this.grid_size[0]; - for (let i = 0; i < this.points_cache.length; i++) { - if (this.points_cache[i][0] == tile_num) { - return this.points_cache[i][1]; - } - } - if (this.points_cache.length > 40) { - this.points_cache.shift(); - } - let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); - this.points_cache.push([tile_num, points]); - return points; + tile_points(tile_num, tile_x, tile_y, scaled_side) { + let line_start_offset = this.tiles_offsets.tile_start_offset( + tile_y * this.grid_size[0] + ); + let offset = + this.tiles_offsets.tile_start_offset(tile_num) - line_start_offset; + let upper_limit = + this.tiles_offsets.tile_end_offset(tile_num) - line_start_offset; + + let line = this.binary_lines[tile_y]; + // we need to copy both for correct results and for performances + // let's precompute also. + let cached_tile = new Float64Array(upper_limit - offset); + for (let i = offset; i < upper_limit; i += 2) { + let x = (tile_x + line.buffer[i] / 255) * scaled_side; + let y = (tile_y + line.buffer[i + 1] / 255) * scaled_side; + cached_tile[i - offset] = x; + cached_tile[i + 1 - offset] = y; } + return cached_tile; + } - display_tile( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - "jit"; - let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - let scaled_current_x = current_x * scale_factor; - let scaled_current_y = current_y * scale_factor; - let recentered_points = center_points(points, scaled_current_x, scaled_current_y); - let screen_points = rotate_points(recentered_points, cos_direction, sin_direction); + invalidate_caches() { + this.points_cache = []; + } - for (let i = 0; i < screen_points.length; i += 4) { - g.drawLine(screen_points[i], screen_points[i + 1], screen_points[i + 2], screen_points[i + 3]); - } + fetch_points(tile_x, tile_y, scaled_side) { + let tile_num = tile_x + tile_y * this.grid_size[0]; + for (let i = 0; i < this.points_cache.length; i++) { + if (this.points_cache[i][0] == tile_num) { + return this.points_cache[i][1]; + } } - - display_thick_tile( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - "jit"; - - let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - let scaled_current_x = current_x * scale_factor; - let scaled_current_y = current_y * scale_factor; - let recentered_points = center_points(points, scaled_current_x, scaled_current_y); - let screen_points = rotate_points(recentered_points, cos_direction, sin_direction); - - for (let i = 0; i < screen_points.length; i += 4) { - let final_x = screen_points[i]; - let final_y = screen_points[i + 1]; - let new_final_x = screen_points[i + 2]; - let new_final_y = screen_points[i + 3]; - - let xdiff = new_final_x - final_x; - let ydiff = new_final_y - final_y; - let d = Math.sqrt(xdiff * xdiff + ydiff * ydiff); - let ox = (-ydiff / d) * 3; - let oy = (xdiff / d) * 3; - g.fillPoly([ - final_x + ox, - final_y + oy, - new_final_x + ox, - new_final_y + oy, - new_final_x - ox, - new_final_y - oy, - final_x - ox, - final_y - oy, - ]); - } + if (this.points_cache.length > 40) { + this.points_cache.shift(); } + let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); + this.points_cache.push([tile_num, points]); + return points; + } + + display_tile( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + "jit"; + let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + let scaled_current_x = current_x * scale_factor; + let scaled_current_y = current_y * scale_factor; + let recentered_points = center_points( + points, + scaled_current_x, + scaled_current_y + ); + let screen_points = rotate_points( + recentered_points, + cos_direction, + sin_direction + ); + + for (let i = 0; i < screen_points.length; i += 4) { + g.drawLine( + screen_points[i], + screen_points[i + 1], + screen_points[i + 2], + screen_points[i + 3] + ); + } + } + + display_thick_tile( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + "jit"; + + let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + let scaled_current_x = current_x * scale_factor; + let scaled_current_y = current_y * scale_factor; + let recentered_points = center_points( + points, + scaled_current_x, + scaled_current_y + ); + let screen_points = rotate_points( + recentered_points, + cos_direction, + sin_direction + ); + + for (let i = 0; i < screen_points.length; i += 4) { + let final_x = screen_points[i]; + let final_y = screen_points[i + 1]; + let new_final_x = screen_points[i + 2]; + let new_final_y = screen_points[i + 3]; + + let xdiff = new_final_x - final_x; + let ydiff = new_final_y - final_y; + let d = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + let ox = (-ydiff / d) * 3; + let oy = (xdiff / d) * 3; + g.fillPoly([ + final_x + ox, + final_y + oy, + new_final_x + ox, + new_final_y + oy, + new_final_x - ox, + new_final_y - oy, + final_x - ox, + final_y - oy, + ]); + } + } } class Interests { - constructor(buffer, offset) { - this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile - offset += 2 * 4; - this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height - offset += 2 * 4; - this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates - offset += 2 * 8; - let side_array = Float64Array(buffer, offset, 1); // side of a tile - this.side = side_array[0]; - offset += 8; + constructor(buffer, offset) { + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile + offset += 2 * 4; + this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height + offset += 2 * 4; + this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates + offset += 2 * 8; + let side_array = Float64Array(buffer, offset, 1); // side of a tile + this.side = side_array[0]; + offset += 8; - let res = new TilesOffsets(buffer, offset); - offset = res[1]; - this.offsets = res[0]; - let end = this.offsets.end_offset(); - this.binary_interests = new Uint8Array(end); - let binary_interests = Uint8Array(buffer, offset, end); - for (let i = 0; i < end; i++) { - this.binary_interests[i] = binary_interests[i]; - } - offset += end; - this.points_cache = []; - return [this, offset]; + let res = new TilesOffsets(buffer, offset); + offset = res[1]; + this.offsets = res[0]; + let end = this.offsets.end_offset(); + this.binary_interests = new Uint8Array(end); + let binary_interests = Uint8Array(buffer, offset, end); + for (let i = 0; i < end; i++) { + this.binary_interests[i] = binary_interests[i]; } + offset += end; + this.points_cache = []; + return [this, offset]; + } - display( - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - let local_x = displayed_x - this.start_coordinates[0]; - let local_y = displayed_y - this.start_coordinates[1]; - let tile_x = Math.floor(local_x / this.side); - let tile_y = Math.floor(local_y / this.side); - for (let y = tile_y - 1; y <= tile_y + 1; y++) { - if (y < 0 || y >= this.grid_size[1]) { - continue; - } - for (let x = tile_x - 1; x <= tile_x + 1; x++) { - if (x < 0 || x >= this.grid_size[0]) { - continue; - } - this.display_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ); - } + display( + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + let local_x = displayed_x - this.start_coordinates[0]; + let local_y = displayed_y - this.start_coordinates[1]; + let tile_x = Math.floor(local_x / this.side); + let tile_y = Math.floor(local_y / this.side); + for (let y = tile_y - 1; y <= tile_y + 1; y++) { + if (y < 0 || y >= this.grid_size[1]) { + continue; + } + for (let x = tile_x - 1; x <= tile_x + 1; x++) { + if (x < 0 || x >= this.grid_size[0]) { + continue; } + this.display_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ); + } } + } - tile_points(tile_num, tile_x, tile_y, scaled_side) { - let offset = this.offsets.tile_start_offset(tile_num); - let upper_limit = this.offsets.tile_end_offset(tile_num); + tile_points(tile_num, tile_x, tile_y, scaled_side) { + let offset = this.offsets.tile_start_offset(tile_num); + let upper_limit = this.offsets.tile_end_offset(tile_num); - let tile_interests = []; - for (let i = offset; i < upper_limit; i += 3) { - let interest = this.binary_interests[i]; - let x = (tile_x + this.binary_interests[i + 1] / 255) * scaled_side; - let y = (tile_y + this.binary_interests[i + 2] / 255) * scaled_side; - if (interest >= interests_colors.length) { - throw "bad interest" + interest + "at" + tile_num + "offset" + i; - } - tile_interests.push(interest); - tile_interests.push(x); - tile_interests.push(y); - } - return tile_interests; + let tile_interests = []; + for (let i = offset; i < upper_limit; i += 3) { + let interest = this.binary_interests[i]; + let x = (tile_x + this.binary_interests[i + 1] / 255) * scaled_side; + let y = (tile_y + this.binary_interests[i + 2] / 255) * scaled_side; + if (interest >= interests_colors.length) { + throw "bad interest" + interest + "at" + tile_num + "offset" + i; + } + tile_interests.push(interest); + tile_interests.push(x); + tile_interests.push(y); } - fetch_points(tile_x, tile_y, scaled_side) { - //TODO: factorize with map ? - let tile_num = tile_x + tile_y * this.grid_size[0]; - for (let i = 0; i < this.points_cache.length; i++) { - if (this.points_cache[i][0] == tile_num) { - return this.points_cache[i][1]; - } - } - if (this.points_cache.length > 40) { - this.points_cache.shift(); - } - let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); - this.points_cache.push([tile_num, points]); - return points; + return tile_interests; + } + fetch_points(tile_x, tile_y, scaled_side) { + //TODO: factorize with map ? + let tile_num = tile_x + tile_y * this.grid_size[0]; + for (let i = 0; i < this.points_cache.length; i++) { + if (this.points_cache[i][0] == tile_num) { + return this.points_cache[i][1]; + } } - invalidate_caches() { - this.points_cache = []; + if (this.points_cache.length > 40) { + this.points_cache.shift(); } - display_tile( - tile_x, - tile_y, - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - let width = g.getWidth(); - let half_width = width / 2; - let half_height = g.getHeight() / 2 + Y_OFFSET; - let interests = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); + this.points_cache.push([tile_num, points]); + return points; + } + invalidate_caches() { + this.points_cache = []; + } + display_tile( + tile_x, + tile_y, + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + let width = g.getWidth(); + let half_width = width / 2; + let half_height = g.getHeight() / 2 + Y_OFFSET; + let interests = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - let scaled_current_x = displayed_x * scale_factor; - let scaled_current_y = displayed_y * scale_factor; + let scaled_current_x = displayed_x * scale_factor; + let scaled_current_y = displayed_y * scale_factor; - for (let i = 0; i < interests.length; i += 3) { - let type = interests[i]; - let x = interests[i + 1]; - let y = interests[i + 2]; + for (let i = 0; i < interests.length; i += 3) { + let type = interests[i]; + let x = interests[i + 1]; + let y = interests[i + 2]; - let scaled_x = x - scaled_current_x; - let scaled_y = y - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = half_width - rotated_x; - let final_y = half_height + rotated_y; + let scaled_x = x - scaled_current_x; + let scaled_y = y - scaled_current_y; + let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let final_x = half_width - rotated_x; + let final_y = half_height + rotated_y; - let color = interests_colors[type]; - if (type == 0) { - g.setColor(0, 0, 0).fillCircle(final_x, final_y, 6); - } - g.setColor(color).fillCircle(final_x, final_y, 5); - } + let color = interests_colors[type]; + if (type == 0) { + g.setColor(0, 0, 0).fillCircle(final_x, final_y, 6); + } + g.setColor(color).fillCircle(final_x, final_y, 5); } + } } class Status { - constructor(path, maps, interests, heights) { - this.path = path; - this.default_options = true; // do we still have default options ? - this.active = false; // should we have screen on - this.last_activity = getTime(); - this.maps = maps; - this.interests = interests; - this.heights = heights; - let half_screen_width = g.getWidth() / 2; - let half_screen_height = g.getHeight() / 2; - let half_screen_diagonal = Math.sqrt( - half_screen_width * half_screen_width + - half_screen_height * half_screen_height - ); - this.scale_factor = half_screen_diagonal / maps[0].side; // multiply geo coordinates by this to get pixels coordinates - this.on_path = true; // are we on the path or lost ? - this.position = null; // where we are - this.adjusted_cos_direction = 1; // cos of where we look at - this.adjusted_sin_direction = 0; // 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.projected_point = null; + constructor(path, maps, interests, heights) { + this.path = path; + this.default_options = true; // do we still have default options ? + this.active = false; // should we have screen on + this.last_activity = getTime(); + this.maps = maps; + this.interests = interests; + this.heights = heights; + this.screen = MAP; + let half_screen_width = g.getWidth() / 2; + let half_screen_height = g.getHeight() / 2; + let half_screen_diagonal = Math.sqrt( + half_screen_width * half_screen_width + + half_screen_height * half_screen_height + ); + this.scale_factor = half_screen_diagonal / maps[0].side; // multiply geo coordinates by this to get pixels coordinates + this.on_path = true; // are we on the path or lost ? + this.position = null; // where we are + this.adjusted_cos_direction = 1; // cos of where we look at + this.adjusted_sin_direction = 0; // 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.projected_point = null; - if (this.path !== null) { - 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 + if (this.path !== null) { + 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)); } - this.starting_time = null; // time we start - this.advanced_distance = 0.0; - this.gps_coordinates_counter = 0; // how many coordinates did we receive - this.old_points = []; // record previous points but only when enough distance between them - this.old_times = []; // the corresponding times + previous_point = point; + } + this.remaining_distances = r; // how much distance remains at start of each segment } - activate() { - this.last_activity = getTime(); - if (this.active) { - return; - } else { - this.active = true; - Bangle.setLCDBrightness(settings.brightness); - Bangle.setLocked(false); - if (settings.power_lcd_off) { - Bangle.setLCDPower(true); - } - } + this.starting_time = null; // time we start + this.advanced_distance = 0.0; + this.gps_coordinates_counter = 0; // how many coordinates did we receive + this.old_points = []; // record previous points but only when enough distance between them + this.old_times = []; // the corresponding times + } + activate() { + this.last_activity = getTime(); + if (this.active) { + return; + } else { + this.active = true; + Bangle.setLCDBrightness(settings.brightness); + Bangle.setLocked(false); + if (settings.power_lcd_off) { + Bangle.setLCDPower(true); + } } - check_activity() { - if (!this.active || !powersaving) { - return; - } - if (getTime() - this.last_activity > 30) { - this.active = false; - Bangle.setLCDBrightness(0); - if (settings.power_lcd_off) { - Bangle.setLCDPower(false); - } - } + } + check_activity() { + if (!this.active || !powersaving) { + return; } - invalidate_caches() { - for (let i = 0; i < this.maps.length; i++) { - this.maps[i].invalidate_caches(); - } - if (this.interests !== null) { - this.interests.invalidate_caches(); - } + if (getTime() - this.last_activity > 30) { + this.active = false; + Bangle.setLCDBrightness(0); + if (settings.power_lcd_off) { + Bangle.setLCDPower(false); + } } - 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. + } + invalidate_caches() { + for (let i = 0; i < this.maps.length; i++) { + this.maps[i].invalidate_caches(); + } + if (this.interests !== null) { + this.interests.invalidate_caches(); + } + } + 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. - let now = getTime(); - - if (this.old_points.length == 0) { - this.gps_coordinates_counter += 1; - this.old_points.push(position); - this.old_times.push(now); - return null; - } else { - let previous_point = this.old_points[this.old_points.length - 1]; - let distance_to_previous = previous_point.distance(position); - // gps signal is noisy but rarely above 5 meters - if (distance_to_previous < 5) { - // update instant speed and return - let oldest_point = this.old_points[0]; - let distance_to_oldest = oldest_point.distance(position); - this.instant_speed = distance_to_oldest / (now - this.old_times[0]); - return null; - } - } - this.gps_coordinates_counter += 1; - this.old_points.push(position); - this.old_times.push(now); + let now = getTime(); + if (this.old_points.length == 0) { + this.gps_coordinates_counter += 1; + this.old_points.push(position); + this.old_times.push(now); + return null; + } else { + let previous_point = this.old_points[this.old_points.length - 1]; + let distance_to_previous = previous_point.distance(position); + // gps signal is noisy but rarely above 5 meters + if (distance_to_previous < 5) { + // update instant speed and return let oldest_point = this.old_points[0]; let distance_to_oldest = oldest_point.distance(position); - - // every 3 points we count the distance - if (this.gps_coordinates_counter % 3 == 0) { - if (distance_to_oldest < 150.0) { - // to avoid gps glitches - this.advanced_distance += distance_to_oldest; - } - } - this.instant_speed = distance_to_oldest / (now - this.old_times[0]); - - if (this.old_points.length == 4) { - this.old_points.shift(); - this.old_times.shift(); - } - // 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; + return null; + } } - update_position(new_position) { - let direction = this.new_position_reached(new_position); - if (direction === null) { - if (this.old_points.length > 1) { - this.display(); // re-display because speed has changed - } - return; - } - if (in_menu) { - return; - } - if (this.instant_speed * 3.6 < 13) { - this.activate(); // if we go too slow turn on, we might be looking for the direction to follow - if (!this.default_options) { - this.default_options = true; + this.gps_coordinates_counter += 1; + this.old_points.push(position); + this.old_times.push(now); - Bangle.setOptions({ - lockTimeout: 10000, - backlightTimeout: 10000, - wakeOnTwist: true, - powerSave: true, - }); - } - } else { - if (this.default_options) { - this.default_options = false; + let oldest_point = this.old_points[0]; + let distance_to_oldest = oldest_point.distance(position); - Bangle.setOptions({ - lockTimeout: 0, - backlightTimeout: 0, - lcdPowerTimeout: 0, - hrmSportMode: 2, - wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. - wakeOnFaceUp: false, - wakeOnTouch: true, - powerSave: false, - }); - Bangle.setPollInterval(2000); // disable accelerometer as much as we can (a value of 4000 seem to cause hard reboot crashes (segfaults ?) so keep 2000) - } - - } - this.check_activity(); // if we don't move or are in menu we should stay on - - this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); - this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0); - this.angle = direction; - let cos_direction = Math.cos(direction); - let sin_direction = Math.sin(direction); - this.position = new_position; - - // we will display position of where we'll be at in a few seconds - // and not where we currently are. - // this is because the display has more than 1sec duration. - this.displayed_position = new Point( - new_position.lon + cos_direction * this.instant_speed * 0.00001, - new_position.lat + sin_direction * this.instant_speed * 0.00001 - ); - - if (this.path !== null) { - // detect segment we are on now - let res = this.path.nearest_segment( - this.displayed_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)) { - // start_profiling(); - // it did not work, try anywhere - res = this.path.nearest_segment( - this.displayed_position, - 0, - this.path.len - 1, - cos_direction, - sin_direction - ); - orientation = res[0]; - next_segment = res[1]; - // end_profiling("repositioning"); - } - // now check if we strayed away from path or back to it - let lost = this.is_lost(next_segment); - if (this.on_path == lost) { - this.activate(); - // 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.activate(); - this.reaching = next_point; - let reaching_waypoint = this.path.is_waypoint(next_point); - if (reaching_waypoint) { - if (settings.buzz_on_turns) { - Bangle.buzz(); - setTimeout(() => Bangle.buzz(), 500); - setTimeout(() => Bangle.buzz(), 1000); - setTimeout(() => Bangle.buzz(), 1500); - } - } - } - } - - // abort most frames if inactive - if (!this.active && this.gps_coordinates_counter % 5 != 0) { - return; - } - - // re-display - this.display(); + // every 3 points we count the distance + if (this.gps_coordinates_counter % 3 == 0) { + if (distance_to_oldest < 150.0) { + // to avoid gps glitches + this.advanced_distance += distance_to_oldest; + } } - display_direction() { - //TODO: go towards point on path at 20 meter - if (this.current_segment === null) { - return; - } - let next_point = this.path.point(this.current_segment + (1 - go_backwards)); - let distance_to_next_point = Math.ceil( - this.projected_point.distance(next_point) - ); - let towards; - if (distance_to_next_point < 20) { - towards = this.path.point(this.current_segment + 2 * (1 - go_backwards)); - } else { - towards = next_point; - } - let diff = towards.minus(this.projected_point); - direction = Math.atan2(diff.lat, diff.lon); + this.instant_speed = distance_to_oldest / (now - this.old_times[0]); - let full_angle = direction - this.angle; - // let c = towards.coordinates(p, this.adjusted_cos_direction, this.adjusted_sin_direction, this.scale_factor); - // g.setColor(1,0,1).fillCircle(c[0], c[1], 5); - - let scale; - if (zoomed) { - scale = this.scale_factor; - } else { - scale = this.scale_factor / 2; - } - - c = this.projected_point.coordinates( - this.displayed_position, - this.adjusted_cos_direction, - this.adjusted_sin_direction, - scale - ); - - let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2); - let cos2 = Math.cos(full_angle + Math.PI / 2); - let cos3 = Math.cos(full_angle - 0.6 + Math.PI / 2); - let sin1 = Math.sin(-full_angle - 0.6 - Math.PI / 2); - let sin2 = Math.sin(-full_angle - Math.PI / 2); - let sin3 = Math.sin(-full_angle + 0.6 - Math.PI / 2); - g.setColor(0, 1, 0).fillPoly([ - c[0] + cos1 * 15, - c[1] + sin1 * 15, - c[0] + cos2 * 20, - c[1] + sin2 * 20, - c[0] + cos3 * 15, - c[1] + sin3 * 15, - c[0] + cos3 * 10, - c[1] + sin3 * 10, - c[0] + cos2 * 15, - c[1] + sin2 * 15, - c[0] + cos1 * 10, - c[1] + sin1 * 10, - ]); + if (this.old_points.length == 4) { + this.old_points.shift(); + this.old_times.shift(); } - remaining_distance() { - let remaining_in_correct_orientation = - this.remaining_distances[this.current_segment + 1] + - this.position.distance(this.path.point(this.current_segment + 1)); - - if (go_backwards) { - return this.remaining_distances[0] - remaining_in_correct_orientation; - } else { - return remaining_in_correct_orientation; - } + // 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; } - // check if we are lost (too far from segment we think we are on) - // if we are adjust scale so that path will still be displayed. - // we do the scale adjustment here to avoid recomputations later on. - is_lost(segment) { - let projection = this.displayed_position.closest_segment_point( - this.path.point(segment), - this.path.point(segment + 1) - ); - this.projected_point = projection; // save this info for display - let distance_to_projection = this.displayed_position.distance(projection); - if (distance_to_projection > settings.lost_distance) { - return true; - } else { - return false; - } + let diff = position.minus(this.old_points[previous_index]); + let angle = Math.atan2(diff.lat, diff.lon); + return angle; + } + update_position(new_position) { + let direction = this.new_position_reached(new_position); + if (direction === null) { + if (this.old_points.length > 1) { + this.display(); // re-display because speed has changed + } + return; } - display() { - if (displaying || in_menu) { - return; // don't draw on drawings - } - displaying = true; - g.clear(); - let scale_factor = this.scale_factor; - if (!zoomed) { - scale_factor /= 2; - } + if (in_menu) { + return; + } + if (this.instant_speed * 3.6 < 13) { + this.activate(); // if we go too slow turn on, we might be looking for the direction to follow + if (!this.default_options) { + this.default_options = true; + Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 10000, + wakeOnTwist: true, + powerSave: true, + }); + } + } else { + if (this.default_options) { + this.default_options = false; + + Bangle.setOptions({ + lockTimeout: 0, + backlightTimeout: 0, + lcdPowerTimeout: 0, + hrmSportMode: 2, + wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. + wakeOnFaceUp: false, + wakeOnTouch: true, + powerSave: false, + }); + Bangle.setPollInterval(2000); // disable accelerometer as much as we can (a value of 4000 seem to cause hard reboot crashes (segfaults ?) so keep 2000) + } + } + this.check_activity(); // if we don't move or are in menu we should stay on + + this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); + this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0); + this.angle = direction; + let cos_direction = Math.cos(direction); + let sin_direction = Math.sin(direction); + this.position = new_position; + + // we will display position of where we'll be at in a few seconds + // and not where we currently are. + // this is because the display has more than 1sec duration. + this.displayed_position = new Point( + new_position.lon + cos_direction * this.instant_speed * 0.00001, + new_position.lat + sin_direction * this.instant_speed * 0.00001 + ); + + if (this.path !== null) { + // detect segment we are on now + let res = this.path.nearest_segment( + this.displayed_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)) { // start_profiling(); - for (let i = 0; i < this.maps.length; i++) { - this.maps[i].display( - this.displayed_position.lon, - this.displayed_position.lat, - scale_factor, - this.adjusted_cos_direction, - this.adjusted_sin_direction - ); - } - // end_profiling("map"); - if (this.interests !== null) { - this.interests.display( - this.displayed_position.lon, - this.displayed_position.lat, - scale_factor, - this.adjusted_cos_direction, - this.adjusted_sin_direction - ); - } - if (this.position !== null) { - this.display_path(); - } - - this.display_direction(); - this.display_stats(); - Bangle.drawWidgets(); - displaying = false; - } - display_stats() { - let now = new Date(); - let minutes = now.getMinutes().toString(); - if (minutes.length < 2) { - minutes = "0" + minutes; - } - let hours = now.getHours().toString(); - - // display the clock - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString(hours + ":" + minutes, 0, 24); - - let approximate_speed; - // display speed (avg and instant) - if (this.old_times.length > 0) { - let point_time = this.old_times[this.old_times.length - 1]; - let done_in = point_time - this.starting_time; - 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", - 0, - g.getHeight() - 15 - ); - - g.setFont("6x8:3") - .setFontAlign(1, -1, 0) - .drawString( - "" + approximate_instant_speed, - g.getWidth(), - g.getHeight() - 22 - ); - } - - if (this.path === null || this.position === null) { - return; - } - - let remaining_distance = this.remaining_distance(); - let rounded_distance = Math.round(remaining_distance / 100) / 10; - let total = Math.round(this.remaining_distances[0] / 100) / 10; - // now, distance to next point in meters - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString( - "" + this.distance_to_next_point + "m", - 0, - g.getHeight() - 49 - ); - - let forward_eta = compute_eta( - now.getHours(), - now.getMinutes(), - approximate_speed, - remaining_distance / 1000 + // it did not work, try anywhere + res = this.path.nearest_segment( + this.displayed_position, + 0, + this.path.len - 1, + cos_direction, + sin_direction ); + orientation = res[0]; + next_segment = res[1]; + // end_profiling("repositioning"); + } + // now check if we strayed away from path or back to it + let lost = this.is_lost(next_segment); + if (this.on_path == lost) { + this.activate(); + // 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; + } - // now display ETA - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString(forward_eta, 0, 42); + this.current_segment = next_segment; - // display distance on path - g.setFont("6x8:2").drawString( - "" + rounded_distance + "/" + total, - 0, - g.getHeight() - 32 + // 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.activate(); + this.reaching = next_point; + let reaching_waypoint = this.path.is_waypoint(next_point); + if (reaching_waypoint) { + if (settings.buzz_on_turns) { + Bangle.buzz(); + setTimeout(() => Bangle.buzz(), 500); + setTimeout(() => Bangle.buzz(), 1000); + setTimeout(() => Bangle.buzz(), 1500); + } + } + } + } + + // abort most frames if inactive + if (!this.active && this.gps_coordinates_counter % 5 != 0) { + return; + } + + // re-display + this.display(); + } + display_direction() { + //TODO: go towards point on path at 20 meter + if (this.current_segment === null) { + return; + } + let next_point = this.path.point(this.current_segment + (1 - go_backwards)); + + let distance_to_next_point = Math.ceil( + this.projected_point.distance(next_point) + ); + let towards; + if (distance_to_next_point < 20) { + towards = this.path.point(this.current_segment + 2 * (1 - go_backwards)); + } else { + towards = next_point; + } + let diff = towards.minus(this.projected_point); + direction = Math.atan2(diff.lat, diff.lon); + + let full_angle = direction - this.angle; + // let c = towards.coordinates(p, this.adjusted_cos_direction, this.adjusted_sin_direction, this.scale_factor); + // g.setColor(1,0,1).fillCircle(c[0], c[1], 5); + + let scale; + if (zoomed) { + scale = this.scale_factor; + } else { + scale = this.scale_factor / 2; + } + + c = this.projected_point.coordinates( + this.displayed_position, + this.adjusted_cos_direction, + this.adjusted_sin_direction, + scale + ); + + let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2); + let cos2 = Math.cos(full_angle + Math.PI / 2); + let cos3 = Math.cos(full_angle - 0.6 + Math.PI / 2); + let sin1 = Math.sin(-full_angle - 0.6 - Math.PI / 2); + let sin2 = Math.sin(-full_angle - Math.PI / 2); + let sin3 = Math.sin(-full_angle + 0.6 - Math.PI / 2); + g.setColor(0, 1, 0).fillPoly([ + c[0] + cos1 * 15, + c[1] + sin1 * 15, + c[0] + cos2 * 20, + c[1] + sin2 * 20, + c[0] + cos3 * 15, + c[1] + sin3 * 15, + c[0] + cos3 * 10, + c[1] + sin3 * 10, + c[0] + cos2 * 15, + c[1] + sin2 * 15, + c[0] + cos1 * 10, + c[1] + sin1 * 10, + ]); + } + remaining_distance() { + let remaining_in_correct_orientation = + this.remaining_distances[this.current_segment + 1] + + this.position.distance(this.path.point(this.current_segment + 1)); + + if (go_backwards) { + return this.remaining_distances[0] - remaining_in_correct_orientation; + } else { + return remaining_in_correct_orientation; + } + } + // check if we are lost (too far from segment we think we are on) + // if we are adjust scale so that path will still be displayed. + // we do the scale adjustment here to avoid recomputations later on. + is_lost(segment) { + let projection = this.displayed_position.closest_segment_point( + this.path.point(segment), + this.path.point(segment + 1) + ); + this.projected_point = projection; // save this info for display + let distance_to_projection = this.displayed_position.distance(projection); + if (distance_to_projection > settings.lost_distance) { + return true; + } else { + return false; + } + } + display() { + if (displaying || in_menu) { + return; // don't draw on drawings + } + displaying = true; + g.clear(); + if (this.screen == MAP) { + this.display_map(); + } else { + let current_position = 0; + if (this.current_segment !== null) { + current_position = + this.remaining_distances[0] - this.remaining_distance(); + } + if (this.screen == HEIGHTS_FULL) { + this.display_heights(0, current_position, this.remaining_distances[0]); + } else { + // only display 2500m + let start; + if (go_backwards) { + start = Math.max(0, current_position - 2000); + } else { + start = Math.max(0, current_position - 500); + } + let length = Math.min(2500, this.remaining_distances[0] - start); + this.display_heights(start, current_position, length); + } + } + Bangle.drawWidgets(); + displaying = false; + } + display_heights(display_start, current_position, displayed_length) { + let path_length = this.remaining_distances[0]; + let widgets_height = 24; + let graph_width = g.getWidth(); + let graph_height = g.getHeight() - 20 - widgets_height; + + let distance_per_pixel = displayed_length / graph_width; + + let start_point_index = 0; + let end_point_index = this.remaining_distances.length - 1; + for (let i = 0; i < this.remaining_distances.length; i++) { + let point_distance = path_length - this.remaining_distances[i]; + if (point_distance <= display_start) { + start_point_index = i; + } + if (point_distance >= display_start + displayed_length) { + end_point_index = i; + break; + } + } + let max_height = Number.NEGATIVE_INFINITY; + let min_height = Number.POSITIVE_INFINITY; + for (let i = start_point_index; i <= end_point_index; i++) { + let height = this.heights[i]; + max_height = Math.max(max_height, height); + min_height = Math.min(min_height, height); + } + + let displayed_height = max_height - min_height; + let height_per_pixel = displayed_height / graph_height; + // g.setColor(0, 0, 0).drawRect(0, widgets_height, graph_width, graph_height + widgets_height); + + let previous_x = null; + let previous_y = null; + let previous_height = null; + let previous_distance = null; + let current_x; + let current_y; + for (let i = start_point_index; i < end_point_index; i++) { + let point_distance = path_length - this.remaining_distances[i]; + let height = this.heights[i]; + let x = Math.round((point_distance - display_start) / distance_per_pixel); + if (go_backwards) { + x = graph_width - x; + } + let y = + widgets_height + + graph_height - + Math.round((height - min_height) / height_per_pixel); + if (x != previous_x) { + if (previous_x !== null) { + let steepness = + (height - previous_height) / (point_distance - previous_distance); + if (go_backwards) { + steepness *= -1; + } + let color; + if (steepness > 0.15) { + color = "#ff0000"; + } else if (steepness > 0.8) { + color = "#aa0000"; + } else if (steepness > 0.03) { + color = "#ffff00"; + } else if (steepness > -0.03) { + color = "#00ff00"; + } else if (steepness > -0.08) { + color = "#00aa44"; + } else if (steepness > -0.015) { + color = "#0044aa"; + } else { + color = "#0000ff"; + } + g.setColor(color); + g.fillPoly([ + previous_x, + previous_y, + x, + y, + x, + widgets_height + graph_height, + previous_x, + widgets_height + graph_height, + ]); + if ( + current_position >= previous_distance && + current_position < point_distance + ) { + let current_height = + previous_height + + ((current_position - previous_distance) / + (point_distance - previous_distance)) * + (height - previous_height); + current_x = Math.round( + (current_position - display_start) / distance_per_pixel + ); + if (go_backwards) { + current_x = graph_width - current_x; + } + current_y = + widgets_height + + graph_height - + Math.round((current_height - min_height) / height_per_pixel); + } + } + previous_distance = point_distance; + previous_height = height; + previous_x = x; + previous_y = y; + } + } + g.setColor(0, 0, 0); + g.fillCircle(current_x, current_y, 5); + + // display min dist/max dist and min height/max height + g.setColor(g.theme.fg); + g.setFont("6x8:2"); + g.setFontAlign(-1, 1, 0).drawString( + Math.ceil(display_start / 100) / 10, + 0, + g.getHeight() + ); + + g.setFontAlign(1, 1, 0).drawString( + Math.ceil((display_start + displayed_length) / 100) / 10, + g.getWidth(), + g.getHeight() + ); + + g.setFontAlign(1, 1, 0).drawString( + min_height, + g.getWidth(), + widgets_height + graph_height + ); + g.setFontAlign(1, -1, 0).drawString( + max_height, + g.getWidth(), + widgets_height + ); + } + display_map() { + let scale_factor = this.scale_factor; + if (!zoomed) { + scale_factor /= 2; + } + + // start_profiling(); + for (let i = 0; i < this.maps.length; i++) { + this.maps[i].display( + this.displayed_position.lon, + this.displayed_position.lat, + scale_factor, + this.adjusted_cos_direction, + this.adjusted_sin_direction + ); + } + // end_profiling("map"); + if (this.interests !== null) { + this.interests.display( + this.displayed_position.lon, + this.displayed_position.lat, + scale_factor, + this.adjusted_cos_direction, + this.adjusted_sin_direction + ); + } + if (this.position !== null) { + this.display_path(); + } + + this.display_direction(); + this.display_stats(); + } + display_stats() { + let now = new Date(); + let minutes = now.getMinutes().toString(); + if (minutes.length < 2) { + minutes = "0" + minutes; + } + let hours = now.getHours().toString(); + + // display the clock + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(hours + ":" + minutes, 0, 24); + + let approximate_speed; + // display speed (avg and instant) + if (this.old_times.length > 0) { + let point_time = this.old_times[this.old_times.length - 1]; + let done_in = point_time - this.starting_time; + 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", 0, g.getHeight() - 15); + + g.setFont("6x8:3") + .setFontAlign(1, -1, 0) + .drawString( + "" + approximate_instant_speed, + g.getWidth(), + g.getHeight() - 22 ); - - // display various indicators - 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_path() { - // 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 - let cos = this.adjusted_cos_direction; - let sin = this.adjusted_sin_direction; - let displayed_x = this.displayed_position.lon; - let displayed_y = this.displayed_position.lat; - let width = g.getWidth(); - let height = g.getHeight(); - let half_width = width / 2; - let half_height = height / 2 + Y_OFFSET; - let scale_factor = this.scale_factor; - if (!zoomed) { - scale_factor /= 2; - } - if (this.path !== null) { - // compute coordinate for projection on path - let tx = (this.projected_point.lon - displayed_x) * scale_factor; - let ty = (this.projected_point.lat - displayed_y) * scale_factor; - let rotated_x = tx * cos - ty * sin; - let rotated_y = tx * sin + ty * cos; - let projected_x = half_width - Math.round(rotated_x); // x is inverted - let projected_y = half_height + Math.round(rotated_y); - - // display direction to next point if lost - if (!this.on_path) { - let next_point = this.path.point(this.current_segment + 1); - let previous_point = this.path.point(this.current_segment); - let nearest_point; - if ( - previous_point.fake_distance(this.position) < - next_point.fake_distance(this.position) - ) { - nearest_point = previous_point; - } else { - nearest_point = next_point; - } - let tx = (nearest_point.lon - displayed_x) * scale_factor; - let ty = (nearest_point.lat - displayed_y) * scale_factor; - 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(1, 0, 1).drawLine(half_width, half_height, x, y); - } - - // display current-segment's projection - g.setColor(0, 0, 0); - g.fillCircle(projected_x, projected_y, 4); - } - - // now display ourselves - g.setColor(0, 0, 0); - g.fillCircle(half_width, half_height, 5); + if (this.path === null || this.position === null) { + return; } + + let remaining_distance = this.remaining_distance(); + let rounded_distance = Math.round(remaining_distance / 100) / 10; + let total = Math.round(this.remaining_distances[0] / 100) / 10; + // now, distance to next point in meters + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString( + "" + this.distance_to_next_point + "m", + 0, + g.getHeight() - 49 + ); + + let forward_eta = compute_eta( + now.getHours(), + now.getMinutes(), + approximate_speed, + remaining_distance / 1000 + ); + + // now display ETA + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(forward_eta, 0, 42); + + // display distance on path + g.setFont("6x8:2").drawString( + "" + rounded_distance + "/" + total, + 0, + g.getHeight() - 32 + ); + + // display various indicators + 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_path() { + // 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 + let cos = this.adjusted_cos_direction; + let sin = this.adjusted_sin_direction; + let displayed_x = this.displayed_position.lon; + let displayed_y = this.displayed_position.lat; + let width = g.getWidth(); + let height = g.getHeight(); + let half_width = width / 2; + let half_height = height / 2 + Y_OFFSET; + let scale_factor = this.scale_factor; + if (!zoomed) { + scale_factor /= 2; + } + + if (this.path !== null) { + // compute coordinate for projection on path + let tx = (this.projected_point.lon - displayed_x) * scale_factor; + let ty = (this.projected_point.lat - displayed_y) * scale_factor; + let rotated_x = tx * cos - ty * sin; + let rotated_y = tx * sin + ty * cos; + let projected_x = half_width - Math.round(rotated_x); // x is inverted + let projected_y = half_height + Math.round(rotated_y); + + // display direction to next point if lost + if (!this.on_path) { + let next_point = this.path.point(this.current_segment + 1); + let previous_point = this.path.point(this.current_segment); + let nearest_point; + if ( + previous_point.fake_distance(this.position) < + next_point.fake_distance(this.position) + ) { + nearest_point = previous_point; + } else { + nearest_point = next_point; + } + let tx = (nearest_point.lon - displayed_x) * scale_factor; + let ty = (nearest_point.lat - displayed_y) * scale_factor; + 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(1, 0, 1).drawLine(half_width, half_height, x, y); + } + + // display current-segment's projection + g.setColor(0, 0, 0); + g.fillCircle(projected_x, projected_y, 4); + } + + // now display ourselves + g.setColor(0, 0, 0); + g.fillCircle(half_width, half_height, 5); + } } function load_gps(filename) { - // let's display splash screen while loading file + // let's display splash screen while loading file - let splashscreen = require("heatshrink").decompress( - atob( - "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" - ) - ); + let splashscreen = require("heatshrink").decompress( + atob( + "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" + ) + ); - g.clear(); + g.clear(); - g.drawImage(splashscreen, 0, 0); - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(0xf800) - .drawString(filename, 0, g.getHeight() - 30); - g.flip(); + g.drawImage(splashscreen, 0, 0); + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(0xf800) + .drawString(filename, 0, g.getHeight() - 30); + g.flip(); - let buffer = s.readArrayBuffer(filename); - let file_size = buffer.length; - let offset = 0; + let buffer = s.readArrayBuffer(filename); + let file_size = buffer.length; + let offset = 0; - let path = null; - let heights = null; - let maps = []; - let interests = null; - while (offset < file_size) { - let block_type = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - if (block_type == 0) { - // it's a map - console.log("loading map"); - let res = new Map(buffer, offset, filename); - let map = res[0]; - offset = res[1]; - maps.push(map); - } else if (block_type == 2) { - console.log("loading path"); - let res = new Path(buffer, offset); - path = res[0]; - offset = res[1]; - } else if (block_type == 3) { - console.log("loading interests"); - let res = new Interests(buffer, offset); - interests = res[0]; - offset = res[1]; - } else if (block_type == 4) { - console.log("loading heights"); - let heights_number = path.points.length / 2; - heights = Float64Array(buffer, offset, heights_number); - offset += 8 * heights_number; - } else { - console.log("todo : block type", block_type); - } - } - - // checksum file size - if (offset != file_size) { - console.log("invalid file size", file_size, "expected", offset); - let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; - E.showAlert(msg).then(function() { - E.showAlert(); - start_gipy(path, maps, interests, heights); - }); + let path = null; + let heights = null; + let maps = []; + let interests = null; + while (offset < file_size) { + let block_type = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + if (block_type == 0) { + // it's a map + console.log("loading map"); + let res = new Map(buffer, offset, filename); + let map = res[0]; + offset = res[1]; + maps.push(map); + } else if (block_type == 2) { + console.log("loading path"); + let res = new Path(buffer, offset); + path = res[0]; + offset = res[1]; + } else if (block_type == 3) { + console.log("loading interests"); + let res = new Interests(buffer, offset); + interests = res[0]; + offset = res[1]; + } else if (block_type == 4) { + console.log("loading heights"); + let heights_number = path.points.length / 2; + heights = Int16Array(buffer, offset, heights_number); + offset += 2 * heights_number; } else { - start_gipy(path, maps, interests, heights); + console.log("todo : block type", block_type); } + } + + // checksum file size + if (offset != file_size) { + console.log("invalid file size", file_size, "expected", offset); + let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; + E.showAlert(msg).then(function () { + E.showAlert(); + start_gipy(path, maps, interests, heights); + }); + } else { + start_gipy(path, maps, interests, heights); + } } class Path { - constructor(buffer, offset) { - // let p = Uint16Array(buffer, offset, 1); - // console.log(p); - let points_number = Uint16Array(buffer, offset, 1)[0]; - offset += 2; + constructor(buffer, offset) { + // let p = Uint16Array(buffer, offset, 1); + // console.log(p); + let points_number = Uint16Array(buffer, offset, 1)[0]; + offset += 2; - // path points - this.points = Float64Array(buffer, offset, points_number * 2); - offset += 8 * points_number * 2; + // path points + this.points = Float64Array(buffer, offset, points_number * 2); + offset += 8 * points_number * 2; - // path waypoints - let waypoints_len = Math.ceil(points_number / 8.0); - this.waypoints = Uint8Array(buffer, offset, waypoints_len); - offset += waypoints_len; + // path waypoints + let waypoints_len = Math.ceil(points_number / 8.0); + this.waypoints = Uint8Array(buffer, offset, waypoints_len); + offset += waypoints_len; - return [this, offset]; + return [this, offset]; + } + + 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; + } + + // 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); + } + + // 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]; + + let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]); + for (let i = start + 1; i < end + 1; i++) { + let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]); + + let closest_point = point.closest_segment_point(p1, p2); + let distance = point.length_squared(closest_point); + + let dot = + cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat); + let orientation = +(dot < 0); // index 0 is good orientation + if (distance <= mins[orientation]) { + mins[orientation] = distance; + indices[orientation] = i - 1; + } + + p1 = p2; } - 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; - } - - // 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); - } - - // 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]; - - let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]); - for (let i = start + 1; i < end + 1; i++) { - let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]); - - let closest_point = point.closest_segment_point(p1, p2); - let distance = point.length_squared(closest_point); - - let dot = - cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat); - let orientation = +(dot < 0); // index 0 is good orientation - if (distance <= mins[orientation]) { - mins[orientation] = distance; - indices[orientation] = i - 1; - } - - p1 = p2; - } - - // by default correct orientation (0) wins - // but if other one is really closer, return other one - if (mins[1] < mins[0] / 100.0) { - return [1, indices[1]]; - } else { - return [0, indices[0]]; - } - } - get len() { - return this.points.length / 2; + // by default correct orientation (0) wins + // but if other one is really closer, return other one + if (mins[1] < mins[0] / 100.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, scale_factor) { - let translated = this.minus(current_position).times(scale_factor); - 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) + Y_OFFSET, - ]; - } - 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 londiff = this.lon - other_point.lon; - let latdiff = this.lat - other_point.lat; - return londiff * londiff + latdiff * latdiff; - } - 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; + constructor(lon, lat) { + this.lon = lon; + this.lat = lat; + } + coordinates(current_position, cos_direction, sin_direction, scale_factor) { + let translated = this.minus(current_position).times(scale_factor); + 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) + Y_OFFSET, + ]; + } + 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 londiff = this.lon - other_point.lon; + let latdiff = this.lat - other_point.lat; + return londiff * londiff + latdiff * latdiff; + } + 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)); + 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 + return R * c; // in meters + } + fake_distance(other_point) { + return Math.sqrt(this.length_squared(other_point)); + } + // return closest point from 'this' on [v,w] segment. + // since this function is critical we inline all code here. + 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 segment_londiff = w.lon - v.lon; + let segment_latdiff = w.lat - v.lat; + let l2 = + segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt + if (l2 == 0.0) { + return v; // v == w case } - fake_distance(other_point) { - return Math.sqrt(this.length_squared(other_point)); - } - // return closest point from 'this' on [v,w] segment. - // since this function is critical we inline all code here. - 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 segment_londiff = w.lon - v.lon; - let segment_latdiff = w.lat - v.lat; - let l2 = - segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // 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. + // 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)); //inlined below - let start_londiff = this.lon - v.lon; - let start_latdiff = this.lat - v.lat; - let t = - (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2; - if (t < 0) { - t = 0; - } else { - if (t > 1) { - t = 1; - } - } - let lon = v.lon + segment_londiff * t; - let lat = v.lat + segment_latdiff * t; - return new Point(lon, lat); + // let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below + let start_londiff = this.lon - v.lon; + let start_latdiff = this.lat - v.lat; + let t = + (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2; + if (t < 0) { + t = 0; + } else { + if (t > 1) { + t = 1; + } } + let lon = v.lon + segment_londiff * t; + let lat = v.lat + segment_latdiff * t; + return new Point(lon, lat); + } } let fake_gps_point = 0; - function drawMenu() { - const menu = { - "": { - title: "choose trace" - }, - }; - var files = s.list(".gps"); - for (var i = 0; i < files.length; ++i) { - menu[files[i]] = start.bind(null, files[i]); - } - menu["Exit"] = function() { - load(); - }; - E.showMenu(menu); + const menu = { + "": { + title: "choose trace", + }, + }; + var files = s.list(".gps"); + 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); + E.showMenu(); + console.log("loading", fn); - load_gps(fn); + load_gps(fn); } function start_gipy(path, maps, interests, heights) { - console.log("starting"); + console.log("starting"); - if (!simulated && settings.disable_bluetooth) { - NRF.sleep(); // disable bluetooth completely - } + if (!simulated && settings.disable_bluetooth) { + NRF.sleep(); // disable bluetooth completely + } - status = new Status(path, maps, interests, heights); + status = new Status(path, maps, interests, heights); - setWatch( - function() { - status.activate(); - if (in_menu) { - return; - } - in_menu = true; - const menu = { - "": { - title: "choose action" - }, - "Go Backward": { - value: go_backwards, - format: (v) => (v ? "On" : "Off"), - onchange: (v) => { - go_backwards = v; - }, - }, - Zoom: { - value: zoomed, - format: (v) => (v ? "In" : "Out"), - onchange: (v) => { - status.invalidate_caches(); - zoomed = v; - }, - }, - /*LANG*/ - "powersaving": { - value: powersaving, - onchange: (v) => { - powersaving = v; - } - }, - "back to map": function() { - in_menu = false; - E.showMenu(); - g.clear(); - g.flip(); - if (status !== null) { - status.display(); - } - }, - }; - E.showMenu(menu); + setWatch( + function () { + status.activate(); + if (in_menu) { + return; + } + in_menu = true; + const menu = { + "": { + title: "choose action", }, - BTN1, { - repeat: true - } + "Go Backward": { + value: go_backwards, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => { + go_backwards = v; + }, + }, + Zoom: { + value: zoomed, + format: (v) => (v ? "In" : "Out"), + onchange: (v) => { + status.invalidate_caches(); + zoomed = v; + }, + }, + /*LANG*/ + powersaving: { + value: powersaving, + onchange: (v) => { + powersaving = v; + }, + }, + "back to map": function () { + in_menu = false; + E.showMenu(); + g.clear(); + g.flip(); + if (status !== null) { + status.display(); + } + }, + }; + E.showMenu(menu); + }, + BTN1, + { + repeat: true, + } + ); + + if (status.path !== null) { + let start = status.path.point(0); + status.displayed_position = start; + } else { + let first_map = maps[0]; + status.displayed_position = new Point( + first_map.start_coordinates[0] + + (first_map.side * first_map.grid_size[0]) / 2, + first_map.start_coordinates[1] + + (first_map.side * first_map.grid_size[1]) / 2 ); + } + status.display(); - - if (status.path !== null) { - let start = status.path.point(0); - status.displayed_position = start; - } else { - let first_map = maps[0]; - status.displayed_position = new Point( - first_map.start_coordinates[0] + - (first_map.side * first_map.grid_size[0]) / 2, - first_map.start_coordinates[1] + - (first_map.side * first_map.grid_size[1]) / 2); + Bangle.on("touch", () => { + status.activate(); + if (in_menu) { + return; } + if (status.heights !== null) { + status.screen = (status.screen + 1) % 3; + status.display(); + } + }); + + Bangle.on("stroke", (o) => { + status.activate(); + if (in_menu) { + return; + } + // we move display according to stroke + let first_x = o.xy[0]; + let first_y = o.xy[1]; + let last_x = o.xy[o.xy.length - 2]; + let last_y = o.xy[o.xy.length - 1]; + let xdiff = last_x - first_x; + let ydiff = last_y - first_y; + + let c = status.adjusted_cos_direction; + let s = status.adjusted_sin_direction; + let rotated_x = xdiff * c - ydiff * s; + let rotated_y = xdiff * s + ydiff * c; + status.displayed_position.lon += (1.3 * rotated_x) / status.scale_factor; + status.displayed_position.lat -= (1.3 * rotated_y) / status.scale_factor; status.display(); + }); - Bangle.on("stroke", (o) => { - status.activate(); - if (in_menu) { - return; + 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 + + 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; } - // we move display according to stroke - let first_x = o.xy[0]; - let first_y = o.xy[1]; - let last_x = o.xy[o.xy.length - 2]; - let last_y = o.xy[o.xy.length - 1]; - let xdiff = last_x - first_x; - let ydiff = last_y - first_y; - - let c = status.adjusted_cos_direction; - let s = status.adjusted_sin_direction; - let rotated_x = xdiff * c - ydiff * s; - let rotated_y = xdiff * s + ydiff * c; - status.displayed_position.lon += 1.3 * rotated_x / status.scale_factor; - status.displayed_position.lat -= 1.3 * rotated_y / status.scale_factor; - status.display(); - }); - - 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 - - - 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)); - - if (go_backwards) { - fake_gps_point -= 0.05; // advance simulation - } else { - fake_gps_point += 0.05; // advance simulation - } - status.update_position(pos); - } + 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); - setInterval(simulate_gps, 500, status); - } else { - status.activate(); + let alpha = fake_gps_point - point_index; + let pos = p1.times(1 - alpha).plus(p2.times(alpha)); - 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) { - if (status.starting_time === null) { - status.starting_time = getTime(); - Bangle.loadWidgets(); // load them even in simulation to eat mem - } - status.update_position(new Point(data.lon, data.lat)); - } - let gps_status_color; - if (frame % 2 == 0 || valid_coordinates) { - gps_status_color = g.theme.bg; - } else { - gps_status_color = g.theme.fg; - } - if (!in_menu) { - g.setColor(gps_status_color) - .setFont("6x8:2") - .drawString("gps", g.getWidth() - 40, 30); - } - }; - - Bangle.setGPSPower(true, "gipy"); - Bangle.on("GPS", set_coordinates); + if (go_backwards) { + fake_gps_point -= 0.2; // advance simulation + } else { + fake_gps_point += 0.2; // advance simulation + } + status.update_position(pos); + } } + + setInterval(simulate_gps, 500, status); + } else { + status.activate(); + + 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) { + if (status.starting_time === null) { + status.starting_time = getTime(); + Bangle.loadWidgets(); // load them even in simulation to eat mem + } + status.update_position(new Point(data.lon, data.lat)); + } + let gps_status_color; + if (frame % 2 == 0 || valid_coordinates) { + gps_status_color = g.theme.bg; + } else { + gps_status_color = g.theme.fg; + } + if (!in_menu) { + g.setColor(gps_status_color) + .setFont("6x8:2") + .drawString("gps", g.getWidth() - 40, 30); + } + }; + + Bangle.setGPSPower(true, "gipy"); + Bangle.on("GPS", set_coordinates); + } } let files = s.list(".gps"); if (files.length <= 1) { - if (files.length == 0) { - load(); - } else { - start(files[0]); - } + if (files.length == 0) { + load(); + } else { + start(files[0]); + } } else { - drawMenu(); -} \ No newline at end of file + drawMenu(); +} diff --git a/apps/gipy/pkg/gps.d.ts b/apps/gipy/pkg/gps.d.ts index c881052f4..3f1c8f372 100644 --- a/apps/gipy/pkg/gps.d.ts +++ b/apps/gipy/pkg/gps.d.ts @@ -12,6 +12,11 @@ export function get_gps_map_svg(gps: Gps): string; export function get_polygon(gps: Gps): Float64Array; /** * @param {Gps} gps +* @returns {boolean} +*/ +export function has_heights(gps: Gps): boolean; +/** +* @param {Gps} gps * @returns {Float64Array} */ export function get_polyline(gps: Gps): Float64Array; @@ -51,46 +56,3 @@ export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: nu export class Gps { free(): void; } - -export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; - -export interface InitOutput { - readonly memory: WebAssembly.Memory; - readonly __wbg_gps_free: (a: number) => void; - readonly get_gps_map_svg: (a: number, b: number) => void; - readonly get_polygon: (a: number, b: number) => void; - readonly get_polyline: (a: number, b: number) => void; - readonly get_gps_content: (a: number, b: number) => void; - readonly request_map: (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) => number; - readonly load_gps_from_string: (a: number, b: number) => number; - readonly gps_from_area: (a: number, b: number, c: number, d: 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__heb2f4d39a212d7d1: (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__h362f82c7669db137: (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} module_or_path -* -* @returns {Promise} -*/ -export default function init (module_or_path?: InitInput | Promise): Promise; diff --git a/apps/gipy/pkg/gps.js b/apps/gipy/pkg/gps.js index 39c2a6804..5c9bfc9bd 100644 --- a/apps/gipy/pkg/gps.js +++ b/apps/gipy/pkg/gps.js @@ -1,733 +1,2 @@ - -let wasm; - -const heap = new Array(32).fill(undefined); - -heap.push(undefined, null, true, false); - -function getObject(idx) { return heap[idx]; } - -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; -} - -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; -} - -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__heb2f4d39a212d7d1(arg0, arg1, addHeapObject(arg2)); -} - -function _assertClass(instance, klass) { - if (!(instance instanceof klass)) { - throw new Error(`expected instance of ${klass.name}`); - } - return instance.ptr; -} -/** -* @param {Gps} gps -* @returns {string} -*/ -export function get_gps_map_svg(gps) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - _assertClass(gps, Gps); - wasm.get_gps_map_svg(retptr, gps.ptr); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(r0, r1); - } -} - -let cachedFloat64Memory0 = new Float64Array(); - -function getFloat64Memory0() { - if (cachedFloat64Memory0.byteLength === 0) { - cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); - } - return cachedFloat64Memory0; -} - -function getArrayF64FromWasm0(ptr, len) { - return getFloat64Memory0().subarray(ptr / 8, ptr / 8 + len); -} -/** -* @param {Gps} gps -* @returns {Float64Array} -*/ -export function get_polygon(gps) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - _assertClass(gps, Gps); - wasm.get_polygon(retptr, gps.ptr); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var v0 = getArrayF64FromWasm0(r0, r1).slice(); - wasm.__wbindgen_free(r0, r1 * 8); - return v0; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } -} - -/** -* @param {Gps} gps -* @returns {Float64Array} -*/ -export function get_polyline(gps) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - _assertClass(gps, Gps); - wasm.get_polyline(retptr, gps.ptr); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var v0 = getArrayF64FromWasm0(r0, r1).slice(); - wasm.__wbindgen_free(r0, r1 * 8); - return v0; - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } -} - -function getArrayU8FromWasm0(ptr, len) { - return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); -} -/** -* @param {Gps} gps -* @returns {Uint8Array} -*/ -export function get_gps_content(gps) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - _assertClass(gps, Gps); - wasm.get_gps_content(retptr, gps.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 {Gps} gps -* @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} -*/ -export function request_map(gps, key1, value1, key2, value2, key3, value3, key4, value4) { - _assertClass(gps, Gps); - const ptr0 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ptr1 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - const ptr2 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len2 = WASM_VECTOR_LEN; - const ptr3 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len3 = WASM_VECTOR_LEN; - const ptr4 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len4 = WASM_VECTOR_LEN; - const ptr5 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len5 = WASM_VECTOR_LEN; - const ptr6 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len6 = WASM_VECTOR_LEN; - const ptr7 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len7 = WASM_VECTOR_LEN; - const ret = wasm.request_map(gps.ptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7); - return takeObject(ret); -} - -/** -* @param {string} input -* @returns {Gps} -*/ -export function load_gps_from_string(input) { - const ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.load_gps_from_string(ptr0, len0); - return Gps.__wrap(ret); -} - -/** -* @param {number} xmin -* @param {number} ymin -* @param {number} xmax -* @param {number} ymax -* @returns {Gps} -*/ -export function gps_from_area(xmin, ymin, xmax, ymax) { - const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax); - return Gps.__wrap(ret); -} - -function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } -} -function __wbg_adapter_84(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); -} - -/** -*/ -export class Gps { - - static __wrap(ptr) { - const obj = Object.create(Gps.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_gps_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.__wbg_log_d04343b58be82b0f = function(arg0, arg1) { - console.log(getStringFromWasm0(arg0, arg1)); - }; - 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_object_drop_ref = function(arg0) { - takeObject(arg0); - }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbg_fetch_57429b87be3dcc33 = function(arg0) { - const ret = fetch(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) { - const ret = getObject(arg0).fetch(getObject(arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) { - const ret = getObject(arg0).signal; - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () { - const ret = new AbortController(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) { - getObject(arg0).abort(); - }; - 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.__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_new_abda76e883ba8a5f = function() { - const ret = new Error(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { - const ret = getObject(arg1).stack; - 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_error_f851667af71bcfc6 = function(arg0, arg1) { - try { - console.error(getStringFromWasm0(arg0, arg1)); - } finally { - wasm.__wbindgen_free(arg0, arg1); - } - }; - 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.__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_84(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_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.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) { - const ret = JSON.stringify(getObject(arg0)); - return addHeapObject(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_wrapper2245 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 267, __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; - cachedFloat64Memory0 = new Float64Array(); - 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('gps_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; +import * as wasm from "./gps_bg.wasm"; +export * from "./gps_bg.js"; \ No newline at end of file diff --git a/apps/gipy/pkg/gps_bg.wasm b/apps/gipy/pkg/gps_bg.wasm index 8e0fbc07eb7d7cf8a96dd646305f808eda4e49cc..bbc9ce6a7449547ed257a72d61207453d2938894 100644 GIT binary patch delta 266102 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

TJG^>?hdH_6c@S?gTq2z>y|_+OqblLj;B} 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≧ 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 HW$^z40Wg=I delta 269785 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 LH>yJtsZW|`>zn~@3T*DPr*(nRfOHn23 zT{I$X-!#WY$G+TbsyrJYGNu+ z>l5gPJpG1A{Czu^GNnNEd8^7z`O@eP<&coZ%t{I~gdnttsyH-xsZixhCaxrF+J`=zsb*Unhx9?cdcns zeOSnolL7<%U~MI5qiGNyj~ep|thmr+qdBJjRC06mupQ#9Ux1k9--;r{?v{-RJJ@QL zOx$QL@!$~tc@r{wUyxhhGG|pG-%Nu^jykI6KPR8Q1zqY(iNB2;kde~zZPTa`r`CU% zs|4)!X$C;#OagnRjtaF!Se8D?mTz{MBANQO8C-8DOc5>z1>%uoU*KZkcK)Z?NXI#I zj=8%D0DuOSSa*y`=`;a7V}IR`nrExcW^1#1XmSQ`1AujW6CcC3RqzB1wkOhabdq z11xEof}rb9m!a>O!p!N}u=w-pE}8e9Y2(8XiGFm5ph6JQ=-=|)dnUid%Fm!GJ_CTD zbpQ+*%Z~)Rp=7TZKYf>5@Pu2IiKWtI3)I%{<$^6pEEyJ;ky~K*94YI!m_|MCO_G*S zTVh)-)Dvxa{$}{0(YRtbTK4VPpVcq)2&{+&$v40e43 zQr|2XzDKolRh<&DXNxJi1S+%UlA@)eXh#y$R`@1p z^#2-pwe3*flJOs!=H45!;zM&(^!`fnpAStNx$Ohfzs1^YRdi$AZ^rYVW`)$-xn?kk zfj;;TSH_}{CacnZpX_-aJJ_xI2r}_(`RF4kvG2+6AHj2|rYTngDU{uhgP$Yn$llT& zuVa6)xA$G-6v^jsKw*na1Uut8xWeQyYa_u!7yM5`zT> zd7*rQSX*9w&3V1Rb#Ya))|;A#?!+(euZ{%w`@zJY^!L!6DEWC!Bv=o79y>7$-9NAs zdiB6g$gv-r6OLvK76(H{LMd9^>{~M(Pol&Tz@d@!jpG5R5!2MUUi(gPyReuPCNkDV zCj~3BC@y_}HiZq`lyZaWGID1??o6IPI*flw$o)T?24!&kT^HN&gcs2xv;|*2Tkzi^ zs1-T+Q}L2L1%_fv$OP`dJ@gZ)xr!4hAZ0`CmQ`$MJQ?*N8~Rxrk{+L!W@jVHo_;*` z;IAgU<1H-{ukX8p@i1VhM{cXs|H|ZEd5#K7oHF`ziZCFCJs^?tnCSWFq*8I9`EJew ziBkwszCVd1!O;^Y^b~Pw^$85j*L)t3(ifOIx-~+Izv2@!u*HM>$HyyGh2dQ@9aDss ziL3awY+Gom7ju$9aM?A8a+qkW2~;Ag`;hAtWSm@QyB6QowPfh9;UmUOE-fj(lL-%e zS=a6rPPwkkbzm?YVnJJ|MaTR^AEN5-F&y{D%ac!ZGFMU^CX7 zHzUDBH49Q{23NQ)W9vs%~^wpa$(F1ZimCB zw-(Q)rU5c3Hl$+Da^ZJ8F+Yl@V<*Dbem&+$XGNo&IEfJ0!r4Is4U+SS&|5YL^T!jF z)g-RpmD4^mH%EVs$?Km%!Tdz(e{NbHQCu;LG#LJfT>QBytnJDiDA%Hdzw8SdD@j13 zGU;>EIAb-T2(p!|*lOykP*et?KgicxQGnqGsl3gcv){da?%qD$>m-}DnG$((yIEMg z=Nag7nb4@znD-OU@{?zeF2-5au)RZE_|QsL}fp5-R*eZw=VblT><&*H2}@@x^$ z+9c1uNatxo|2K*5fU5eN9J2%E^!7@>9j1}Iwi_`KyX3dsripyB6Si%b>c;GX@8|n8 zY4x^gSX<@2xnOrh;R^De{A-tKE@O9@n$a1D`S8RqO*`*-+4!aDmT|pXoL2JtKW*X^ z5H&7-NF6=UM4YD-d5_t+ni>e0plQ*A9ayC=4J8D@coJqQCJY$7z+M3M%ZNc=Ksz1rmF!Bb5g6y}_sv~D`?NJNC3Lie%rbMhQsw^qAN zn{y_&8zV}EYzD{!`w7m_Iz!IN0%aa>KgLtNgV7MUP|F{*Q2>VNpefrO_i;yR~0h{G37sC%i3QW z3fPAf`GWKz;JMJ%d7Lri_5xhI`}P7F-a(^KoZ*b86DTt{1aOlk8GFsqK#)#*O^ZrN zH}x91aj!W_e&1*A^Jg&_gJpwx#JKqX?la?ROlO3xUd0iv66g>t+TuUv`fNz>u!49% z;p<=)mwEp&cVxewx&}+Y%kJ8n<*&Kt>XyBGoCDhIx-|1b8>Lu>SFM9XOqQU4LAL9kJI;zv`x?%ld8HI}>qB^4KQ zuX_^#qra3*KZ2F*mG6Eu6RPY^^%%YTDqAM}WNyzbf3W~6Gy@dD%6CeWpW&{bA$@)} zZDD>J_$4^O!k^8JQuSAJVw-oJbOjL$F!PDJ%zRE8RtO5L$~bxpyW+-z1+2@FkfFbs zUfw;j?l;uyocW9CUFXecR|r)qCZm2#1{T*PPjbFw<0EtltsUq!kT)R4LC$L zJhS)K@WQ0{LyK&p?j7ZV3Dot5GUo-ba-0*ONg3PAcE^5*v;}D{AMoaD*i2)DUj`U? z)DKEg*-tesOyMMEFHK(XXip}p1vXMw4*W2lFWX2EClSBW<0KEPn=qlo z*7cs8@VL$EFbq3igE^2mLFnf4^W%`5f!R^t)wv-JUIJeM z4#75~5x(vWJeP}>#^ARG?haxD@tOQ6(n0dIZIV4aMFOWkWaW@Bp3lUu0>uz{w7%_- z9hN7fxC+3Nt@Um7#LL{;)5C*$4bXwDJ@YXqXt+ZJ2q-PmZ2Rc6m|U4=&xwwWODAJ5 zkp{-r3{MK>i!|F=p1>97Y-la#Cu|k@!PrE6l!qmue``aw%>2qUYKJxJI$Xou7?Th3 zcT98?`pj{c@D~uqW89*=G%>stk*wVi{c2h}%)3#}|H0JaTxquL6tGAPM(K)Llkyd! z(`4cgh~|1A-4<2d!?oZ;{Da6HxJ0G+_O$A#Lbf&C_KyA(lU5n_?;VFY@PU6(?Qsdr zN=pym9h%p$Yf=hU$I5Da7J6Sm2dcWT&% zaq1@^y3D_qt(EM!tlhxP6wxoKWc$gmOj~psYv^MxvC$LZ2^+wmOWG4IuK#Zo48j2T z2eGXFz0@E;v!hDngIV{KvIzbb{D6OhLZy*F7iZdLz5i0P_x)M363epJ$v0WHb2ft> z!gWNH7fm3GyE@Ht{X5&N=gGx%f`TO36#k3!$J1UEX}w;j?;zlR9%Ub?KTY6nT*s zg0sJaO&1e_C|Cch3|hE#1?k||DZsD)EsZ+FR^AFrTXUBQjPV~%kODY+uoN*%4WmMS zYh3E)00bB3*v_?XKGZW8_PI7Ci;)fKA{3FbVV(-W^b@pCC^2$olm-}M)3wNh-5!UEhW)GCH*%M4bB1!#5hoaPeJ56u?ltW3nM`0O-6>Agrk`OxNdbxIG{!558EWxtrB1f`In@%R8Hkt+|(!Izz^hV`H%%MDE$ z$fvpXGN^CeYS~7Jkh{8;y|p8YKDx?}5S_FFBpirgCCCbeA7SIr%U7}<&?(sH@2E}QafGw%lZHP3d;dmW@tsWcj+d`u)syFS>8 zfD1XTj;$jzYTJDHlGfC=edN3AKyZ83w%JiswK=!89po+MjowmJ-*)k~$yN1j>(~V# z;a#q<1)bhQEVBax(tpIgA9T9G-#F_!-60bIPc$+lZtGr6eLAd`!IarAQh)5j)5lMjX38*Bx)};BN-npAEQvNJS*jnJvk0k8b-Ze7W z*c#=ybtAbb-<}wKFD5hcZGQCnh|FJtxTW{9wWe6t7$e>z{%9D?v{MD>8Qrw%n#E3? zdNtxayJUV^Ed{%G@s7jC>|`atDt?IP^rj)U-hC;7a_-2j z?8SWyi1j2;i3$q6Nl{^*#tR_Qhn9R5 z6-f71p&pI`VFwT!zzQ0H2R1~=fNp8$WTbIeDIMXQJPyx>@}?SSPxM zV!Y1WkQl&-4fVUk)_~BW&HQGFX7ti|0w>kmH9T$_(p|3QE1Us~3mK~k$< zx%nwStgxBg>;9mb^27S`W=5_5pEOf?c+LFTT9xQdY>q!c|32EQRHt^n+3mVzl&8MN#zH%35A^^b#bAZOs%IH;p7!yb#I5HYjYBaV3 zivQtAT1q6wZ~+gl^d(mu02B2$;13csToOJ;s2;)GVD2*jk?QCK>d#Dj3dbvP!T;7m z;HKE7nI{P+g_N60@})@pO1HMsKi}p`Mnl^;x*#U48``64mwbXo7K9ts*Fl&Es3WzQ z5ueB%4Q-R=GmvNoSionw=H#yaTke+(N<&utm$E1|w;CW*8`;LFKK*7Rn}?sBjqK4f zx}D9CPaE1cO?H5=`%9Ev0T@Z3LHJ#DEgJd!$5ItYiW}Q=s*ZUBT1Bm3E`C%w>JOQq zeEcP-JA`15=KLt*FPpcp1=!(>VkVj{AAcE%U*mZ&H=6prS5orpJHXdp6x`Nd7`F5m z1-H3*f>zq*1$T8guI>SkCI+$7*jd5#I(~=b&ob52+yfSJb#~N)!4pqRT@C}KhSi0Z ze-|_51uyePXsq9eW12i^8A0QGgp6-1A0ec{>bc83l9YGjm%*zS>k@_ZLP>>5fYeCj z*T%NbMbu|uk%FWLgA^VC@`lyFBHCL_6sZQ)3D18jJ*5NUY&bp#LV;-`kcN zMJGo>GnET(wl#`xr>ippA?3%M0sxvn3CtWH^nXBBNAMALPQq7Ul%l*HZ0D(BGbhcv z9^5y`(0B3?VdoA&c-(=iAZP~El7^;?s|TQ`X~7<}0oGl@27%$lF8#p`k=4P_vj2Qk zgU+30g-Z`4WP%BxTg~^9>fdACafmHlJ5aheJnq9hN)_7mUM4so7;UQxHeC2 zZDt$G?@g?!I*-n5q*LZ;EcEZ*N%2di_W)Z<#x=E_kb%6Zscl$q56A=eFu=5MM*|ED z{Un_rB7Q5?&@whl2%6b8(NUjE(A74}o%A_A^y~qRU-?jLTO<0hk=)j{ZniV+qZw$B zZ)b^rlx^t!@e`ZFCkDvB6SiBy8<tgZ`s&~nPmbPizGI{_?rPKn#tp0+EDA9mWtLLhvjiFBVylQDV7w$*%b2Sd3 zeM)<=F?PFgP$mCwf7eKRF9f zsn(k`e@Yh3-we&i%D_4SteGy?9A)c!-^l2rY@Yul0wf6Zv}hzu{BqfLzpWbe_sJct zZ4*=opV`_rlD^-VG+B0(&96~{bZim~tS$%!B6$%b;O~&{kFqU_L+fxVRPg`?hit9a zKvX^hvD5DOBQi8;;1C>)-GVoN7v2ln;idKnP+EoKU+_oyU;oq$OS3li^y0TkL==Pn zdUs}5`Ty>YjA&yA6%PhQRyfL!MZ54nAe%}ZF0;?m@=&;j{o=f`ZG=E7{kOILv+@os zW@uX{qESu552+>fh$?YSUj`*}PQ8nCKLGG)=y|zv?a}rsY*6t2(Y6Si+(g@|{o#tX zHh<6v7DIXmX+oE|d0T)TKVZGm{sD=WquMzuy8>RcY^zp$#}${k2$T0Rm|(QxE8}sz z0M=yxWgHYmi}aNu?B7b!7dv{>0#v^pI0x%tXwLyNB*V;UryCh&P8^Z>aqDcB#3ik! zlo3SyaoK9u*E8f~WOHL8z$fc>(NIdMFS*B(<9j4W{-;1a@-d~D5v|zZ$vwx|)+n^~ z{4w?l`M#aaulhK~a5G)8A}^W@@Ye`NTjmjH^-~3(Yjp zTRESo#N`ESNO&(xV5qFE}ZTbv?=iPxzF;x#>Jl+q4LTL@yIY>?d#*7z^=(X2T;iyLWcj>;+`Nj z%22@p3Bj5=_@RQcE|nFfZ*;1+<`56>NSF0pZEp4LPr{gy#$tb}GXVfRba`@DTeHU9 zjBURXmTD zov-0}tn7Sk*?E8mwT+FumgYYyznx%fWQ|z50*9nD52VBrxPeB1^C9DT)I- zXvycB7>m}-g)PHD2_EWEz!)fLeX_l>I08KgWH|!I-Yhu&;HXprnoL?jJCr>m!L+-b zVOcLUz^6bQn&T$|U?X$jQF9z6@C%?`i^tV~j?-3@*RFIV#@Pj;U9fJ&fa4xA|72U( z?32t2F8?tu!{A`PeqXwrVvlX1#)9%DTYpPv6rOcYX~p;eAdJ3Kr0_YvVE=#5%)Iyat;j%7`8|r+5Xk47cWS{w_~(>@-I{v9zF}1O%`fE-pDS7K=&& zoO{#=NF9ji^I&SkSZMmgD*y@nL_!A+z=3cPDhIVW0O`l0_fQ(?uW% zx@8~fGL3q`?(g}lRQy7Dzk{uFIm7YE^*BvR@~HqL8i!OsWu6IOLL4=8?2sm^Ay~PC zO5A6s7@e}zq{wPn}$!#(c0U)hAr}OT((+npnKFqAnuWoocy#@x7W{SYVsCq5;o-KJ zoRfp7AAhllgXuzt+eDrl*TEFCv!#0zTjxU1{O{pxP3sct0=}pqPB#pIdw`NpWC_|n?Fxtg zC8@1YvB~u-sxknJAx5wW2dri85b4(-elRFRyAFl18rfS8B9f2R_NQmbnWx!ir&Z+3 z(V!e34ql8=gv7WjGHZnRmt)4&!)KBVS~i`AzVDIP-|We!v;sXy8Ipnom^BPZn%Ehr z45Mt0hBVMe4FWGSJAgh$yMWbiMg}U#O^%V$zu893m!R^8E*xH3@X4x30Blu)xsNAX zK1G}k-A^hHOLm5>8BkUQ%>qp>lYZYyCyT@taC6APno*&43$Sx1U1`ry=KydcSDkJL zNR`uV_2_3BF<4VNoo?I7+o#(aow4!6p9qo*Nt6Fbkd#(u+A|7KUxWn%5rIHgl)tw! ze;_IZv0jWsM*Wev3_r_Wi0GDQzcqdH;}stN&;E~p7MHDO*}{BE${3^n1?J50qzb7C zlU!Dv?W|GzCV8AnkV26Q@&6nu~RDTk9sV^t6pPw{oNFGf|e!up?Zw(VtS{alKye;I$aZIJy+m3*QFjC8SJ+E23TY+Elh z(n-~GY*F6JVdSW#um}Z;l6$Zh@~Qv&L)Rh%@8)xC_2WK+jqH%jmcq%wnXT*@fq19F zQ*GTN0n-WK2YQbMg1XET43IIR7~f8p`sq*4rt#C4HRm9-ZI}Fbjva6bll0uvZocsV z>c_;-ObJ3xD<;3Dol>`GbA~ zqJ>`LouqzJB~P>e;+IF{vh!_&ep_?Y8x{@{yMvSb{BPzU{ zO>8t$`2uL1AIhK$Z1d_GR|df^)+O3So53Gn=3QWO+x}LCb}-H_MI-bwa`Itz2qGvu zv{5@G%`db~Gv+`%hf5+&`dx=0fSWJ0)$|9P|usYciTk0-*Art@PKr`;Pm>i5J-iiwBZ_$M6}zBe$r>(&!-zwX%6)AQm3D z7eBAU8byQ0ag{o|%FSWWJ%kc)B}i>7U_jvw&v0NPV8wJ=Ia;LZr}MVZqjb)d?0c!b z;Npw%0Wyh+2#sT55!5Hq4ern(XgqodbU?S@33FN1;Q0wqhY?=Lfw@0kBnbD%izN5Q zBcJ5{cry1=+o9g)+e;%sGwnby<_jFp-2pP_0z8mBmv)(LXQ%z5lrgvr?wl88#$~oi z*{&SeC&GVQKEBM>EZz)zL)ipX0gDFL&rq%(8tS1ZQ~9FHfE6yNs}=D_wJiodLKjKk zwhMuzK8DWvp`V6AzstQ*fJPhr@UH5{7lEW9?)fsCcd(A-?uI?@U>%czm)pBg6z|`c z+ZHnaGMhNq+qHMS!3r74buT|Gsox)z?_D{zzYV}02cHD%>Q35$nl@HV5u_W`i!_VY z$oRl=A<+@W(Uuq73;QE9F18h@0 z&YB!TTLcv#>%Owe%${rdxoE^buA>#S-$zxFx zWPt5Za~YXL7IPX%sZ`mW1P8kG8DyJclc9QpZ0)MQGXD%+0Y+f)C?>6xUV{)T_nW)_ z+X44C%FIEwLGJEMho#Pr+B_elQCpdOKFAhCNBYw0DtlCPypdC`wRNi=Fx~NU<%<$q zSc(1SM7%32&I5eCb`7kA^JMfjh|i^vP#~+XvH7{n)IyR`P)?G7rOAmAx&1h~*laxv zu?du%P*Q$$ux(ju?n);elXwJsd9wL6m5!y}wYIrja+R&G$fsX_d3K-ZNJW*TKqRc9AKIM@0ABdfWk)sxfJ7L2itnbti&FI z)B<#zqgYpLH~uJ%8sJ5dU5UrhHT-B^0ltV?@AA;~_S7g)qt#&B*SkRe@sO>1e4Vh8 zOpJt>*(Sr`m{K`dvyYpr5XxdPZZ9J`i;M6S#cW1eR=UCF?N?c@^$p0KLIMAgH`u>1 zm+I3S>`66QI>yB+;D@dtWHhm0ea9Oi8k(CxGhUbaH`zXQ`Zykv6pgT_t`l#7)FPX1 zv{maapetTOqQU=zS?&xdkI8`abIy&nZ`M1ezw&FYf(*I|1Z1{6ev@r<_W1`EWXNIR zGZ3ZZP87l1hd}akXkttA--;JR12~w$51ofV?a%;Y4FLS*9!R&Z0iopOn`{d|6%WG< z&Zs|Ge!0n>2Gr_(vu#uyikMhoCHExobib3aLSskP76FQ;H~vX0K((1F>UGC=A=fIY zPJcJd1KzEihL;@t0y3LzcVbz0){P6+_)lA>Hg^|@1(V^^fk`?!!4$AFr1P!z#4JhL+esV0ztuKu!iOkf z#AsxQ(us%oOE!a{;5Ep5=)Au_$Y=9H;$A{f++aOfrx!bXCOHYI{e7n@vXu~n&6=*sXQ73^4T-OmCa4`Z~OFxDg zp(bhqCmH>KZ1MO-GW&Yl_(WBf2thyw<*5GG24cu(KZ0l=XC2jAHL@^yVRQ1r70D<^ z-Bjv}+imm8&s5g^+jQn$ka~B3nVsrFHwigyh^^a>pavp{ zr9CD(7jZGcT$s0Kl>(p|acQSjazVO$z@M!kwt#y}O7~)fYwVLD#kS_(#%AcW{4KS< zPS@MT^^K=|JN`UuJ_do5y&xnhpF6m32wZQd&AsG%HjrM{z#hJQ(lq7n1mgq}z4|5eb0%dy&&*gJ&q#5E2#5(4`k;`KIFOfxYl*|bm`&gag`6BpY8B6#Y9Tv;NCyJt1CR2+T zRG=^Yq6hK#DIt|ax?2K^8Jii@y19Tgelc|R6JzoQgIUmP&hD!>hD-)0cmrFW4o2BN z=!z)gE-zwoC~0w%2PbJ^EL?iP$LsRV-C)Zv-EC{-&daR$2uKb9!##3J{;2sNSuD)W z&l8wZrMC%9ciFfcIl|^$34Nx_ripTUl#D>5%h@&2Js5`{Pd}7>^9lzOp!1fqOpbTY z=MeE)`R`E|MSi&~7-6ef?jY+gmp4Y(f7*R4%`{PZ+-pxu?}Tab$5xjA-U~zKBlp^4 z>TRLC8|?ZTq%bGgf@*tiw<|yryasV;exJSZ&~Pf*BkhrY@<{)Ywn=nUTpk<=Blq-C z_GE01Icb#bTWu1JVamo&1rRF!ZIR_8?Z`8h5{C6oPlY68;?lh0>O(-zRQ(C;3gd*& z3jy*3q2@e=&&xqnQM5~eddd8UoUSGR83h?-cPVC$Kt2mc{=5unbHBY6p89$B+k}&S z-G?a|%g6Tv)nBqQ89qa=@BSiuwV{xV&y+47+G+Z4nCq{2}iIohsvHq*c6raNw=U2&GVb@g4RxAI)%i}RgvSOUA zAN@ThEymmW4wl@+DJ=QpZ5^=vZR2dh>4#6Y!Lf7(qpnMl5JycRp~RqR%-pxE5P8Iup&~u#XP&|`kq&55yT`9e-+4`n&gPA zn_wGMw4TBKBHlyLLGPPjyLo?;ys5TUhqeDf6aM}Uf5fX9!FK)-Q_=oz9Q$u27cmm= zA%nj~nE#kG9aB@)Dl3eslVcH@h7WScx-Ec1n3s0lhd6tfMtUGnL#`c(_P&8oH95wl}?2bCt`gW&4l_JR3YHCyAgG?5+34oT(mBZGU#Riix?0Gw9D3sP`bBC z=Z8U$w#hvYBWS0|!^ma+On#V%N*ufBq{x z$9yb(*iL8zt_3-0HHdxOp8z)|?mPy2M<^)LO{t<9^ubAZ$bSR7)l11EwoZn@kb%Uz zWZEOJ_3V`Ok04-o_lIc6iSEHKI0m_gbx$@tV%xXd1#epN-aZ_=d-1<%XM?p%u;h%; z!F;gy!N%}{jC$0ziS>rGzUgaI6^0H>8)DSr(r}VJpGpx1gU6W)3n^S)dK9Y6D8^|( zxWdEnlS4X>ydRx~{74KRAYi1b`XH-8Q+M?jSq`Z7!~80~zzi-;-Ho4wc~41_Yyu6u z3hJ%1j^@1Bg*3Nc1`D=Y(u*$N^GA20)`OOG4es~Cuv z3y45)_@nnA3Sc*B@$S-NtZi0ve`(e8U&6F{GUf?;S+)I&zf*eqQZgpncA1#~uyRzl zA*Ili`HgcFh9Yj91761&0)c*2zM5>ip0+>xJCKV_T3L~cQI*H4f(&a)7O^bi-$SP$ z7ki|F7+9uT?+ylaxHiI8b zDtWy0e-ifUIoN{D_HaK%QhTbcU9^`)&a(Gs0K-<`D(zCB{Nr(qsD|^WLK|Kn#Zzr7 zNuLH6;C<6@A%#IJry)Qfbyj)0&CkDw%Z%Z5j=Sw|JpZR0e_{@7M;=DYl&e0|Z4t~f zArd|?9m!N*%7*EH_b;XD3@GHf;|$gcHB&xFmr*lpoBb}WJ@8W2OfcI1Gi9zA7- ztzD~ok_-WVL5xEqOw1W!a;45J+tGVa{yxhlF6yk0hW?c1#cbfLzqg-;FDd17Yg zu(iH`O|6%J!41?UEDMvUMxRMoxiAeSHb0e*XW0|tFlbMJcUIEea<=q zcQmkpG&JT?$)K(Rc7KEwfh>UrHQvN+`bwn7Y+JbBxc@oZcBnlE1<(kcDlZb$9w%bL zF!!{qn{As^yzf=G?>}W*rk4P@fKD3x>NUI1HkY!7@<76xTP>*gl$F`Iw<{%R1)VcNu?%$D^ z3ol6)PRvO7n!3w`OHh|;_^eW$glN!ME`Hk9ViD1Pb8Y>j7gBB5jY<`|P%K%&g`x}U zS5S{&Yw`}%R}^-^s*SD`m;AXfr0;&(R{HziS4yE4LmA|^dIKCBQFVGWw!6^^<;vlp zf(Dykb-jpi?`ZfRl0Dfmi8GZe%ejzk@f zfN1+aLR2J%;0qy!BvaR;j;VK7%?^srZ}Z_JA1D_tut&K*bW3*Sw*|JF+`kZX?mk(t z5c!^6dAvd{Ux+$Zp#>*pl}!Q!?9ghB3^qeNCvGC{0Mo!ia0(8o!f$lkQKL371?U1meA!sA~&1=u^(czXAp1LqYVK`vKAO^5pv+$kkIK`s=~-K=rnBYadq{a8nt}2%1YD}VZJQR-?!a+@-e8o_ z8KE2yYN==^AAJ2S+qBN19+aD&vBv~~gYl%{9C*qmxiBxNp^>q|SQye_{RF?8a6vnv zg3Vs%%|_HF`pW1L&JmWj!9Ys?4Zx*aJco?zw`JgSU|~gzZTtLjnlGzVwHwwfTqAr2 zjYDfewTz)OH-9M)FSaf7W*yKpd=_ZB6`H>4S=-0!=B(3m<Yj6WxV z$;9Iwl|?!yV*5lT{nuGPNZ?Aa$17<9dFP^vXoyo!l7HP@M@0G&>#&{s-&9U_ga2LWpEK}@x6tl%%I}HWjRb$GOr8f6( z6X50yOz;SQAaLMLFqy{-_=i{x`D!ta{bzZi%S?k<3N_UlTP8MSL2P*74cj1nE8lr7 zE=`}ec`f##?oLoS_=rCwdLvMYAlT;4^b5OKm?!xKS^R=6%$ufLYy)=DT@-2ToOFmm z%9a~nu+7e%`EV(8VRQ;Sc{`3lQ5hf-;y_s@Z`$?vqVrKAlna$1Ap<#jOP?(K7@_;p zCFv&QhUe|E(XmG6J#Xvh(FT8T4)KHRdfv7^V|}`ckWfbaEr@ng@rre<$il%`(K%M9 ziucBv@;}82w`Ca!5vbHxpf2wr0U2!63x@C3GfBK?3vxTB%;NA!_(Waxt;~__FG32{ zpQ6egh;kVE%z`w3lzg(x_KdEMO4FBMJC2sx+hyo-gn&<%D_=&bg%g+CqbmIh0_m22 z^vehz{8XNP89BgR&;$U5%HppZN+Ur&vU4{tM|=R^k50~%N0%eq!!E~%kCnO4*m{@3 zl$}C|{jiA=Y#DqYBv|E2?iG*7n>7*V{U|uMN4yp)4Crf?zm>s-^1=$+{D=>gc0vHGc)iw|1Fky=ZmXuDdLKeXV>vK< z6~4(3yeat8h(B1CuYhClK2=qcP0Mw!*i+^ES8U6pKR^UsvN*`n?*Qx+!6;;u4V{h( zphh@yqQ+UTVjTv(X7i%2Wy{Z<5dbs$HCxS}Oa&ccQ7{b$*h)mrz4CRr=NiP1Bkx)| zrIf0@)9<~@L0?MUm3++R6xq1S*7`dM7}gRZ*7TT5u-^>nGpG^lUWmq@uS79Y!H^|h z(G(~J2?AbWASQTE{5Y~~R#Hgn=X}Bc=AqR#@02GQDW;ltJ166~XJTCvD7u=Zo3~J% zdxxeW&tJycv}I9yF-GmdXrRs)f3MuI%62#&b_mCm9g$_%(kNYm8qqeT#|M!#cW=dL zVC8MF+9qo3gVq}w4WO=2sPg=MQ8{72<;r{>cYr z=sK+PGMT*2c1rqvxSF-2zh=6rI&QtKS$)?l&^2g!2$+|kXRtn8u^tw~_hj^X+u*1J z`~1zHeR}SCe0r0-xgODKJLJFX?dj34GUdCqww`R*V4G*$mz@U$s27(uiQUpRYLHO$n6bPdQ0X?973sY{K_732ZC1B7e-L+Q9ODm;V(dO;2y##G`tp@nN z5SdUP4G#1+-Wu7s$sWtSK=|{5`&ld9-m*2<3IdN{DL=Ss5H}E1Oo@8d4QJsV1(F&G2$-35Um<|i`{KMMc=!ze7jK(F&`@pG`aqeu2MRU}lwA@_ZM9-9UQ2{lt4IVn z3!e^^zfiD4M^~h#BV2@(QUpRrPb0`4lvOOFKPrpuA3z1cag$_=9Anh0nnnxPXgvTe zj5g3s9seYx>D&HdO5<|p7~8n+RusH&29*VRL^~V=)m=D&b>*~oY>lQ2y`G57adsqG zor~NHw4Z_S=%2*Q8S1omZSCS=j2TJYZmzc>Y@|Smpzt;= zWC+fqCZ?>wNp=UUA(20YEErY_S5OTS9MQpSJFIb8utt;e(HdW3c^It0+EQgv8UgD( zO!lS6d$wNjQf5P8oY>w0ZdNCr9OG6GxEI=c{I5OqdMZYSM@lJfx(LSbFs3!14g|qN zbpJ;{GP|D}iMMpZ5P(csi<|WAQi{ox&L1L$>xU|id}qCnoTUYlcpp0PbZP%ST(VQ- z!W*&0@^$Y+9Q^0Iu#SF=SyHzWC|6u+Z-L|WZNw2{cyDK4j_nX_!?N@Ka_tsdSp9A6 zb&-fZIayI68PzIByl2~{%}oPd!?#;imfQy9A?B}2qMed9uKeKzH$)iR00FfMNJ7$p zN=zjZNKt5i=3WUH#n~4fB?vwFW=vQ<|SN82g*u=EVTO@dB z3sB&J_w9h30{~TtO%;wN>Tjk~nbcH{{}BGS5_w{U&2LUZm@*r)%N-){0AjJKV1qiP zg7_K*LCEsb^aBLGwV!ZWdbL6?SiO7|YPAHL_~Y!_!4@3DUtR7y?Ir6&+pu^Sy^63S zxya-;sc#aF8BgMbG?hjfX9^vdz#pu{F?voNnD|^Ka8WW&2}pWGlRoFoA4>|OOrvZz z286Wt5P<&>hoG;iJqGp1kVu}cNrS58K};w|Dk8zJyxoIQGf;=zHYdzjXvuwEtH~!H z*&LLh-}4bl)UTJs$F@U@W*7ijf-|IeL7YY#oB>DuY2ZsoM(Bi$2bm}3#XCl1&yTU( zY(Q~Z?VyM}{>TV?YCod(d)Y*U5+5Q7#;I807Ai@a7|j!M&jVkn_Sav@b*})}6&?=j zGZ}yVGg*J~N{e!kkeqry!(R(P7PuikD{s%9<@TV^xjghc-5qWu|Z4?Sw zN6V0nwnh4pk?2WDiK`wv;)EzZjeiCF!&ft4?2|^@>?wJa#~jv9&US)_mPw$xDl4|x zys8YxP&tf0Sx8aJiORRzY?GWhhDbphJ?8!Wc)T_7E|2md765-Ri+5vtK-)U)C2Y8g z@`lLCedIIjxH6`Su6e!wsTFNX>p8lCMFfpY#Cqf*5BK8FZO!bXkWmMeb&ESm8h?&h z*nfO(I~J!X0j#_Ok$D9Yps_6B?r4+l0-+xXYJg@zzQfgysPt7vMc`z*(OFXt=iQb% z?+I*`P$eNpZHEaVEA2Y~qbjz) z?=H#S-FvgirV!HE1VTbfLP8C7K}0MR!HPaX zYhaYJOIzuwT#L40fPEbarCSCxZE~MBFT`+wrWD$^Uu#@csjCCE`QI}*Ph{{Dk-<-c z@g+#C4_0kJHy=))yr&Vr;{~?(xMh6grH$Z5)N&!ytmJ1)V#LZ#aG)U`*6!ECj@j75 zI71n+i};$fxDIIis-Gbm@VT^BlRy@@IfD(8g4zHK1KvLaOu@h^l}5p$lnXW{=-nXibl+AYdzSMYUY zoNE=xeeIc7I{!s|2(>=mH}eS$8+y`m4@)|Uub&TFi+hL5j#_NdIG4q@`Rbnf;!4B5 zgytebX|VTBTE=6F-u{%u=~#p-#&ATgwyp|K;V8-vr!0-SzJ;#Juq?O^SBGfwPQx8Y zl9;QIRwlt&hVtfOArtvS9 z*e+j$)}3ys*NGkX0P)z({FxkAs3W1+*|_tT^th=Gl{#~gjP}t;Lo%=ZV#!XeJ*$B{ zyKsPyKEaF5r?W}x4l$j0@p-o6yd~a(7s1Rpg5LU9ODkK|Q7E?e!qjxW)*n6<&jmF` z?ikg(%%j=)UoCN=SFsR4eI{3NhuQb%FipxkV+jkLfYLQMoJS#t&iy-&y>iB4Hyvi& z{ja~fhdxo47f66Tb2<=^&G?JkYaKr^#CoM}3_PVjufrRE9p2))JS31AbeQ`1@n>-J z_0xZIO$ozu*DhQi4rS*LSQ@cwzgv_r6xDxlsrrv$uKlMlp-v>xo&! zSxcj4*Xrbw4!iO`Lhs`-62ZI>?-#f)SR(DZAoEjqR;`9t)(K8EoDvS6@lmufPb_;0 zQQ*A%TNf-zHnGGupY6V2X=*1(9Vn^a&CK*0q+wFsh%lBPYkMS@| zzF>(9n^HH;+JX(AT-Qa*P2*6ojS^TZ9QU2I*uznadO@_J&g~@|%eiQ2mQ~v)>EnF| z;|p`}gXz)V=OJv$MN3lS`UAweWjl|TEq3s~y=cj4@`-jPT^pQgdP*yG8m#i`K=gv2 zhDoBK1@VFhuU^4B{=|YKX{+Q*+Bt3-wp_Q07hSK4sc6IBxqbnHZOlNQOO{T}ucM-{ z&}CGBzfkvtMcD7pSmJW0)`=oeFVp_!kP;4Fy~64C zCtmH{wNX2=jSgq+MppPI&QD^tFS(pf)9{gwoe)djAlT_Emed9t@IqsszwpioHn89L z3mwB^EViH`FmGZQlEp?}G$#C~xQ>c7{0m**=YL@qaRzFWHl+C=7y^Su8CHJ963q_+ zYFd09>wd+OWMEunGwU+0aYhJQjjs5=3}HR4SP~7KeHzCQ%PTzE(Aq#xLS3`AS1tF* zN9ht9tT%7CYDrB_4~)CC^&qST<1$35oKVQ4Ac*m;ePGQl(by|@8~P=WlELZW0Jd=4{T*GndBDi zR(`G8?fNA z{zrHXckPD3Rgahh(L9nCVEKuE$7gJvS&lWeU~Mi#ukUUk+ss?=+Q>DGdCE^%WX=iN zjc$}3HOUz?8MqcCN5|;e&~<7N^qbc7UHEuL5c|G?%y$YX1=oK1R=2@bQd{#;7M^JD z9ynoxlvM}GDXD)SoL^@=$F2s+je3iem(m!uPTZK${IA1&c7L$kxX2d9&k9tWsckL+o>4UBpR3L~H6;Wte1#S&sGB(-Bw! zr*M!qEJ4HI@6ltE07qd^bX*7l=95OI6AZ7Z>8MO4x5}}B!NGb~YL$}%@PoSWeGWey}Kf#wsV8 z5qBF!j>abIEsC6l6E5QvISHrCXDf1-KxCWQK}Ak&H!&bPd=~~MU~#*i%fZjTxx!rY zF+(n4}+@= zR0#y0uG&TwBGVzo)G=PiBb_>OZPyNG;$$1y=)4oY&j%Y90oYi-5IG@PFX2Wh|uvYEvtx~?V~g` z6x-TI!_*`$qwvK)HbST9kUy*$;d>+Kds&`c&PcgrC%xXg*6->tTGZAKp`n0Qhq0A* zxruEN_lJ`*>@T~VW=dt}8e)-fF4a~FS>rG{qd;4aLA&C|;-OL0+M%z$ z8bc}YeO}5v*Y1@6SwNaHP62e9Z^Hw?5W)psZ8I7XAt$HO@x3%;$czWYLbJ9QB{}eQ zdF&~fP|xM`o%``&a$Hm`Z^Xlnoeu1|Vg#`#4Ap0K$W2Tun9U)l;F<+8+RvidJ;jI> zEF8k;X`p`Q>+jp6^bj}ymO2h8PlIPT;Vr+ANI4GI|ARN3cr)nBB5G4Aus}(p!=P1G zI=MtgmLRzN%w2|L?m_>YrckpPS8ch@EvcdQKXIu(WI%L=@=T{!%8cyti|(Wk*KGT6v z2Y*2WUl7pVzBr<&b)$AUwX){#c~e=tvP*$1xb9XbqC&&x`w^^11lBR;gtBfCXfbm&({_y(&uDzXavgG9Lv29F zKZrvmNNGBDvUeSFQ{3=VM8+;Q4Thh@6^lmbPv~PaeB~WOEEC;sqj%3-;qi$h&E%++ zo5;dWe&YUSoQ0?IR8()|yhraxYpQK9DXmXM~(!ekO!J zR_BG->(*LmJw2eW$FANMm|gdD=m*&;%PsI}y9X{>Vna<_PPy!2V~;mL(;C(`N^Tmm zS3iA?d=;~TD7lNNhAoJaW9?XYBvq{K0399dcofhVGZ}nQ`i%V<7 z8iHt*q<2y5_#Otm73SK@zk(>nHyE_irQ~stD;{A!B=0{a{2s&}bwagC5~}T*>6>Zj z*Ra>2^1c}E+C-#^#!|N0wD{58ICiF?+{^r4C<~9qLhj)$7E1~afl)M5AlzAI#B6&crv<= zmmp)Q@J-!|6{Mr&n{M&l=QRJR+wO*5n$k1Uh*JA9HCPhL*I6KUxZJI1MNtut6ik zJIUt2SUC}|>kNpM8(Fn?4jflln^>gxzGlqlZZ^Cvj7y&!e_7a`Sh-=#Gc^9dUijG% zx@b=H>-v%pMIPd~N~=9k3M@Q9ZXAo7Xw*1T2B?bu7hx5RS(=IQT#6kfol-K})~}FqunRuG9jBO?h^Wr*A&e+CN`vV!i}98LMft zV5F0O$TOrOP@3Cu7YdaQ>SD2ObzEiNCCE7q4?O>wa9TT4v%)DYQBKD}{sD<{9Df0^ zu^iQ`R>%BiS7RwIdK;8)j?=xI8%Ru_aj;jC}BsseIGL$)u=W8sg znn+;->k;}2Jx0TP!2 z7$nss%0WR{#g2u{l_bXnb<}OqRuW}QMsz-It>d<6D~*s$I*JQw5a5%4x}t^0V~s&b zsEs5xBTj_rol$*p0eN`)FT%s{rNn%eRKb>crwSb+d%xyk=D%7#s z=90@?7VJ8^Lhw2f__OdSRC?Q-j)3!uG5K7JHnD_cIo`HUK~0+If}I}8azg=sxA80w zqmsqLMp5kiwob$IqKZfYQANoA_FeRzj^j~^%=HO@AjhL>6BG%KzvDY>As7j2<7wCK z>GTFEA3C6ovsaTLgYR3onOh@s6^RlhX(kCkS(tqzU5`ENxEOpLw+3(>isK7*tA_zrd^ zMNW&M%lc^GKs_M|=X$!70#_Na)=lIX>#2hV6kWgac-5KNqfO-TET*X(89X=4g?rNb zHf3-c;h4VbRmE@^p~X^+=}?rA^}V#)cZ9_Skc27!X}elN*&FbCf9DcYO_a ztUgowH`UMa9wbSnLlM=}vRr?oy7(A^x(Y5(INc-hNYHG>V?WEDNSEWU{o@TZjK4m)1thn z3UMbICbKk$7&(fb28|>NDugen^Zek0D{2_eS`g|Q8HBEFSo<~(Do;OvA&N&_MY8}x zMOh&6;0JMVivbN%v3pGFnd@wFr(RUy>q3qds2jmjHP&kmwz0V!-TqVvcfqWxr}<7E z{ESUi>A(-kI)+70n$$=wHq-}Fg-GFe&F{@IX_|p(Nj9jWR%3P7m7tG`16k%br{K^9 zqHF}6iG|x_RGLfIlcYHLu{v$<>S1^V(reJ1LGw50d+ce@?co#-$%!C$1mRun zdSSr0ewb(ktRn?zu^5)AcucL(*u@rdb3Y!3OO-BR2txWEvQW4FYAcWD*YPFBfm&s1 zm=;Qm_zxV(G2$J1=Yt$+=|L@qK#Ki!eBn@)j(LCVSdWIG#iHR4>tSlER98VgF#W!C z2`ifMyh8M6*Q9(YjYt#{e)5Xc>EkH$2iGqL-=@+V2HL$Cb+WPyvn1mK{DiD&Lj@j% zD|K#M_i)hx-a>TBr>Q6~HxGzFg_rI^4{))Tv4|kd%7P z-7)FdgiXJ`SIVy$?|I_z`x|DgduRDUS`eVIvD9^NBmcQe*ODz zpFjvHkY8%-TJ4J`&^Z%L!jFp!0lx++j;_Zyxoot40j{I=q7+_$BBS%KY8g$TY<%{` zf(0KfdTQ!7l3&Zn3*Y@YdDY}?PkhI-${*&}U#4Dq;j2x@u4tF0_q=#{?rX14dvSai zWtFN%#QemQd?`{Zf*)3Ve0dFo16{zj;lTR*RzVEfqLbXt@y~uu=H)YPIy;P=kOXxU`j|YN%`q%ndTerM5 z^_X6++b-mIFMd03 z`rbK9Ms)CF8$;^((uGyOeDcQDWxwz$tydb*EXL=m+G&XB*$?OZ`q$zOm#*m<{PvGa zmlyqU_Gnwmpw@Me;&mOvkU}vse$*L!8H+Rrsb`#F)xrqIPr;%YqE=3usafhn3aV7c zFI0ENO5SO&13$BK@1$z!DTA<=R)=BjU``e>q7@-VE)9CiN0RR~h;y-*vgCBktOLu( zq{R{2wS&8VU%B(b(^XtHrJm34T6*Z6?SCwJj~6{S%kxK`ovX)vt;6@151jmF&#_&+f_VCK7X31J#d9Z@oz{!~qZfbO zeC?MRi+CQORy6cSDbsGJuV+u>jk-MATVZ(Cd+cB_R zks~Mgm*I^Y1a2Z>HMNPPhQRLlF$90A!&I37R5@p^#afbMmkw}=EPpEoE+_-a^7K$H zU}VLL5|*#DP^b8qin0Oi`r-tP$YN=og~LmLevHt7DFv;3PU61j0N5DJGsnx}kjdb1 z5)<`+uZIi{=Z(I;L;p*Uup*e|jas71=wxY3AP@d2^nyx40KY*nnIPXAq)gEWi?8}z z*&JB)*{xga=6!;lcdUGrHBd|LDd+@OBzZ%P;w3eLW{frH8EF@L2$xA-94D}|sd?q) zj=s0HAYNCEz+wP@7(sQA!DS75r=1+-UyP6B>8mSMy0vbuPs_E1{5b{yJZBKRy;ehH zdldut)R?s@qszr`gSV9wnYJ3w+X2Pw`nTy}6sW=1&zhuYwg0^F0cIN??LsiFEYmmK z`Mf~eoWb&QJc$l<_2`^xOY8+brcP+nCdUC;w6bi{i>?>wCUU%(NK=#AFo(?sFq_#s z$nBEm<1I+!4a3v(XhZSg5;Bj`6W@1nJj(JqU^@1JG2A+{R@E1189`LF_H?T;K^LVv z4QqeG{sqRB!ahf9k6h%Jn$-k)$zb+2Y&XxqA4t{!Y{y|H;kbrc<6={@*k6OO9Mvhu zSNKJt1WB)GDpuDps5jV52))}D&^5uLWU90V@A}s$EI%OCEwss9qiG#2&9z8Viz-)Y zhb+6UY^B0crQRZrm)Mw&a`T~Ym<3~-iL{8x@ts-ICYYzv*c0X9z_Aoz#b>j)%%8wE zK3pAB6g3E@+CVaKQa66!SZk7S6uw4MFGN;f>nPu9N-xaLau??3w{6#M*s%QkcKKO_ z9ol#3)V5t#;jo-`g#}spo!YnW*s)_48Ga5xy+PS?d&Fha|7l1#8f$w)y$TISf?IxoBzEYL^y@5=pj4&C}0VX86YDL z*7-HEL$bgwgRCGMNCBxjrtl&4FqJ#;t{`kE%rKAxL~S<$lsTfT#FIIO?VKrhVH@3Y zJR3Mnj`nx7I;2r>rb`o8`EG{Y?Il}8H^m)emeB;VJH`yG#%!u#wjP{iJ zB?0#9*S>2PiVZ?x|Am~Kwe%?Y|$;_?zM>Kjn&OLSirek^W#!|%MZ-rR3;gF9>-Kp{D~ifW zE6~IcD^(i(ii`5W_ekSBZm*;`ctsx8Q@zn97IUYZn0ON+xeXEj2BI1_;uz*Sq}=PT z^c59ncq>-I=L7ID5k!?G@H4PeW#^A@d!0oU&eAfU(>179-@XPv)vz1Gd;olx!JTlC z0dIu8kBem{Te*?f6+qWj{YFx4zTZ zUZ`yuaSCt~P&$aDg)(P!PpyZW0Y4VP&U)p-h*oxo^bqg{1Z)qpm7NW*klPo148Bkk z)DA1W`I*DZ$3hq+>8S2TDuLo2<8h8~kM=m7KCe4}WQ8JyYEo;}P2r!~G{q?})e2Pf zO!;O~A_-}plCsfmlD9iGTVJ2Y>-Lp-ox>`JCoe{1q&RwnGJC$9;@=BQx~K=t5inl_ zbpYw#r@q!4cG6)%pcWt_rkWw1HOwJVUqJdl14Q;rP!@=Oq^#P2sAh9Oy54gSFZHl< zC31=RfiO0-RPJYg01;Kf!`bspc9LL&A8Ws~k2XkgTR5T|QZZa8i=m+Qd~g!Vg_c?^396lS zi&r!49bt{qS)JJY0=XsY-d!&4J-eZUtFrl!6Ow&_-_o(xNGo_FZ5pjL;7RaD@-6UW zwxdjrR|deDQoT2toh?Ii@&Qx#{*=xa-d_Wcl0q9VEgPe?^Of*J4_V(YDX%+0BJ7*> z@L#~BFNAw2vQ0Vx*v}P1SS^ENM?D-~4`%~Ygca`5MWw@?XcQi=r@{vfBuTe7(jG{6 z^NsL0%iX>a$_VhO!PVunuu-yN9^Z(?kCKzxzXm^4{`f%;Q_lw9o&sev_|#KUITsfV zCi$IwA?sCj=QRyIg( z5)=bnr?KB21hpc(?}c~DkHA}Dr+f>18Fr@uD=04-TnTqOaDo9-d6Q`7f`mwRhP?}j zDx5^Lt0egYHAs%6n?#M0N9u_LPKTYIPvEAo-(|p5d4#)x@<5bmcTgeJq^H0+##`j0 ziXB!|>h_KUUyBFByZ!)9P+n!}7_Ym0sBeU~Y)qb03&-5Wa@id}$2ug^v4cPvAR}&9 z4^zH{`(W5fKm?{b3D*igr$mm(7yR47r@9n)B6>li~658+EXuJC!v zF{Y8MNl-#mpQQyT-XyK>EiEhXG=&}caok}qo^o!)9x!RvMVgx$*`6e}p{E?z zN@}cW8$WnyAfqNq(A}nhPvKa9xBOyB0l0-geL*xQx(OuOa*KMnB{1<0AWSQm6bm&S zveUSzH)sGVl$z5jdzkvsA+}3PD?>U%#*9#LZ zug`<$t~Vqptt=Vl@uI>pBtZ}fp2~eNX|jd`%^xB7mn~`s8Q!u=#Bv0QP$6=pP3e4I z)CT@kc!u+Fsnjc3mn0gclAllvqWFlc+=FIN$_BoSS$XLmYj~)1E8?Q3q$j(LBs4R4 zF@m?K&bwKD$z1)%Epo8gFNw0vM17N96}TfX$+^Jv99f{jBhZ!Lznez1=&qdJ3ZL7X zAuVd=s1v>neyQDv@K3=Gy&J%jxI1b@cp$}2vjBlr;3ftf0^C%qocXuQv7!0JMVR#R z{!40_#x~t1&tRh}<$LU7+!ZC6C1nM0N=j#&ZpE36rw=F-#{+JscVbO1q^t zt+lsgL;B0PUHbro&6Byi)~}w1A{D@$T8TF9(IzL4>0xeXg#+adsmlT5;6>zy<~k%O z0#ol`(n{uE58?pEx(BeXat?ut;qWqu;x}S+J^{R_o;zWq``f_r2LFVc*TZdr>-A!m z+HUMlR?5?cB&qMQ(=!X zV3N{kq#y7gIvNpxFtrVVX~bkqus<+~u5hR3Y0Lm&>TZPl2-uwlTm*YN%7ySiB1gk6 zfggdLp1^G2ksj3-3?Awv$3|qe(VB%2->f!lawqv=wxL)KVV{)9*0`ncLCPo(lmn`j zB6j{Jxiw!~>C_JnRuCzBdaj~UEJG9(h*@1Lr-#cK2BeZSKU=G?6);szZ$uZ$anzW0 zWwT?2c*g>!ZMd9a-vuuZfgh`qER>xg3hj;6eSuuJz^5$rV$A+76+|d*~N{?zG z88iTdkGKw(+q1L7OI2D+lLf|IAB!2>T;@CC*G{zr$aE<8*xr<-8 zb%7gAa|G^M4?hfSeDLymcr-9Qv5&^%k$jUP^ed}k59+ARDwZQV zHkNdsoIpCLw5-&VU64~i>qg}mdk@rPXKx3m^ZKVJu&EpZ6I#i0c0jVq952=tW zkicjIDyqzN_yWY`-;tWQ2*+0NN8%!IA6Th}z7r&Rr$Mj_oirHsyFf-B>DPPe@$Ln_ zuO9DyU}|>=>kmZu02rj1j1khnXlOlN9a*un+c1&}b0ZX{k`Pnmb5 z$J?j4Y?!-vP<~mtX9CpfP!*MCcQL*XERzze9b zfHAQcg9%A)E}wjlETVb6F_CHTLxaxg6c$98nE|ROtMumM(PzRgp8R>(hgB987kIo* z56$I0UY0OUc85EW3Hm+KLt8L-j7=LS4{vx1kn->tOe&mQC*oqtBXak4^z4)r(F>rS zWd-AI^5kQo;jZ%GUUvZ%x`?%>yI>Hn2!<7y=KnxBCj67}Z6v#vtcGS^a5?yBNU>;@ zWyHzIq`+5$g*#jKsN5~_Erh!b?$_{yc7wlXfg7>t$K)LIv7YR<$8ZTsNH1-FIS!^i zn5ub9PWE>Mb|dIclpRfH1pW+|+PA>_>bZXhoMv!80^A(rpPPJ2k&GnpOp*kuYf~8iY-}NS*@9hcR~71mDT@!-v@nvqV2*UP1stp%sFyq z!&1+fq1cOQF4$ZKc?z?hljFJz_H2kW$EkxLea* z!fhMeh%fLaU}`)9{|-#OQgInJuSv@r+>-DRE(7iX48Ng)z|~-+u$<{~j$g$0DOeGP zFuQb|fvzew+k^kQ*_+JXI)=%yCj*uY{Lw6khdhv@h(9V=TO$)V78uj&+KNd7gW8+$ zkPVzt5BD?R4sh=XOhY)y_3a9@y3--!Md)(4()=SgGebIck2doXZa%ohzzsDn*#&a~ zn9$wkwm~xDMdqJm}#awC{YAm0=BVqw*oh2qh`uE0WaOq zN=UzmAh!eG%?BolWAD@YUeVVI%m*sfot~F}HKi&;98x4G0h9*n1iB6628{tR&`LJ& zMR{m%^#Luq*61^-T?#x0n7UDcMRKo#C-nJz*cX5nve={6&fQ;wm40YMT+(l}iIY~b zUqF7S3pFMDG9C=sNe?ssa;@daR4s&B7r+WO+lK-e6b!P^PYXY zaGjvL4tX$F8h{Y=6K?xp$ACJ3EqGvReOY6(?iGG$3QKh?u+!w%CmvZHq!ACHDzK+p z+S*f6?i=SUE~@a+R<^eMUJ(d%2O=S<7m>UMOm!si55Oe50)Grla()v(MhupfNr!lQ5i9QjQl)&ZMb--Rv(+71*u^R z{4sDgFr~<|3G;0oiySNkzQNS>4L3GvDpsR#*iY$x1sR|bz{j#y?Kc=9Ek0`fPwKsS zb^D)Xh*e~LIqWMyD+66?qpapPniREzR+4lX=}=3EM2<ORCpRluozau^)+2*U=WwP6}LL z7cey@fyE(LfyE(Lfol)B_6vl0266N-#IX#R(h_0T0n?a3;Lm`m!3s>>SnXli*hIl^ z20s1={E?=uJr|o~-am}3uaajK;-K{o=&@t6+vg+I;`Eg{3yMY;6?mM(#yKDHc*_v* z?E-CWG#U0#1MUkusVw16vPZp_z@@O$`pesF#Vd0EaKR?+$D?6k^Kf<9+bnaQd?!2o zmMyt=0p?aEsn2Lku8AeRiBJdsTp%VF4_QX7#np|3_jn-d* zJA_#hnADnn^vk(GPQ9^lZ1)F-Ywb)-nr>980-C|T7BTflUiipVTUP115SQ=8jL?Rg zX?nf|?hf1#B=F6!Q=cpFP4)0#6t&U+>%dfFV@q4N7Ga(NuZ1CwV%0BGOTQ+B%x^oT=m&)Ns)SbYh?u?k~ zPGCdbId>xtN=MY4z&u^>t6zN1I^ZUI=a<8!0>Cdo>DA}=SQ|7*6uc-rS|9KQeg(J* z3;)tO$abkJoDVr-S;?2yd`H$R;e6#S6}^j1a&2ITzqEQRbT7O57rSkrbzFyY^TPjc z3#_ID7WEuSgjURH4P^!&zx>L&G`ZTqUkiI*ZDZpuZ_xm^*H+G9+d)G? zg`kn3518qM^|tQokOTUm#H9708glm(^rM04Zxy{-w3-gj~~{55j&3^c{%o zhe7L65|==2;a(0}3OWw@9&Vyaet`W57ifo(eH83j-_RB8%~||dxrELpg|X$|Ta~0O zuWPODZe%GF-khK+@aUKIM)kwrSifn&T=8nF*q=C0%lmQIe+HcZon)iFv<`Khg7q}$ z4CpNA9OyhKZ;6&b12nj71IE?A{EW>n;Qb1^K+z0g)+1I&xb(Irp_8EsI>1e0&5u~q zT1^6*RPFOHx!~&FsMn`K(r+M=^WQ<*mdq~rXbN8qE9*G{Dn(O)1+i|8uo8i-QRo-cjb|MB@ugW14i z)*7|)=DU0WF`)j0TMV7_U43H$#Ls5y>`Ld7{FvuLl7Oc zjQ~Z0sLyK%5`ON2dn$-luDl*!rMJ}S!^tLAe9W4~4*zJqrH9lAPV^(=9~MJO*eZjn zM?9K{l!Mz8=pQ$Mc2$VM*aYgGzR?cfAcdyLyr|(+_VEepziu#=I?U!=k7D3~U!A zM`eQd(HF84Ep1)NNzzu6$mW-_1ql?Au4-4R-I0S>`Ir~%~V zQV-MBv7_y4y%Bt8uswpru?|aYoy`}2toALj;oEOZs^5Iu_LkYa_YAvpxvh<9 zQuV~;wwFw}uP|<c`gDj+jgnSl(J&q50I4Z2MYUiTS`p*5Q3yFGTw2 z`?mJxLr+z|`@U_SDPqFp9S}tHGWg*}Qe6F2oxjf3z>G``-(btYEuOPB*yfs#Ph&Y7 zZL5%pA2-^1m=Dch=^xt0m~qeif)8y+O*Pd|*VtT?==M#Jxi##uk8BC%eT>cd$o8c9 z!0hVAn^Cl;#caeDTekVo-0G?=wosG#%qwi^zimU1h;=LcpP9#+ZMCJL3i@reWtnzV z`?uN>O_mc2kj0;?7jCod-5n^_o>Z*WFU(e^n&{^IWu~Wa-qn%9_Su49B66V|ARNgl4{ek`Q zrZUTX>Ij>;SXpFxp0$5VDKuX>#^$}He5CI9Ne{jMCpL14(p8>)9E|Vss#h*iUQF|M zoEync%eBPwQ4cS0E7&7JZ8EYzZKP&UHKG?zgFPM898_55Eum?&zUwHRLVToV(m{8X zj>H=orG3iBw#lMDZNQU3B-KRLs>oqdSXHDH1!{rFM2l<*dn-_D5at0Do|}uy+&;cb zTE}08aPNyl5yL%R%?D<7;ET92*(aYXjS{oK68UcfyU|NFJO8;d$WAOVtjl2ozfj`a z7=5*cv;IVP<1{W_fBbuf+Ob!@P`dM&MEdR7&tE9{1B@|sKumor$~v~sYQv*2y3xU0 zquV{&3kG%aO9y?luBLciffv$#a*r$wQ1HM+`*xIj@H1_b+B5PyQ;yzFRAZglIc?ju?=aXq?q+XU39q{XI(sA{=*kX$tu(S5S>2fF z8>MxYKJ)8EOChEFbY^!$I7(PXUrm}b2*P8LXmUP?4f;~);9NC7l25OGg(n)7R$xJe z_R?5$PUIFim5RAmLq^J}Jw=1O8z$CU>v=!2p z1?=vxl$IR>S-sO;I-I|^#-j{~5TZmBMJ1IbQg7IaPjoYTXTLH%bl}2Beu6g-EZk_G z@s09OPBQHOVAPNpn)O_tkAK@lLl zNg;Jy6p516e!%o*uXGD|R1TW3)0#_oKpl|JQXC1uL1(-t{PCxf`iTjz6c@Ph0-XnL z^u9xB8J-%y-u(vm9ef{_`1%ia4es0D*01;Nz3%{1V&}hAaHDWF(isbXF`yKXz%&*_ zA3lC3VsFY{h{Ga94WTdYXHVQOC!zp-!+jWamXu3wETE05Ec4OZY%Gfs|rBUEfxzp%VU{X}H+BWtLb`{|R{vcCz{A~b^*8H_U zq>RSUe%T?VNvB3691qT)aF=D z$J=(p@sc_f#7#>yl< zfxs2{#^pLsEoc3HP%_L>E7-rtylDkn{)3WY_N`!_!%UgIB9gBpEQCpIp?5J(ZcPY+%y!`LZr!mT1EhnvHhmI)Cltu8P!q1A8EcB?-qJ;>v7kpAeA}lr= zJ>H^xr+7{)u2wpV=SzQwJ%3brSh@Ed&Hp3sFv~IIVHYsP`xVSkn0H}2sIaIMC$mKi zDCc)rzhg?AIcgPq_!#2KTE*rZQ^h6z|!;Q+oP>#L(-#lnssn0z&4-P^s-LR9W8hHc2yUSPD(a0M{ zJgrXrK2JgKvO+#|f!0ML`EYc=q>3znosO~@1AD+L1c{@g!(kVK-2!2Zmck z1+z7XdIGX%g1U5P7tbgknBABZomKAd6S5iw4RU~DL2;l2P$H-?s0oPtGy|o9(m~BZ z*`Riy_Mi@{BiY<@xX57aIb}qz|0PCbC2MVTE8C%#b zrb}u51KUgOwj-tfh(R2Ddh~J3M&8$2Lv!6E zZAbp-*M)f6Hq_igT&MHYc_qv~08F$BTx*1b&zj@4^Gd9LH~9C1{{o0c2?Ec7oq896 z=fX~1p}?5*WAs#uY4IV-fJ5uyVZhW6itrBrV|o@DVD#4*z--AcN=E1iWE3n;MJ9Il zuS#S8x=0>JBbac-OCx-e-f`F=_L2vR`2y_p%z8TBA~#;@S|5mW2#<8MTjNK;Aq<{< zmF2}Aq_1%yjNsG6S4+Is;U@4Y6Sr2}O;3>C9QFsm<2r6V`}BfRPA|f?q%oz}Q1}(F z`#_Z-FLV8-G>SDkj|2M=(4*XW6r9;>C(*CcOPxtZkA8S?<>z}UDzs^4`3Cl{i%NU* z+6~ofE-K~*exv8{@Jveg37GiLX#y-I2#(7zq$gpglBUv{2>WgDt3|72o*s(ao`Rb* zy{tkzLhNi_zCmjvH*M0Yygy8Mp>E2}q^0`GL*s(<|tRXM;`AJp-cFDsX>1AI+`=9so@931c5s}Kr#xFfFXKj0_y}HDI3DRKQh;A0t z64V)V8|WcWIp}fFv!MB)rJ!oiF3|U&bD*oB@DI`5g33UXK(B({2CW8d1bqrR2D%Cg zsXtQv!Gd^d7w8zt3Vq-dqIamr$ARhmaWKW z6qP9`6Vwyb1&Q1N^L~&A^zc@;!>lHmr5)^qS-rn~(;bo0V3hZrAgY@=s8gzNqPehF zfoLD!$e(Azn!1`Dz6)WgYgq}CgfWD*4pKX}cop91M@BM8+^vA?Ldx>_?Fxk@bkMKM zPKFy9yTuIO8ObMWkL%_OFexlsJ{zAgTDp^2g4Ntx&w@+C_xir_+`c?#MR_q4K2!@1 zK&9g)a`;Qwr8TJi2-gT$#EST)1gmj&;{(lS?*yxn_60~)=!1o9559ozpN&{(ATkg{ zroe@;Q?nG<4LeOZ1xC*=k;)Nx0qjl#UJHAw0n^}w`U2tqEbIthJ;b6e2<{-bG!-+( zdLJ-VfYiWZvpXUi#U&;;O>3Fard`L*l55@@EGI;L-#i<0<`8w6efHi+p5zMnk6_DV z)fiTT%bCr)_p&{9HG@o3sM^nbWeFP?s>Yh79u8B(&29Iw$HUZ{ZNk$yR_;(6vy=Ev z2D#bs#thyDOnOtq7Yb~Q zpLQ{Un*==27N^mJ8o<@389|PV+m`nAnFxASo#;7fAQHL=JgP)D8heM5Jk+Et-uHexE8Wonn>vinu?P=Y+ zbuC}ibJSrrG*<0thWT2oTG+9}_t32flK_*(9CHyF#pg_KolC=;8a;!?k@6TzFtv@K9p9GoP~5DfQYIC9*3f+ zZ@vSjP}!?se;2fx)K^0mAFoE+^M1e_6yZoebJv;@I#@@z!zt6fQs2%)^;pBwVc9_&Z81wr9BJbKY zxhwXG@Tyn^-n#R6`6NKw$If-;r5EGoOQ(}xGTA&&!RIo8!Ov@@XiTz8@=**25VsR1 zrI8JjI*M*bBk$z-&dYWFM^Ab1OnHZwK%q_q4+6_GD|(!D$n1L12G%c8jSn|I$40Op zB*|$QgUJWF&aV-kq^<}Q1B%f36aO(77v9)@+1640h3=GGFp)*$FR)Cs**VRN3AM^q7ir|JjSuR z8><~!tc4dU!5uJDV0vi-0}E6Idgpxwd>V7^_>omNR$C|uKWSrw?4Q`l#%gZx0N^(4 zOk*{H-Rx8&%~OA3L!9c(5jEhGF8vZFt>e7#J$u)wW-BqrwYHUgoSg->4hE(*9@ZvV z9n112$yR9R9iOvh$!aHg(ihOu`#UpN6EvN&=VbNHNk40iq9=-m>dA;H`vTt$_bdZ$ zXVFxSUT@+mN> zgy60=QIjG>JnO-y8Unv_6E($g3YbKs0V+QcCTr6iI`HWxYFJ{-No^!RN~b+YV46B7 zRIh5Hz8M^G;G8yA9{?VW3lE%QBb%$a(QVIb1=|}Fw zSoOdb>cb8FXQ%DPp7ea&9M3J=CD-)n#JP;230cfNs&E*D+XifwTpPKq=>^I43E3Kq zpEX-@9pqN$`i+w7G`FUYJcFaM-1>+4CCN2)2K)x~sP6TF z)5MvQYaKZRkH6sj@jc14lY{wf-WqxYFCuXe^x?oNgLkI}g! z)kBhN%1lDXCkHjzHbrvH;b_>>f1ez?3}4$JB#(cjQFXJg_Din!Ih?Wdp3>~GPd_8M zKIVA!JxcfJ5pPMZuQ^fz=Dd(L9s5tN+0R!yTOne9c diff --git a/apps/gipy/pkg/gps_bg.wasm.d.ts b/apps/gipy/pkg/gps_bg.wasm.d.ts index b4303ee30..3b95ada78 100644 --- a/apps/gipy/pkg/gps_bg.wasm.d.ts +++ b/apps/gipy/pkg/gps_bg.wasm.d.ts @@ -4,6 +4,7 @@ export const memory: WebAssembly.Memory; export function __wbg_gps_free(a: number): void; export function get_gps_map_svg(a: number, b: number): void; export function get_polygon(a: number, b: number): void; +export function has_heights(a: number): number; export function get_polyline(a: number, b: number): void; export function get_gps_content(a: number, b: number): void; export function request_map(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): number; @@ -12,8 +13,8 @@ export function gps_from_area(a: number, b: number, c: number, d: number): numbe 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__heb2f4d39a212d7d1(a: number, b: number, c: number): void; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb15c13006e54cdd7(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__h362f82c7669db137(a: number, b: number, c: number, d: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h4d77bafb1e69a027(a: number, b: number, c: number, d: number): void; diff --git a/apps/gipy/pkg/package.json b/apps/gipy/pkg/package.json index 0f6ae6a75..57fbc3352 100644 --- a/apps/gipy/pkg/package.json +++ b/apps/gipy/pkg/package.json @@ -4,6 +4,7 @@ "files": [ "gps_bg.wasm", "gps.js", + "gps_bg.js", "gps.d.ts" ], "module": "gps.js", From b9dc5a11ced52b20c1caa9b81a18632933701028 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 22 Aug 2023 14:50:37 +0200 Subject: [PATCH 21/54] gipy: wasm fix --- apps/gipy/ChangeLog | 1 + apps/gipy/pkg/gps.d.ts | 44 +++ apps/gipy/pkg/gps.js | 745 ++++++++++++++++++++++++++++++++++++- apps/gipy/pkg/gps_bg.wasm | Bin 743877 -> 743453 bytes apps/gipy/pkg/package.json | 1 - 5 files changed, 788 insertions(+), 3 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 3ca699a4f..ac67eafda 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -101,3 +101,4 @@ 0.21: * Jit is back for display functions (10% speed increase) + * Store, parse and display elevation data diff --git a/apps/gipy/pkg/gps.d.ts b/apps/gipy/pkg/gps.d.ts index 3f1c8f372..e4644f74f 100644 --- a/apps/gipy/pkg/gps.d.ts +++ b/apps/gipy/pkg/gps.d.ts @@ -56,3 +56,47 @@ export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: nu export class Gps { free(): void; } + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_gps_free: (a: number) => void; + readonly get_gps_map_svg: (a: number, b: number) => void; + readonly get_polygon: (a: number, b: number) => void; + readonly has_heights: (a: number) => number; + readonly get_polyline: (a: number, b: number) => void; + readonly get_gps_content: (a: number, b: number) => void; + readonly request_map: (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) => number; + readonly load_gps_from_string: (a: number, b: number) => number; + readonly gps_from_area: (a: number, b: number, c: number, d: 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__hb15c13006e54cdd7: (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__h4d77bafb1e69a027: (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} module_or_path +* +* @returns {Promise} +*/ +export default function init (module_or_path?: InitInput | Promise): Promise; diff --git a/apps/gipy/pkg/gps.js b/apps/gipy/pkg/gps.js index 5c9bfc9bd..563bf6251 100644 --- a/apps/gipy/pkg/gps.js +++ b/apps/gipy/pkg/gps.js @@ -1,2 +1,743 @@ -import * as wasm from "./gps_bg.wasm"; -export * from "./gps_bg.js"; \ No newline at end of file + +let wasm; + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +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; +} + +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; +} + +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__hb15c13006e54cdd7(arg0, arg1, addHeapObject(arg2)); +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } + return instance.ptr; +} +/** +* @param {Gps} gps +* @returns {string} +*/ +export function get_gps_map_svg(gps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(gps, Gps); + wasm.get_gps_map_svg(retptr, gps.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(r0, r1); + } +} + +let cachedFloat64Memory0 = new Float64Array(); + +function getFloat64Memory0() { + if (cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; +} + +function getArrayF64FromWasm0(ptr, len) { + return getFloat64Memory0().subarray(ptr / 8, ptr / 8 + len); +} +/** +* @param {Gps} gps +* @returns {Float64Array} +*/ +export function get_polygon(gps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(gps, Gps); + wasm.get_polygon(retptr, gps.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v0 = getArrayF64FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 8); + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Gps} gps +* @returns {boolean} +*/ +export function has_heights(gps) { + _assertClass(gps, Gps); + const ret = wasm.has_heights(gps.ptr); + return ret !== 0; +} + +/** +* @param {Gps} gps +* @returns {Float64Array} +*/ +export function get_polyline(gps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(gps, Gps); + wasm.get_polyline(retptr, gps.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v0 = getArrayF64FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 8); + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +* @param {Gps} gps +* @returns {Uint8Array} +*/ +export function get_gps_content(gps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(gps, Gps); + wasm.get_gps_content(retptr, gps.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 {Gps} gps +* @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} +*/ +export function request_map(gps, key1, value1, key2, value2, key3, value3, key4, value4) { + _assertClass(gps, Gps); + const ptr0 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ptr2 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len2 = WASM_VECTOR_LEN; + const ptr3 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len3 = WASM_VECTOR_LEN; + const ptr4 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len4 = WASM_VECTOR_LEN; + const ptr5 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len5 = WASM_VECTOR_LEN; + const ptr6 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len6 = WASM_VECTOR_LEN; + const ptr7 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len7 = WASM_VECTOR_LEN; + const ret = wasm.request_map(gps.ptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7); + return takeObject(ret); +} + +/** +* @param {string} input +* @returns {Gps} +*/ +export function load_gps_from_string(input) { + const ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.load_gps_from_string(ptr0, len0); + return Gps.__wrap(ret); +} + +/** +* @param {number} xmin +* @param {number} ymin +* @param {number} xmax +* @param {number} ymax +* @returns {Gps} +*/ +export function gps_from_area(xmin, ymin, xmax, ymax) { + const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax); + return Gps.__wrap(ret); +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} +function __wbg_adapter_85(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h4d77bafb1e69a027(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +} + +/** +*/ +export class Gps { + + static __wrap(ptr) { + const obj = Object.create(Gps.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_gps_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_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.__wbg_log_d04343b58be82b0f = function(arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbg_fetch_57429b87be3dcc33 = function(arg0) { + const ret = fetch(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) { + const ret = getObject(arg0).fetch(getObject(arg1)); + return addHeapObject(ret); + }; + 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_signal_31753ac644b25fbb = function(arg0) { + const ret = getObject(arg0).signal; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () { + const ret = new AbortController(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) { + getObject(arg0).abort(); + }; + 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.__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_new_abda76e883ba8a5f = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + const ret = getObject(arg1).stack; + 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_error_f851667af71bcfc6 = function(arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(arg0, arg1); + } + }; + 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.__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_85(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_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.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)); + return addHeapObject(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_wrapper2214 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 268, __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; + cachedFloat64Memory0 = new Float64Array(); + 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('gps_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; diff --git a/apps/gipy/pkg/gps_bg.wasm b/apps/gipy/pkg/gps_bg.wasm index bbc9ce6a7449547ed257a72d61207453d2938894..7a42fb564e0b20e7bbc6645e7ef0725ff06f8bec 100644 GIT binary patch delta 906 zcmZ8f%Zk)M6z%DBC*2W5#TTRLhvN&RC6B63Wh3rnKonPko1&iSXeN>FG>vN)et=6I z5Ix9Zf!1BTc?qo+iJyno-A8_B%$i>`b)gXxsX} zZCTy1Zu#ATCeOOwt$Nl~?Q&5O_5x2K3gbiy03ZmMoEo1;-65*A=Nc9m#e81A)Hgrh zXk-hl?HN;^y{9KWv6u3Mfqm*yoUIi@6r4EkI-ix7~&9RKF*{#{Ut8Od{;#6%<)i0jYAPY8AiYuI_#M}o%F`dGu2GJmLQNZfq+kA3aBL9 z%<=kLNBym%KKMTk#3JeYFbw0602mW9?`Pj@DraPYQ3Mki1b_)2gc1@n<*sXP_SD^b zQpa_vu^BGtGBvMRKZ7%kZZ(^iD_uU@C?AM}a{fx^`g-lYS-U$5Z3RoAqtI2b6&!`0 OLSMoCymn9hc=QJjUK!~C delta 1363 zcmZ`(J#W)c6m{doPTCHXPpLpcp$k+gv7h~HZ|q1wCRotJ@53goYge{IKp=HMf{7_3 zs>F_jDlsqu0}BH23m8~f`3c}89^QQ_!&%Nd@A%$x@AKhN|I7RS(T&Ez*EM%#`+B@z z5E^gq6lVyT&{4``k&>d!M`=uAQEHm3S)XZQl4LRA0|z;Xx;_=YO$X62!rCmOJA##j z=h;5VMWpEq$7}!nY$_(&lKk1}=SC09#CfUK+MXp@D(3!%tehHpZxs+%^({!;(=S2d znSK!xhpNXXRq;#pXf^vF-8#8(o)FKmeV6md_8gB<#9?yNyG^B>r_Bvm&YNcUf!tE< zUByO1%0mwe-$yj`LpRcvW#NUz_=+-Qy9B$w?FEr=9M`c}gq1RkR_~5{_oNg#qw{HM zAnDVRU{sLBSfFHjzjft6Hl5EgCaHK_64wia>m!bki*e{mq9+_|9Rxc)c6Z9O5W%7=M2fyZmo@t)Yr@+a_J nbft5aJRf#m4m(Q~8Wr>k%?hmwMg_A%yTVMf{9)Kx`F86cBxtWp diff --git a/apps/gipy/pkg/package.json b/apps/gipy/pkg/package.json index 57fbc3352..0f6ae6a75 100644 --- a/apps/gipy/pkg/package.json +++ b/apps/gipy/pkg/package.json @@ -4,7 +4,6 @@ "files": [ "gps_bg.wasm", "gps.js", - "gps_bg.js", "gps.d.ts" ], "module": "gps.js", From e6f30b9dc0df975b188f93792786b1840d889e10 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 22 Aug 2023 16:52:28 +0200 Subject: [PATCH 22/54] gipy: removed 'lost' message --- apps/gipy/ChangeLog | 1 + apps/gipy/README.md | 5 ++--- apps/gipy/TODO | 2 -- apps/gipy/app.js | 17 ++++++++++------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index ac67eafda..a5384c0c8 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -102,3 +102,4 @@ 0.21: * Jit is back for display functions (10% speed increase) * Store, parse and display elevation data + * Removed 'lost' indicator (we now change position to purple when lost) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index ac65f8c3f..dc260fd70 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -79,17 +79,16 @@ On your screen you can see: * 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 ### Lost If you stray away from path we will rescale the display to continue displaying nearby segments and -display the direction to follow as a purple segment. +display the direction to follow as a purple segment. Your main position will also turn to purple. Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you are. On path it just needed to scan a few points ahead and behind. -The distance to next point displayed corresponds to the length of the black segment. +The distance to next point displayed corresponds to the length of the purple segment. ### Menu diff --git a/apps/gipy/TODO b/apps/gipy/TODO index ef1df3dc5..b642b5cf6 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -45,8 +45,6 @@ JIT: array declaration in jit is buggy + when lost we still get powersaving + try disabling gps for more powersaving -+ remove "lost" indicator and change position point's color instead - + when you walk the direction still has a tendency to shift + put back foot only ways diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 70c1aafd3..2aec35429 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -1126,7 +1126,11 @@ class Status { previous_y = y; } } - g.setColor(0, 0, 0); + if (this.on_path) { + g.setColor(0, 0, 0); + } else { + g.setColor(1, 0, 1); + } g.fillCircle(current_x, current_y, 5); // display min dist/max dist and min height/max height @@ -1267,11 +1271,6 @@ class Status { .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_path() { // don't display all segments, only those neighbouring current segment @@ -1329,7 +1328,11 @@ class Status { } // now display ourselves - g.setColor(0, 0, 0); + if (this.on_path) { + g.setColor(0, 0, 0); + } else { + g.setColor(1, 0, 1); + } g.fillCircle(half_width, half_height, 5); } } From 7baa0d21739caeb79a29fec4a31e3ce8069ecbdf Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 22 Aug 2023 17:05:41 +0200 Subject: [PATCH 23/54] gipy: small fixes --- apps/gipy/ChangeLog | 2 ++ apps/gipy/TODO | 2 -- apps/gipy/app.js | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index a5384c0c8..73164dbd3 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -103,3 +103,5 @@ * Jit is back for display functions (10% speed increase) * Store, parse and display elevation data * Removed 'lost' indicator (we now change position to purple when lost) + * Powersaving fix : don't powersave when lost + * Bugfix for negative remaining distance when going backwards diff --git a/apps/gipy/TODO b/apps/gipy/TODO index b642b5cf6..8c767c463 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -41,8 +41,6 @@ JIT: array declaration in jit is buggy ************************** -+ there is still a bug with negative remaining distances (when lost and nearest point is endpoint ?) -+ when lost we still get powersaving + try disabling gps for more powersaving + when you walk the direction still has a tendency to shift diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 2aec35429..0ccdff36b 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -847,7 +847,6 @@ class Status { // now check if we strayed away from path or back to it let lost = this.is_lost(next_segment); if (this.on_path == lost) { - this.activate(); // if status changes if (lost) { Bangle.buzz(); // we lost path @@ -857,6 +856,9 @@ class Status { } this.on_path = !lost; } + if (!this.on_path) { + this.activate(); + } this.current_segment = next_segment; @@ -959,14 +961,12 @@ class Status { ]); } remaining_distance() { - let remaining_in_correct_orientation = - this.remaining_distances[this.current_segment + 1] + - this.position.distance(this.path.point(this.current_segment + 1)); - if (go_backwards) { - return this.remaining_distances[0] - remaining_in_correct_orientation; + return this.remaining_distances[0] - this.remaining_distances[this.current_segment] + + this.position.distance(this.path.point(this.current_segment)); } else { - return remaining_in_correct_orientation; + return this.remaining_distances[this.current_segment + 1] + + this.position.distance(this.path.point(this.current_segment + 1)); } } // check if we are lost (too far from segment we think we are on) From 822ca78504924d9f1c9ba418ca5c9691c9b914a0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 22 Aug 2023 16:00:50 -0500 Subject: [PATCH 24/54] Fix field name for movementPerDay() --- apps/health/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/health/app.js b/apps/health/app.js index fdc69dd28..eea0a3cce 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -103,8 +103,8 @@ function movementPerDay() { var data = new Uint16Array(31); var cnt = new Uint8Array(31); require("health").readDailySummaries(new Date(), h=>{ - data[h.hr]+=h.movement - cnt[h.hr]++; + data[h.day]+=h.movement + cnt[h.day]++; }); data.forEach((d,i)=>data[i] = d/cnt[i]); setButton(menuMovement); From e7ad8097323f4720c57439317be88eecb803d3a7 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 22 Aug 2023 16:38:06 -0500 Subject: [PATCH 25/54] Add missing semicolons (fix warnings in IDE) --- apps/health/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/health/app.js b/apps/health/app.js index eea0a3cce..3b615ff1d 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -89,7 +89,7 @@ function movementPerHour() { var data = new Uint16Array(24); var cnt = new Uint8Array(24); require("health").readDay(new Date(), h=>{ - data[h.hr]+=h.movement + data[h.hr]+=h.movement; cnt[h.hr]++; }); data.forEach((d,i)=>data[i] = d/cnt[i]); @@ -103,7 +103,7 @@ function movementPerDay() { var data = new Uint16Array(31); var cnt = new Uint8Array(31); require("health").readDailySummaries(new Date(), h=>{ - data[h.day]+=h.movement + data[h.day]+=h.movement; cnt[h.day]++; }); data.forEach((d,i)=>data[i] = d/cnt[i]); From fde449c8c0ef863c15b2565d6360c0f6d939cdd9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 22 Aug 2023 16:39:35 -0500 Subject: [PATCH 26/54] Bump version number --- apps/health/ChangeLog | 1 + apps/health/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 6bc15be83..489715931 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -27,3 +27,4 @@ movement graph in app is now an average, not sum fix 11pm slot for daily HRM 0.26: Implement API for activity fetching +0.27: Fix typo in daily summary graph code causing graph not to load diff --git a/apps/health/metadata.json b/apps/health/metadata.json index 10c8268cb..10a146bdd 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -2,7 +2,7 @@ "id": "health", "name": "Health Tracking", "shortName": "Health", - "version": "0.26", + "version": "0.27", "description": "Logs health data and provides an app to view it", "icon": "app.png", "tags": "tool,system,health", From 463bba49968102907baf231bd7a084bf688605d5 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 23 Aug 2023 08:00:05 +0200 Subject: [PATCH 27/54] gipy: min height --- apps/gipy/app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 0ccdff36b..674771293 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -1043,6 +1043,12 @@ class Status { max_height = Math.max(max_height, height); min_height = Math.min(min_height, height); } + // we'll set the displayed height to a minimum value of 100m + // if we don't, then we'll see too much noise + if (max_height - min_height < 100) { + min_height = min_height - 10; + max_height = min_height + 110; + } let displayed_height = max_height - min_height; let height_per_pixel = displayed_height / graph_height; From b8e29f397dd7ced3ab9b544f8c5e4cab9e5d2db1 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 23 Aug 2023 08:29:21 +0200 Subject: [PATCH 28/54] gipy: doc change --- apps/gipy/README.md | 4 ++++ apps/gipy/app.js | 2 +- apps/gipy/heights.png | Bin 0 -> 3627 bytes 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 apps/gipy/heights.png diff --git a/apps/gipy/README.md b/apps/gipy/README.md index dc260fd70..0df008b38 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -101,6 +101,10 @@ If you touch the screen you will switch between display modes. The first one displays the map, the second one the nearby elevation and the last one the elevation for the whole path. +![Screenshot](heights.png) + +Colors correspond to slopes. + ### Settings Few settings for now (feel free to suggest me more) : diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 674771293..4fa51f779 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -1082,7 +1082,7 @@ class Status { if (steepness > 0.15) { color = "#ff0000"; } else if (steepness > 0.8) { - color = "#aa0000"; + color = "#ff8000"; } else if (steepness > 0.03) { color = "#ffff00"; } else if (steepness > -0.03) { diff --git a/apps/gipy/heights.png b/apps/gipy/heights.png new file mode 100644 index 0000000000000000000000000000000000000000..07f82511b7d9f731f2cd059123feeb9af62ecc17 GIT binary patch literal 3627 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 Ezcg}uFaQ7m literal 0 HcmV?d00001 From cf0695c3eb5a39fc7ecd5fb77ab909486e1f760d Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Thu, 24 Aug 2023 10:01:50 +0200 Subject: [PATCH 29/54] gipy : fix for backwards position in heights --- apps/gipy/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 4fa51f779..56137ae58 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -996,8 +996,12 @@ class Status { } else { let current_position = 0; if (this.current_segment !== null) { + if (go_backwards) { + current_position = this.remaining_distance(); + } else { current_position = this.remaining_distances[0] - this.remaining_distance(); + } } if (this.screen == HEIGHTS_FULL) { this.display_heights(0, current_position, this.remaining_distances[0]); From 52ee0825c5a7d3d04aa7d3e1c164888d9df095e4 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Thu, 24 Aug 2023 10:56:59 +0200 Subject: [PATCH 30/54] sokoban: small fixes --- apps/sokoban/ChangeLog | 4 ++++ apps/sokoban/app.js | 17 ++++++++++++----- apps/sokoban/metadata.json | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/sokoban/ChangeLog b/apps/sokoban/ChangeLog index 9fa2c8172..f931ec63e 100644 --- a/apps/sokoban/ChangeLog +++ b/apps/sokoban/ChangeLog @@ -1 +1,5 @@ 0.01: Initial code +0.02: + * Fix for last level offsets parsing + * Fix for title display + diff --git a/apps/sokoban/app.js b/apps/sokoban/app.js index 890156214..3915556e3 100644 --- a/apps/sokoban/app.js +++ b/apps/sokoban/app.js @@ -56,6 +56,9 @@ function next_map_offsets(filename, start_offset) { } } } + if (offsets.length == 1) { + offsets.push(raw_maps.length); + } return offsets; } @@ -88,7 +91,7 @@ function load_current_map() { let current_set = config.levels_set; let offsets = config.offsets[current_set]; let set_filename = config.levels_sets[current_set]; - let set_name = set_filename.substring(0, set_filename.length - 4); // remove '.txt' + let set_name = set_filename.substring(8, set_filename.length - 4); // remove '.txt' and 'sokoban.' let current_map = config.current_maps[current_set]; map = load_map(set_filename, offsets[2 * current_map], offsets[2 * current_map + 1], set_name + " " + (current_map + 1)); map.display(); @@ -98,10 +101,12 @@ function next_map() { let current_set = config.levels_set; let current_map = config.current_maps[current_set]; let offsets = config.offsets[current_set]; + let won = false; if (2 * (current_map + 1) >= offsets.length) { // we parse some new offsets let new_offsets = next_map_offsets(config.levels_sets[current_set], offsets[offsets.length - 1] + 2); // +2 since we need to start at ';' (we did -2 from ';' in previous parser call) - if (new_offsets.length == 0) { + if (new_offsets.length != 2) { + won = true; E.showAlert("You Win", "All levels completed").then(function() { load(); }); @@ -110,9 +115,11 @@ function next_map() { config.offsets[current_set].push(new_offsets[1]); } } - config.current_maps[current_set]++; - s.writeJSON("sokoban.json", config); - load_current_map(); + if (!won) { + config.current_maps[current_set]++; + s.writeJSON("sokoban.json", config); + load_current_map(); + } } function previous_map() { diff --git a/apps/sokoban/metadata.json b/apps/sokoban/metadata.json index ef4a45f83..752c17e75 100644 --- a/apps/sokoban/metadata.json +++ b/apps/sokoban/metadata.json @@ -2,7 +2,7 @@ "id": "sokoban", "name": "Sokoban", "shortName": "Sokoban", - "version": "0.01", + "version": "0.02", "description": "Classic Sokoban game (microban levels).", "allow_emulator":false, "icon": "sokoban.png", From f95e1508872efb78effa55324fc3ea1c2b3ee6f5 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:35:03 -0400 Subject: [PATCH 31/54] Create app.js Added the code for the app. --- apps/bblobface/app.js | 768 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 apps/bblobface/app.js diff --git a/apps/bblobface/app.js b/apps/bblobface/app.js new file mode 100644 index 000000000..579a6bbb4 --- /dev/null +++ b/apps/bblobface/app.js @@ -0,0 +1,768 @@ +{ + // ~~ Variables for clock ~~ + let clockDrawTimeout; + let twelveHourTime = require('Storage').readJSON('setting.json', 1)['12hour']; + let updateSeconds = !Bangle.isLocked(); + let batteryLevel = E.getBattery(); + + // ~~ Variables for game logic ~~ + const NUM_COLORS = 6; + const NUISANCE_COLOR = 7; + let grid = [ + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]) + ]; + let hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]); + let nextQueue = [{pivot: 1, leaf: 1}, {pivot: 1, leaf: 1}]; + let currentPair = {pivot: 0, leaf: 0}; + let dropCoordinates = {pivotX: 2, pivotY: 11, leafX: 2, leafY: 10}; + let pairX = 2; + let pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left + let slotsToCheck = []; + let selectedColors; + let lastChain = 0; + let gameLost = false; + let gamePaused = false; + let midChain = false; + + /* + Sets up a new game. + Must be called once before the first round. + */ + let restartGame = function() { + grid = [ + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]) + ]; + hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]); + currentPair = {pivot: 0, leaf: 0}; + pairX = 2; + pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left + slotsToCheck = []; + gameLost = false; + lastChain = 0; + + //Set up random colors + selectedColors = new Uint8Array([1, 2, 3, 4, 5, 6]); + for (let i = NUM_COLORS - 1; i > 0; i--) { + let swap = selectedColors[i]; + let swapIndex = Math.floor(Math.random() * (i + 1)); + selectedColors[i] = selectedColors[swapIndex]; + selectedColors[swapIndex] = swap; + } + + //Create the first two pairs (Always in the first three colors) + nextQueue[0].pivot = selectedColors[Math.floor(Math.random() * 3)]; + nextQueue[0].leaf = selectedColors[Math.floor(Math.random() * 3)]; + nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 3)]; + nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 3)]; + }; + + /* + Readies the next pair and generates a new one for the queue. + */ + let newPair = function() { + currentPair.pivot = nextQueue[0].pivot; + currentPair.leaf = nextQueue[0].leaf; + + nextQueue[0].pivot = nextQueue[1].pivot; + nextQueue[0].leaf = nextQueue[1].leaf; + + nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 4)]; + nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 4)]; + + pairX = 2; + pairOrientation = 0; + + calcDropCoordinates(); + }; + + /* + Calculates the coordinates at which the current pair will be placed when quick dropped. + */ + let calcDropCoordinates = function() { + dropCoordinates.pivotX = pairX; + + //Find Y coordinate of pivot + dropCoordinates.pivotY = -2; + for (let i = 11; i >= 0; i--) { + if (grid[i][pairX] == 0) { + dropCoordinates.pivotY = i; + break; + } + } + if (dropCoordinates.pivotY == -2 && hiddenRow[pairX] == 0) + dropCoordinates.pivotY = -1; + + //Find coordinates of leaf + if (pairOrientation == 1) { + dropCoordinates.leafX = pairX + 1; + + dropCoordinates.leafY = -2; + for (let i = 11; i >= 0; i--) { + if (grid[i][pairX + 1] == 0) { + dropCoordinates.leafY = i; + break; + } + } + if (dropCoordinates.leafY == -2 && hiddenRow[pairX + 1] == 0) + dropCoordinates.leafY = -1; + } else if (pairOrientation == 3) { + dropCoordinates.leafX = pairX - 1; + + dropCoordinates.leafY = -2; + for (let i = 11; i >= 0; i--) { + if (grid[i][pairX - 1] == 0) { + dropCoordinates.leafY = i; + break; + } + } + if (dropCoordinates.leafY == -2 && hiddenRow[pairX - 1] == 0) + dropCoordinates.leafY = -1; + } else if (pairOrientation == 2) { + dropCoordinates.leafX = pairX; + dropCoordinates.leafY = dropCoordinates.pivotY; + dropCoordinates.pivotY--; + } else { + dropCoordinates.leafX = pairX; + dropCoordinates.leafY = dropCoordinates.pivotY - 1; + } + }; + + /* + Moves the current pair a certain number of slots. + */ + let movePair = function(dx) { + pairX += dx; + + if (dx < 0) { + if (pairX < (pairOrientation == 3 ? 1 : 0)) + pairX = (pairOrientation == 3 ? 1 : 0); + } + if (dx > 0) { + if (pairX > (pairOrientation == 1 ? 4 : 5)) + pairX = (pairOrientation == 1 ? 4 : 5); + } + + calcDropCoordinates(); + }; + + /* + Rotates the pair in the given direction around the pivot. + */ + let rotatePair = function(clockwise) { + pairOrientation += (clockwise ? 1 : -1); + if (pairOrientation > 3) + pairOrientation = 0; + if (pairOrientation < 0) + pairOrientation = 3; + + if (pairOrientation == 1 && pairX == 5) + pairX = 4; + if (pairOrientation == 3 && pairX == 0) + pairX = 1; + + calcDropCoordinates(); + }; + + /* + Places the current pair at the drop coordinates. + */ + let quickDrop = function() { + if (dropCoordinates.pivotY == -1) { + hiddenRow[dropCoordinates.pivotX] = currentPair.pivot; + } else if (dropCoordinates.pivotY > -1) { + grid[dropCoordinates.pivotY][dropCoordinates.pivotX] = currentPair.pivot; + } + + if (dropCoordinates.leafY == -1) { + hiddenRow[dropCoordinates.leafX] = currentPair.leaf; + } else if (dropCoordinates.leafY > -1) { + grid[dropCoordinates.leafY][dropCoordinates.leafX] = currentPair.leaf; + } + + currentPair.pivot = 0; + currentPair.leaf = 0; + }; + + /* + Makes all blobs fall to the lowest available slot. + All blobs that fall will be added to slotsToCheck. + */ + let settleBlobs = function() { + for (let x = 0; x < 6; x++) { + let lowestOpen = 11; + for (let y = 11; y >= 0; y--) { + if (grid[y][x] != 0) { + if (y != lowestOpen) { + grid[lowestOpen][x] = grid[y][x]; + grid[y][x] = 0; + addSlotToCheck(x, lowestOpen); + } + lowestOpen--; + } + } + + if (lowestOpen >= 0 && hiddenRow[x] != 0) { + grid[lowestOpen][x] = hiddenRow[x]; + hiddenRow[x] = 0; + addSlotToCheck(x, lowestOpen); + } + } + }; + + /* + Adds a slot to slotsToCheck. This slot will be checked for a pop + next time popAll is called. + */ + let addSlotToCheck = function(x, y) { + slotsToCheck.push({x: x, y: y}); + }; + + /* + Checks for a pop at every slot in slotsToCheck. + Pops at all locations. + */ + let popAll = function() { + let result = {pops: 0}; + while(slotsToCheck.length > 0) { + let coord = slotsToCheck.pop(); + if (grid[coord.y][coord.x] != 0 && grid[coord.y][coord.x] != NUISANCE_COLOR) { + if (checkSlotForPop(coord.x, coord.y)) + result.pops += 1; + } + } + return result; + }; + + /* + Checks a specific slot for a pop. + If there are four or more adjacent blobs of the same color, they are removed. + */ + let checkSlotForPop = function(x, y) { + let toDelete = [ + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]), + new Uint8Array([0, 0, 0, 0, 0, 0]) + ]; + let blobsInClump = 0; + let color = grid[y][x]; + let toCheck = [{x: x, y: y}]; + + //Count every blob in this clump + while (toCheck.length > 0) { + let coord = toCheck.pop(); + if (grid[coord.y][coord.x] == color && toDelete[coord.y][coord.x] == 0) { + blobsInClump++; + toDelete[coord.y][coord.x] = 1; + if (coord.x > 0) toCheck.push({x: coord.x - 1, y: coord.y}); + if (coord.x < 5) toCheck.push({x: coord.x + 1, y: coord.y}); + if (coord.y > 0) toCheck.push({x: coord.x, y: coord.y - 1}); + if (coord.y < 11) toCheck.push({x: coord.x, y: coord.y + 1}); + } + if (grid[coord.y][coord.x] == NUISANCE_COLOR && toDelete[coord.y][coord.x] == 0) + toDelete[coord.y][coord.x] = 1; //For erasing garbage + } + + //If there are at least four blobs in this clump, remove them from the grid and draw a pop. + if (blobsInClump >= 4) { + for (let y = 0; y < 12; y++) { + for (let x = 0; x < 6; x++) { + if (toDelete[y][x] == 1) { + grid[y][x] = 0; + + //Clear the blob out of the slot + g.setBgColor(0, 0, 0); + g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); + + //Draw the pop + let colorInfo = getColor(color); + g.setColor(colorInfo.r, colorInfo.g, colorInfo.b); + if (color < NUISANCE_COLOR) { + //A fancy pop for popped colors! + g.drawEllipse((x*18)+36, (y*14)+7, (x*18)+50, (y*14)+21); + g.drawEllipse((x*18)+27, (y*14)-2, (x*18)+59, (y*14)+30); + } else if (color == NUISANCE_COLOR) { + //Nuisance Blobs are simply crossed out. + //TODO: Nuisance Blobs are currently unusued, but also untested. Test before use. + g.drawLine((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); + } + } + } + } + return true; + } + return false; + }; + + // Variables for graphics + let oldGhost = {pivotX: 0, pivotY: 0, leafX: 0, leafY: 0}; + + /* + Draws the time on the side. + */ + let drawTime = function(scheduleNext) { + //Change this to alter the y-coordinate of the top edge. + let dy = 25; + + g.setBgColor(0, 0, 0); + g.clearRect(2, dy, 30, dy + 121); + + //Draw the time + let d = new Date(); + let h = d.getHours(), m = d.getMinutes(); + if (twelveHourTime) { + let mer = 'A'; + if (h >= 12) mer = 'P'; + if (h >= 13) h -= 12; + if (h == 0) h = 12; + + g.setColor(1, 1, 1); + g.setFont("Vector", 12); + g.drawString(mer, 23, dy + 63); + } + let hs = h.toString().padStart(2, 0); + let ms = m.toString().padStart(2, 0); + g.setFont("Vector", 24); + g.setColor(1, 0.2, 1); + g.drawString(hs, 3, dy + 21); + g.setColor(0.5, 0.5, 1); + g.drawString(ms, 3, dy + 42); + + //Draw seconds + let s = d.getSeconds(); + if (updateSeconds) { + let ss = s.toString().padStart(2, 0); + g.setFont("Vector", 12); + g.setColor(0.2, 1, 0.2); + g.drawString(ss, 3, dy + 63); + } + + //Draw the date + let dayString = d.getDate().toString(); + let dayNames = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; + let dayName = dayNames[d.getDay()]; + let monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JLY", "AUG", "SEP", "OCT", "NOV", "DEC"]; + let monthName = monthNames[d.getMonth()]; + g.setColor(1, 1, 1); + g.setFont("Vector", 12); + g.drawString(monthName, 3, dy + 84); + g.drawString(dayString, 3, dy + 97); + g.setColor(0.5, 0.5, 0.5); + g.drawString(dayName, 3, dy + 110); + + //Draw battery + if (s == 0) batteryLevel = E.getBattery(); + if (Bangle.isCharging()) { + g.setColor(0, 0, 1); + } else if (batteryLevel <= 15) { + g.setColor(1, 0, 0); + } else { + g.setColor(0, 1, 0); + } + g.drawString(batteryLevel + "%", 3, dy + 1); + + //Schedule the next draw if requested. + if (!scheduleNext) return; + if (clockDrawTimeout) clearTimeout(clockDrawTimeout); + let interval = updateSeconds ? 1000 : 60000; + clockDrawTimeout = setTimeout(function() { + clockDrawTimeout = undefined; + drawTime(true); + }, interval - (Date.now() % interval)); + }; + + /* + Returns a tuple in the format {r, g, b} with the color + of the blob with the given ID. + This saves memory compared to having the colors stored in an array. + */ + let getColor = function(color) { + if (color == 1) + return {r: 1, g: 0, b: 0}; + if (color == 2) + return {r: 0, g: 1, b: 0}; + if (color == 3) + return {r: 0, g: 0, b: 1}; + if (color == 4) + return {r: 1, g: 1, b: 0}; + if (color == 5) + return {r: 1, g: 0, b: 1}; + if (color == 6) + return {r: 0, g: 1, b: 1}; + if (color == 7) + return {r: 0.5, g: 0.5, b: 0.5}; + return {r: 1, g: 1, b: 1}; + }; + + /* + Clears the screen and draws the background. + */ + let drawBackground = function() { + //Background + g.setBgColor(0.5, 0.2, 0.1); + g.clear(); + g.setBgColor(0, 0, 0); + g.clearRect(33, 0, 142, 176); + g.setBgColor(0.5, 0.5, 0.5); + g.clearRect(33, 4, 142, 6); + + //Reset button + g.setBgColor(0.5, 0.5, 0.5); + g.setColor(0, 0, 0); + g.clearRect(143, 150, 175, 175); + g.setFont("Vector", 30); + g.drawString("R", 152, 150); + + //Pause button + g.clearRect(0, 150, 32, 175); + g.fillRect(9, 154, 13, 171); + g.fillRect(18, 154, 22, 171); + }; + + /* + Draws a box under the next queue that displays + the current value of lastChain. + */ + let drawChainCount = function() { + g.setBgColor(0, 0, 0); + g.setColor(1, 0.2, 0.2); + g.setFont("Vector", 23); + g.clearRect(145, 42, 173, 64); + + if (lastChain > 0) { + if (lastChain < 10) g.drawString(lastChain, 154, 44); + if (lastChain >= 10) g.drawString(lastChain, 147, 44); + } + }; + + /* + Draws the blob at the given slot. + */ + let drawBlobAtSlot = function(x, y) { + //If this blob is in the hidden row, clear it out and stop. + if (y < 0) { + g.setBgColor(0, 0, 0); + g.clearRect((x*18)+34, 0, (x*18)+52, 3); + return; + } + + //First, clear what was in that slot. + g.setBgColor(0, 0, 0); + g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); + + let color = grid[y][x]; + + if (color != 0) { + let myColor = getColor(color); + g.setColor(myColor.r, myColor.g, myColor.b); + g.fillEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); + g.setColor(1, 1, 1); + g.drawEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); + } + }; + + /* + Draws the ghost piece. + clearOld: if the previous location of the ghost piece should be cleared. + */ + let drawGhostPiece = function(clearOld) { + if (clearOld) { + g.setColor(0, 0, 0); + g.fillRect((oldGhost.pivotX*18)+38, (oldGhost.pivotY*14)+8, (oldGhost.pivotX*18)+47, (oldGhost.pivotY*14)+17); + g.fillRect((oldGhost.leafX*18)+38, (oldGhost.leafY*14)+8, (oldGhost.leafX*18)+47, (oldGhost.leafY*14)+17); + } + + let pivotX = dropCoordinates.pivotX; + let pivotY = dropCoordinates.pivotY; + let leafX = dropCoordinates.leafX; + let leafY = dropCoordinates.leafY; + let pivotColor = getColor(currentPair.pivot); + let leafColor = getColor(currentPair.leaf); + + g.setColor(pivotColor.r, pivotColor.g, pivotColor.b); + g.fillRect((pivotX*18)+40, (pivotY*14)+10, (pivotX*18)+45, (pivotY*14)+15); + g.setColor(1, 1, 1); + g.drawRect((pivotX*18)+38, (pivotY*14)+8, (pivotX*18)+47, (pivotY*14)+17); + g.setColor(leafColor.r, leafColor.g, leafColor.b); + g.fillRect((leafX*18)+40, (leafY*14)+10, (leafX*18)+45, (leafY*14)+15); + + oldGhost = {pivotX: pivotX, pivotY: pivotY, leafX: leafX, leafY: leafY}; + }; + + /* + Draws the next queue. + */ + let drawNextQueue = function() { + g.setBgColor(0, 0, 0); + g.clearRect(145, 4, 173, 28); + + let p1 = nextQueue[0].pivot; + let l1 = nextQueue[0].leaf; + let p2 = nextQueue[1].pivot; + let l2 = nextQueue[1].leaf; + let p1C = getColor(p1); + let l1C = getColor(l1); + let p2C = getColor(p2); + let l2C = getColor(l2); + + g.setColor(p1C.r, p1C.g, p1C.b); + g.fillEllipse(146, 17, 157, 28); + g.setColor(l1C.r, l1C.g, l1C.b); + g.fillEllipse(146, 5, 157, 16); + g.setColor(p2C.r, p2C.g, p2C.b); + g.fillEllipse(162, 17, 173, 28); + g.setColor(l2C.r, l2C.g, l2C.b); + g.fillEllipse(162, 5, 173, 16); + + g.setColor(1, 1, 1); + g.drawLine(159, 4, 159, 28); + g.drawEllipse(146, 17, 157, 28); + g.drawEllipse(146, 5, 157, 16); + g.drawEllipse(162, 17, 173, 28); + g.drawEllipse(162, 5, 173, 16); + }; + + /* + Redraws the screen, except for the ghost piece. + */ + let redrawBoard = function() { + drawBackground(); + drawNextQueue(); + drawChainCount(); + drawTime(false); + for (let y = 0; y < 12; y++) { + for (let x = 0; x < 6; x++) { + drawBlobAtSlot(x, y); + } + } + }; + + /* + Toggles the pause screen. + */ + let togglePause = function() { + gamePaused = !gamePaused; + + if (gamePaused) { + g.setBgColor(0.5, 0.2, 0.1); + g.clear(); + drawTime(false); + + g.setBgColor(0, 0, 0); + g.setColor(1, 1, 1); + g.clearRect(48, 66, 157, 110); + g.setFont("Vector", 20); + g.drawString("Tap here\nto unpause", 50, 68); + + require("widget_utils").show(); + Bangle.drawWidgets(); + } else { + require("widget_utils").hide(); + + redrawBoard(); + drawGhostPiece(false); + + //Display the loss text if the game is lost. + if (gameLost) { + g.setBgColor(0, 0, 0); + g.setColor(1, 1, 1); + g.clearRect(33, 73, 142, 103); + g.setFont("Vector", 20); + g.drawString("You Lose", 43, 80); + } + } + }; + + // ~~ Events ~~ + let dragAmnt = 0; + + let onTouch = (z, e) => { + if (midChain) return; + + if (gamePaused) { + if (e.x >= 40 && e.y >= 58 && e.x <= 165 && e.y <= 118) { + g.setBgColor(1, 1, 1); + g.clearRect(48, 66, 157, 110); + g.flip(); + togglePause(); + } + } else { + //Tap reset button + if (e.x >= 143 && e.y >= 150) { + restartGame(); + newPair(); + redrawBoard(); + drawGhostPiece(false); + g.flip(); + return; + } + + //Tap pause button + if (e.x <= 32 && e.y >= 150) { + togglePause(); + return; + } + + //While playing, rotate pieces. + if (!gameLost && !gamePaused) { + if (e.x < 88) { + rotatePair(false); + drawGhostPiece(true); + } else { + rotatePair(true); + drawGhostPiece(true); + } + } + } + }; + + Bangle.on("touch", onTouch); + + let onDrag = (e) => { + if (gameLost || gamePaused || midChain) return; + + //Do nothing if the user is dragging down so that they don't accidentally move while dropping + if (e.dy >= 5) { + return; + } + + dragAmnt += e.dx; + if (e.b == 0) { + dragAmnt = 0; + } + if (dragAmnt >= 20) { + movePair(Math.floor(dragAmnt / 20)); + drawGhostPiece(true); + dragAmnt = dragAmnt % 20; + } + if (dragAmnt <= -20) { + movePair(Math.ceil(dragAmnt / 20)); + drawGhostPiece(true); + dragAmnt = dragAmnt % 20; + } + }; + + Bangle.on("drag", onDrag); + + let onSwipe = (x, y) => { + if (gameLost || gamePaused || midChain) return; + + if (y > 0) { + let pivotX = dropCoordinates.pivotX; + let pivotY = dropCoordinates.pivotY; + let leafX = dropCoordinates.leafX; + let leafY = dropCoordinates.leafY; + + if (pivotY < -1 && leafY < -1) return; + + quickDrop(); + drawBlobAtSlot(pivotX, pivotY); + drawBlobAtSlot(leafX, leafY); + g.flip(); + + //Check for pops + if (pivotY >= 0) addSlotToCheck(pivotX, pivotY); + if (leafY >= 0) addSlotToCheck(leafX, leafY); + midChain = true; + let currentChain = 0; + while (popAll().pops > 0) { + currentChain++; + lastChain = currentChain; + drawChainCount(); + g.flip(); + settleBlobs(); + redrawBoard(); + g.flip(); + } + + newPair(); + drawNextQueue(); + drawGhostPiece(false); + + //If the top slot of the third column is taken, lose the game. + if (grid[0][2] != 0) { + gameLost = true; + g.setBgColor(0, 0, 0); + g.setColor(1, 1, 1); + g.clearRect(33, 73, 142, 103); + g.setFont("Vector", 20); + g.drawString("You Lose", 43, 80); + } + + midChain = false; + } + }; + + Bangle.on("swipe", onSwipe); + + let onLock = on => { + updateSeconds = !on; + drawTime(true); + }; + + Bangle.on('lock', onLock); + + let onCharging = charging => { + drawTime(false); + }; + + Bangle.on('charging', onCharging); + + Bangle.setUI({mode:"clock", remove:function() { + //Remove listeners + Bangle.removeListener("touch", onTouch); + Bangle.removeListener("drag", onDrag); + Bangle.removeListener("swipe", onSwipe); + Bangle.removeListener('lock', onLock); + Bangle.removeListener('charging', onCharging); + + if (clockDrawTimeout) clearTimeout(clockDrawTimeout); + require("widget_utils").show(); + }}); + + g.reset(); + + Bangle.loadWidgets(); + require("widget_utils").hide(); + + drawBackground(); + drawTime(true); + + restartGame(); + + newPair(); + drawGhostPiece(false); + + drawNextQueue(); + drawChainCount(); +} From a109cfd391e109d84a1d6f07d0486fd551fa7ecc Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:35:34 -0400 Subject: [PATCH 32/54] Create ChangeLog Added the changelog. --- apps/bblobface/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/bblobface/ChangeLog diff --git a/apps/bblobface/ChangeLog b/apps/bblobface/ChangeLog new file mode 100644 index 000000000..6a29fdb72 --- /dev/null +++ b/apps/bblobface/ChangeLog @@ -0,0 +1 @@ +1.00: Initial release of Bangle Blobs Clock! From fd367c06dcf7d17ba1352c232b0f7ff266369e70 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:35:56 -0400 Subject: [PATCH 33/54] Create app-icon.js --- apps/bblobface/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/bblobface/app-icon.js diff --git a/apps/bblobface/app-icon.js b/apps/bblobface/app-icon.js new file mode 100644 index 000000000..e8d9baced --- /dev/null +++ b/apps/bblobface/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+HGm56+5BQ4JBAJItXAAoMMCJQAPJ5pfhJApPQL65HHKIbTU2nXAAu0I5xQNBo4tC2gAFGIxHIL5oNGEoItGGIgwDL6oMGFxgwFL6oVFFxwwEL7YuPGARfVBYwvUL6YLGL84THL84KHL7YHCL6AeBFx+0JggAGLx4wQFwa3DAIwvHNJQwMFwhgIEQ7ILGAYxHBAQWJADUeFAIAEjwtnjwAFGMglBFowxEGA/XgrgICJouMGA4aBAIgvMB4ouOGAouGMZgNGFx4wCPQ5hMN44vTK44wLNo5fUcRwuHL67iOHAxfhFxYJBBooeBFx8ecRY4KBowwOFxDgHM5BtHGBguZfhIkBGI4ICFyILFAIxBHAAoOGXIgLHBowBGFo0FAAoxHFxhfPAoQAJCIguNGxRtGABYpDQB72LFxwwEcCJfJFx4wCL7gvTADYv/F/4APYoQuOaoYwpFz4wOF0IwDGI4ICF0IxFAAgtFA=")) From cfbc148a86da07530c3b320fa11cc105bbd8879d Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:37:42 -0400 Subject: [PATCH 34/54] Create metadata.json --- apps/bblobface/metadata.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/bblobface/metadata.json diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json new file mode 100644 index 000000000..0d50a2424 --- /dev/null +++ b/apps/bblobface/metadata.json @@ -0,0 +1,14 @@ +{ "id": "bblobface", + "name": "Bangle Blobs Clock", + "shortName":"BBClock", + "icon": "app.png", + "version": "1.00", + "description": "A fully featured watch face with a playable game on the side.", + "type": "clock", + "tags": "", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"bblobface.app.js","url":"app.js"}, + {"name":"bblobface.img","url":"app-icon.js","evaluate":true} + ] +} From 8fd2a87342c4143b2e3be930bdfdf81402e40237 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:38:27 -0400 Subject: [PATCH 35/54] Upload Image Uploaded the png for the icon. --- apps/bblobface/app.png | Bin 0 -> 691 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/bblobface/app.png diff --git a/apps/bblobface/app.png b/apps/bblobface/app.png new file mode 100644 index 0000000000000000000000000000000000000000..2201fa621ee4950521e77d8914e279dffb9d614f GIT binary patch literal 691 zcmV;k0!;mhP)C*2ppRI|6k@Fx>{5a)Y{%9H!mi;NC!rNmH;sRFh1Ik z3_immfIwXb%nT5+y|;S)Mv+`!#7#r}!M!~rer9oGBFu@Ax-zp)*049%n~@ceaC2~_;g%pjT|k?un0Z*WH@(nF_no1dZCEPDG2;=RNO8)AB z-N3#~o~TbAxNNvQ)K>srQmI-4U~smtk6l521XU2vDNz43Lq0(Su~h-neuaGYdj_#h z0ohmXE}atl6qr?P5L*<;eFJ;54?Lm3W`q=w84XvVp56BXF5z1U)LEzpcf1aE?4?pF z$hQitPLn}<0_Kzu}$hUqkp5IPv4Q|#N!yTFL`2{%5LMz9>hB=MiFkU3Tssi3R z`jDNoP*X;qK)M_2eg=lQ--`bLW^m)D(YF+!r68I<@DApJsGIF8Rj9<;Ec6QkNSK>O Ze*m1G3ni=?{K^0T002ovPDHLkV1h2JIyC?Q literal 0 HcmV?d00001 From d9718f4e57337111ba3ac430ca4d2997063df69a Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:45:07 -0400 Subject: [PATCH 36/54] Add tags to metadata.json --- apps/bblobface/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json index 0d50a2424..ad822e15c 100644 --- a/apps/bblobface/metadata.json +++ b/apps/bblobface/metadata.json @@ -5,7 +5,7 @@ "version": "1.00", "description": "A fully featured watch face with a playable game on the side.", "type": "clock", - "tags": "", + "tags": "clock", "game", "supports" : ["BANGLEJS2"], "storage": [ {"name":"bblobface.app.js","url":"app.js"}, From baf720c5bcfc54494db7421284969504099532b1 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:47:00 -0400 Subject: [PATCH 37/54] Fix metadata.json Attempt to fix the metadata.json --- apps/bblobface/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json index ad822e15c..c9e4acca6 100644 --- a/apps/bblobface/metadata.json +++ b/apps/bblobface/metadata.json @@ -5,7 +5,7 @@ "version": "1.00", "description": "A fully featured watch face with a playable game on the side.", "type": "clock", - "tags": "clock", "game", + "tags": ["clock", "game"], "supports" : ["BANGLEJS2"], "storage": [ {"name":"bblobface.app.js","url":"app.js"}, From 4b925b7fea4c33179745c68250db268f2576c5e1 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:50:22 -0400 Subject: [PATCH 38/54] Actually fix metadata.json After trying multiple things, I think this will fix the tags. --- apps/bblobface/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json index c9e4acca6..51b5b81f5 100644 --- a/apps/bblobface/metadata.json +++ b/apps/bblobface/metadata.json @@ -5,7 +5,7 @@ "version": "1.00", "description": "A fully featured watch face with a playable game on the side.", "type": "clock", - "tags": ["clock", "game"], + "tags": "clock, game", "supports" : ["BANGLEJS2"], "storage": [ {"name":"bblobface.app.js","url":"app.js"}, From e881ab3f52042855bc885451d89e9dc29baa7aff Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 13:05:54 -0400 Subject: [PATCH 39/54] Screenshots Uploaded a few screenshots for the README. --- apps/bblobface/screenshot1.png | Bin 0 -> 3797 bytes apps/bblobface/screenshot2.png | Bin 0 -> 4808 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/bblobface/screenshot1.png create mode 100644 apps/bblobface/screenshot2.png diff --git a/apps/bblobface/screenshot1.png b/apps/bblobface/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..91650c07a62a25d0c8ff75aeba9a2dbe0d72af18 GIT binary patch literal 3797 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 literal 0 HcmV?d00001 diff --git a/apps/bblobface/screenshot2.png b/apps/bblobface/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..64644965f7cfab5d4c278b85863c7ae8ba943434 GIT binary patch literal 4808 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 Date: Sun, 27 Aug 2023 13:57:51 -0400 Subject: [PATCH 40/54] Create README.md --- apps/bblobface/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 apps/bblobface/README.md diff --git a/apps/bblobface/README.md b/apps/bblobface/README.md new file mode 100644 index 000000000..4c32221f6 --- /dev/null +++ b/apps/bblobface/README.md @@ -0,0 +1,34 @@ +# Bangle Blobs Clock +What if every time you checked the time, you could play a turn of a turn-based puzzle game? +You check the time dozens, maybe hundreds of times per day, and Bangle Blobs Clock wants to add a splash of fun to each of these moments! +Bangle Blobs Clock is a fully featured watch face with a turn-based puzzle game right next to the clock. + +![](screenshot1.png) +![](screenshot2.png) + +## Clock Features +- Hour and minute +- Seconds (only while the screen is unlocked to save power) +- Month, day, and day of week +- Battery percentage. Blue while charging, red when low, green otherwise. +- Respects your 24-hour/12-hour time setting in Locale +- Press the pause button to access your Widgets +- Supports Fast Loading + +## The Game +This is a turn-based puzzle game based on Puyo Puyo, an addictive puzzle game franchise by SEGA. +Blobs arrive in pairs that you can move, rotate, and place. When at least four Blobs of the same color touch, they pop, causing Blobs above them to fall. +If this causes another pop, it's called a chain! Build a massive chain reaction of popping Blobs! +- Drag left and right to move the pair +- Tap the left or right half of the screen to rotate the pair +- Swipe down to place the pair + +## More Info +If you're confused about the functionality of the clock or want a better explanation of how to play the game, I wrote up a user manual here: https://docs.google.com/document/d/1watPzChawBu4iM0lXypreejs3wvf2_8C-x5V2MWJQBc/edit?usp=sharing + +## Credits +- I'm Pasta Rhythm, computer scientist and aspiring game developer. +- ![nxdefiant, who made a Tetris game.](https://github.com/espruino/BangleApps/tree/master/apps/tetris) Bangle Blobs is my first Bangle app and my first time using JavaScript, so this was a daunting project. This Tetris game served as a great example that helped me get started. +- ![gfwilliams for Anton Clock](https://github.com/espruino/BangleApps/tree/master/apps/antonclk) and ![Targor for Kanagawa Clock.](https://github.com/espruino/BangleApps/tree/master/apps/kanagsec) These were good examples for how to make a watch face for the Bangle.js 2. +- Thanks to Gordon Williams and to everyone who contributes to Espruino and the Bangle.js 2 projects! +- SEGA, owners of the Puyo Puyo franchise that Bangle Blobs is based on. Please check out official Puyo Puyo games! From 639b26b7b3f3b9f65d23498f36e606a03fa77cfd Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:01:48 -0400 Subject: [PATCH 41/54] Edited README Fixed some formatting issues with the README --- apps/bblobface/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/bblobface/README.md b/apps/bblobface/README.md index 4c32221f6..7eab8221d 100644 --- a/apps/bblobface/README.md +++ b/apps/bblobface/README.md @@ -28,7 +28,7 @@ If you're confused about the functionality of the clock or want a better explana ## Credits - I'm Pasta Rhythm, computer scientist and aspiring game developer. -- ![nxdefiant, who made a Tetris game.](https://github.com/espruino/BangleApps/tree/master/apps/tetris) Bangle Blobs is my first Bangle app and my first time using JavaScript, so this was a daunting project. This Tetris game served as a great example that helped me get started. -- ![gfwilliams for Anton Clock](https://github.com/espruino/BangleApps/tree/master/apps/antonclk) and ![Targor for Kanagawa Clock.](https://github.com/espruino/BangleApps/tree/master/apps/kanagsec) These were good examples for how to make a watch face for the Bangle.js 2. +- [nxdefiant, who made a Tetris game.](https://github.com/espruino/BangleApps/tree/master/apps/tetris) Bangle Blobs is my first Bangle app and my first time using JavaScript, so this was a daunting project. This Tetris game served as a great example that helped me get started. +- [gfwilliams for Anton Clock](https://github.com/espruino/BangleApps/tree/master/apps/antonclk) and [Targor for Kanagawa Clock.](https://github.com/espruino/BangleApps/tree/master/apps/kanagsec) These were good examples for how to make a watch face for the Bangle.js 2. - Thanks to Gordon Williams and to everyone who contributes to Espruino and the Bangle.js 2 projects! - SEGA, owners of the Puyo Puyo franchise that Bangle Blobs is based on. Please check out official Puyo Puyo games! From a46751a3731c226a05053d5a30c679448760440b Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:04:48 -0400 Subject: [PATCH 42/54] Add README to metadata --- apps/bblobface/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json index 51b5b81f5..6af247c91 100644 --- a/apps/bblobface/metadata.json +++ b/apps/bblobface/metadata.json @@ -4,6 +4,7 @@ "icon": "app.png", "version": "1.00", "description": "A fully featured watch face with a playable game on the side.", + "readme":"README.md", "type": "clock", "tags": "clock, game", "supports" : ["BANGLEJS2"], From aeca14baa08977d8f2fa467d015d7efe3fe29fb1 Mon Sep 17 00:00:00 2001 From: PastaRhythm <83084413+PastaRhythm@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:34:27 -0400 Subject: [PATCH 43/54] Update README.md Made a quick change to the credits to avoid confusion and make it more readable. --- apps/bblobface/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/bblobface/README.md b/apps/bblobface/README.md index 7eab8221d..54e07e9f8 100644 --- a/apps/bblobface/README.md +++ b/apps/bblobface/README.md @@ -26,9 +26,10 @@ If this causes another pop, it's called a chain! Build a massive chain reaction ## More Info If you're confused about the functionality of the clock or want a better explanation of how to play the game, I wrote up a user manual here: https://docs.google.com/document/d/1watPzChawBu4iM0lXypreejs3wvf2_8C-x5V2MWJQBc/edit?usp=sharing -## Credits -- I'm Pasta Rhythm, computer scientist and aspiring game developer. +## Special Thanks +I'm Pasta Rhythm, computer scientist and aspiring game developer. I would like to say thank you to the people who inspired me while I was making this app: - [nxdefiant, who made a Tetris game.](https://github.com/espruino/BangleApps/tree/master/apps/tetris) Bangle Blobs is my first Bangle app and my first time using JavaScript, so this was a daunting project. This Tetris game served as a great example that helped me get started. - [gfwilliams for Anton Clock](https://github.com/espruino/BangleApps/tree/master/apps/antonclk) and [Targor for Kanagawa Clock.](https://github.com/espruino/BangleApps/tree/master/apps/kanagsec) These were good examples for how to make a watch face for the Bangle.js 2. - Thanks to Gordon Williams and to everyone who contributes to Espruino and the Bangle.js 2 projects! - SEGA, owners of the Puyo Puyo franchise that Bangle Blobs is based on. Please check out official Puyo Puyo games! +- Compile, the original creators of Puyo Puyo. The company went bankrupt long ago, but the people who worked for them continue to make games. From b75c2751c9882a8c1a21e4d48e1d23a6b0438670 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 29 Aug 2023 17:04:37 +0100 Subject: [PATCH 44/54] Fix daily summaries for 31st of the month - https://github.com/espruino/BangleApps/pull/2986 --- apps/health/ChangeLog | 1 + apps/health/app.js | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 489715931..02b53c56d 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -28,3 +28,4 @@ fix 11pm slot for daily HRM 0.26: Implement API for activity fetching 0.27: Fix typo in daily summary graph code causing graph not to load + Fix daily summaries for 31st of the month diff --git a/apps/health/app.js b/apps/health/app.js index 3b615ff1d..db21d9243 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -48,7 +48,7 @@ function stepsPerHour() { function stepsPerDay() { E.showMessage(/*LANG*/"Loading..."); current_selection = "stepsPerDay"; - var data = new Uint16Array(31); + var data = new Uint16Array(32); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); setButton(menuStepCount); barChart(/*LANG*/"DAY", data); @@ -72,8 +72,8 @@ function hrmPerHour() { function hrmPerDay() { E.showMessage(/*LANG*/"Loading..."); current_selection = "hrmPerDay"; - var data = new Uint16Array(31); - var cnt = new Uint8Array(31); + var data = new Uint16Array(32); + var cnt = new Uint8Array(32); require("health").readDailySummaries(new Date(), h=>{ data[h.day]+=h.bpm; if (h.bpm) cnt[h.day]++; @@ -100,8 +100,8 @@ function movementPerHour() { function movementPerDay() { E.showMessage(/*LANG*/"Loading..."); current_selection = "movementPerDay"; - var data = new Uint16Array(31); - var cnt = new Uint8Array(31); + var data = new Uint16Array(32); + var cnt = new Uint8Array(32); require("health").readDailySummaries(new Date(), h=>{ data[h.day]+=h.movement; cnt[h.day]++; From 6c4d3f41a59d519bc502f6d1a1792c472586398d Mon Sep 17 00:00:00 2001 From: Niko Komin Date: Tue, 29 Aug 2023 11:32:03 -0700 Subject: [PATCH 45/54] added new app --- apps/nightwatch/ChangeLog | 1 + apps/nightwatch/README.md | 20 +++ apps/nightwatch/metadata.json | 16 +++ apps/nightwatch/nightwatch.app.info | 6 + apps/nightwatch/nightwatch.app.js | 175 +++++++++++++++++++++++++ apps/nightwatch/nightwatch.icon.js | 1 + apps/nightwatch/nightwatch.icon.png | Bin 0 -> 959 bytes apps/nightwatch/nightwatch.info.js | 6 + apps/nightwatch/nightwatch.settings.js | 25 ++++ apps/nightwatch/screenshot.png | Bin 0 -> 3608 bytes apps/nightwatch/screenshot2.png | Bin 0 -> 3795 bytes 11 files changed, 250 insertions(+) create mode 100644 apps/nightwatch/ChangeLog create mode 100644 apps/nightwatch/README.md create mode 100644 apps/nightwatch/metadata.json create mode 100644 apps/nightwatch/nightwatch.app.info create mode 100644 apps/nightwatch/nightwatch.app.js create mode 100644 apps/nightwatch/nightwatch.icon.js create mode 100644 apps/nightwatch/nightwatch.icon.png create mode 100644 apps/nightwatch/nightwatch.info.js create mode 100644 apps/nightwatch/nightwatch.settings.js create mode 100644 apps/nightwatch/screenshot.png create mode 100644 apps/nightwatch/screenshot2.png diff --git a/apps/nightwatch/ChangeLog b/apps/nightwatch/ChangeLog new file mode 100644 index 000000000..dc179ee9d --- /dev/null +++ b/apps/nightwatch/ChangeLog @@ -0,0 +1 @@ +1.0: first working version of App diff --git a/apps/nightwatch/README.md b/apps/nightwatch/README.md new file mode 100644 index 000000000..6d1749c5d --- /dev/null +++ b/apps/nightwatch/README.md @@ -0,0 +1,20 @@ +# The Nightwatch + +Snuggle into your sleeping bag, hang the Bangle on the tent wall +and check the screen before you fall asleep: + +![](screenshot.png) +![](screenshot2.png) + + +Reads temperature and pressure sensor. Shows current, maximal and minimal values +since the start of the app. Also show a graph of the last 20 measures. + +Swipe left/right between values. + +Screen is updated periodically, time step is configurable in settings. + + +# Creator + +[Niko Komin](https://www.laikaundfreunde.de/niko-komin/) diff --git a/apps/nightwatch/metadata.json b/apps/nightwatch/metadata.json new file mode 100644 index 000000000..90e05214e --- /dev/null +++ b/apps/nightwatch/metadata.json @@ -0,0 +1,16 @@ +{ + "id":"nightwatch", + "readme":"README.md", + "name":"The Nightwatch", + "shortName":"Nightwatch", + "supports" : ["BANGLEJS2"], + "icon":"nightwatch.icon.png", + "screenshots" : [ { "url":"screenshot.png","url":"screenshot2.png" } ], + "version":"1.0", + "description":"Logs sensor readings (currently T and p), show min/max and graph.", + "tags": "tools,outdoors", + "storage": [ + {"name":"nightwatch.app.js","url":"nightwatch.app.js"}, + {"name":"nightwatch.img","url":"nightwatch.icon.js","evaluate":true} + ] +} diff --git a/apps/nightwatch/nightwatch.app.info b/apps/nightwatch/nightwatch.app.info new file mode 100644 index 000000000..36345ce26 --- /dev/null +++ b/apps/nightwatch/nightwatch.app.info @@ -0,0 +1,6 @@ +require("Storage").write("nightwatch.info",{ + "id":"nightwatch", + "name":"nightwatch", + "src":"nightwatch.app.js", + "icon":"nightwatch.icon.png" +}); \ No newline at end of file diff --git a/apps/nightwatch/nightwatch.app.js b/apps/nightwatch/nightwatch.app.js new file mode 100644 index 000000000..035307106 --- /dev/null +++ b/apps/nightwatch/nightwatch.app.js @@ -0,0 +1,175 @@ +// PTLOGGER +// MEASURES p AND T PERIODICALLY AND UPDATES MIN & MAX VALS +// DISPLAYS EITHER OF BOTH + + +var settings = Object.assign({ + dt: 5, //time interval in minutes +}, require('Storage').readJSON("nightwatch.json", true) || {}); + +let dt = settings.dt; +delete settings; + +var timerID; + +const highColor = '#35b779';//#6dcd59; +const lowColor = '#eb005c';//#3d4a89;//#482878; +const normColor = '#000000'; +const historyAmnt = 24; + + +const TData = { + ondisplay:true, + unit: '\xB0C', + accuracy: 1, + value : 100, t_value:'0:00', + values : new Array(historyAmnt), + maxval : -100, t_max:'0:00', + minval : 100, t_min:'0:00' +}; + +const PData = { + ondisplay:false, + unit: 'mbar', + accuracy: 0, + value : 0, t_value:'0:00', + values : new Array(historyAmnt), + maxval : 0, t_max:'0:00', + minval : 10000, t_min:'0:00' +}; + +function minMaxString(val,accuracy,unit,time){ + return time+' '+val.toFixed(accuracy)+unit; +// return val.toFixed(accuracy)+unit+'('+time+')'; +} + +function updateScreen() { + // what are we showing right now? + let data; + if (TData.ondisplay){data = TData;} + else {data = PData;} + + // make strings + let valueString = data.value.toFixed(data.accuracy)+data.unit; + let minString = minMaxString(data.minval, data.accuracy, data.unit, data.t_min); + let maxString = minMaxString(data.maxval, data.accuracy, data.unit, data.t_max); + + // LETS PAINT + g.clear(); + g.setFontAlign(0, 0); + + // MINUM AND MAXIMUM VALUES AND TIMES + g.setFont("Vector:18"); + g.setColor(normColor); + g.drawString(maxString, g.getWidth() / 2, 11); + g.drawString(minString, g.getWidth() / 2, g.getHeight() - 11); + + g.setColor(normColor); + + // TIME OF LAST MEASURE AND SIZE OF INTERVAL + g.setFontAlign(-1, 0); + g.drawString(data.t_value, 0, g.getHeight()/2 - 25); + g.setFontAlign(1, 0); + g.drawString('dt='+dt+'min', g.getWidth() , g.getHeight()/2 - 25); + + //////////////////////////////////////////////////////////// + // GRAPH OF MEASUREMENT HISTORY + g.setFont("Vector:16"); + const graphHeight=35; + const graphWidth=g.getWidth()-30; + const graphLocX = 15; + const graphLocY = g.getHeight() - 16 - 18 - graphHeight; + + // DRAW SOME KIND OF AXES + g.setColor(0.4,0.4,0.4); + g.drawRect(graphLocX,graphLocY,graphLocX+graphWidth,graphLocY+graphHeight); + g.drawLine(graphLocX,graphLocY+graphHeight/2,graphLocX+graphWidth,graphLocY+graphHeight/2); + g.drawLine(graphLocX+graphWidth/2,graphLocY,graphLocX+graphWidth/2,graphLocY+graphHeight); + g.drawLine(graphLocX+graphWidth/4,graphLocY,graphLocX+graphWidth/4,graphLocY+graphHeight); + g.drawLine(graphLocX+3*graphWidth/4,graphLocY,graphLocX+3*graphWidth/4,graphLocY+graphHeight); + g.setColor(normColor); + + // DRAW LINE + require("graph").drawLine(g, data.values, { + x:graphLocX, + y:graphLocY, + width:graphWidth, + height:graphHeight + }); + + let graphMax=Math.max.apply(Math,data.values); + let graphMin=Math.min.apply(Math,data.values); + g.setFontAlign(0, 0); + g.setColor(highColor); + g.drawString(graphMax.toFixed(data.accuracy), g.getWidth()/2, g.getHeight() - 16 - 18 - graphHeight); + g.setColor(lowColor); + g.drawString(graphMin.toFixed(data.accuracy), g.getWidth()/2, g.getHeight() - 16 - 18); + g.setColor(normColor); + + let historyLength = (historyAmnt*dt >= 60)?('-'+historyAmnt*dt/60+'h'):('-'+historyAmnt*dt+'"'); + + g.drawString(historyLength,25, g.getHeight() - 16 - 18 - graphHeight/2); + + //////////////////////////////////////////////////////////// + // LAST MEASURE + g.setFontAlign(0, 0); + g.setFont('Vector:36'); + g.drawString(valueString, g.getWidth() / 2, g.getHeight() / 2); + + data.ondisplay = true; +} + +function updateMinMax( data, currentValue ){ + data.values.push(currentValue); + data.values.shift(); + data.value=currentValue; + + let now = new Date(); + data.t_value = now.getHours()+':'+String(now.getMinutes()).padStart(2, '0'); + if (currentValue < data.minval){data.t_min=data.t_value;data.minval = currentValue;} + if (currentValue > data.maxval){data.t_max=data.t_value;data.maxval = currentValue;} +} + +function switchDisplay(){ + if (TData.ondisplay) {TData.ondisplay=false;PData.ondisplay=true;updateScreen();} + else {PData.ondisplay=false;TData.ondisplay=true;updateScreen();} +} + +function settingsPage(){ + Bangle.on('swipe',function (){}); + eval(require("Storage").read("nightwatch.settings.js"))(()=>load()); + Bangle.on('swipe',switchDisplay); + console.log(3); +} + +function handlePressureSensorReading(data) { + updateMinMax(TData,data.temperature); + updateMinMax(PData,data.pressure); +} + +function startup(){ + // testing in emulator + // handlePressureSensorReading({ "temperature": 28.64251302083, "pressure": 1004.66520303803, "altitude": 71.72072902749 }); + // updateScreen(); + + // ON STARTUP: + // fill current reading into data, + // before `updateMinMax` uses it + Bangle.getPressure().then(d=>{TData.value=d.temperature; + TData.values.fill(d.temperature); + PData.value=d.pressure; + PData.values.fill(d.pressure); + handlePressureSensorReading(d); + updateScreen();}); + Bangle.on('swipe',switchDisplay); + + //Bangle.on('tap',settingsPage); + + timerID = setInterval( function() { + Bangle.getPressure().then(d=>{handlePressureSensorReading(d);updateScreen();}); + }, dt * 60000); + +} + +startup(); + diff --git a/apps/nightwatch/nightwatch.icon.js b/apps/nightwatch/nightwatch.icon.js new file mode 100644 index 000000000..19b4623f0 --- /dev/null +++ b/apps/nightwatch/nightwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4MA///ospETUQAgc//gFDv4FF/wFP/4FF/5PCgIFChF/AoWA/1/+YFBx/+g4EBAAPAFAIEBEgUDBQYAN/E/AgQvDDoXHDocH4wFDgf8v4RCDooAMj/4AoZcBcM8DOAgFFgJSDAqQAhA==")) diff --git a/apps/nightwatch/nightwatch.icon.png b/apps/nightwatch/nightwatch.icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3a3282a2f655eba40d0441711ed16c1a809216 GIT binary patch literal 959 zcmV;w13>(VP)Vs(=D#7dY15`A0EmtJSi%|rFP2^IAo;3<%`y2-Hk*Aq zG!sUyF!8R5|M?Pe+la$PZjrFZh~N6J71vcs&xB^di2Y>&4!^*<7p##pOHJN4!)R1B)doOs4)O$NzqyfyNz7`VAe=@cZeeUMZzwUi*mW#r1DS9 zWKx|*+;|)TR!F1X!6H@yJ`j1pi0hAtC;pT)ITR=49*M_IygAf2WyC27+l^cXV6JzO z#|~Z(M64q@FD5^9K_>q3KlfiX;aeY)Q4~EpKy_DF*HdQEeDBd-oJrud3bOk3j|pE% zSPdW@D#1Mq$=8kCD)CG`0v2&Qc^0|VpWV~b^F*i>UN~maLf`V@R{tpCiim%!`)1iE z@_Vl}16KJ^^ehOCU~XsU_)I4AT@gkV>VVl=Pm2kIZb2Tj!zf z&Ci%I(SYR=HjsQJG#WFbx7)JR=u7N{MGG=O-j~l737?mBxw}4hYMSGf;Dg1)ze`%} z*}`CZM9?rr%dhL6_hvRz#I=&(Kl6O=uLX(URaVeASl6A9_-WZ7>h)UiYo0PjTsHAm zQWwP0k%)+ap1KKeuO#tEd0UitzQp4!>fe!JMBH+rp#gMwak`@%Se)*m^hW88KJtCn z2F?py@|wv+Q52ehjwp(z4`%vGku)yX-Tji!hWdpzn#pFfQ~f;1Q^v^m-K`ew@6U+^ zCVNGm^NuC*hh!Fz8x2_LE;x?UXxQ;e9H+DH5Z%X!{KkV8d7&^H@RNjXB+V>(Q{tBe z-{Y+pzb4k5{KCbMT>Nq>^+H2Lv}x036t~xOIz6TTwVCBYDmLfxlF({o{C7c7z##u}Y6&|0giXZnp;a+*uaWygt3vWEKRgbtN-me1 h)GWWa+O!$1+y}#On>2C^{7nD=002ovPDHLkV1lA$%cuYV literal 0 HcmV?d00001 diff --git a/apps/nightwatch/nightwatch.info.js b/apps/nightwatch/nightwatch.info.js new file mode 100644 index 000000000..ccbc8909a --- /dev/null +++ b/apps/nightwatch/nightwatch.info.js @@ -0,0 +1,6 @@ +require("Storage").write("nightwatch.info",{ + "id":"nightwatch", + "name":"The Nightwatch", + "src":"nightwatch.app.js", + "icon":"nightwatch.icon.png" +}); diff --git a/apps/nightwatch/nightwatch.settings.js b/apps/nightwatch/nightwatch.settings.js new file mode 100644 index 000000000..c543b7343 --- /dev/null +++ b/apps/nightwatch/nightwatch.settings.js @@ -0,0 +1,25 @@ +(function(back) { + var FILE = "nightwatch.json"; + // Load settings + var settings = Object.assign({ + dt: 30, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "nightwatch" }, + "< Back" : () => back(), + 'log freq (min)': { + value: 0|settings.dt, // 0| converts undefined to 0 + min: 1, max: 60, + onchange: v => { + settings.dt = v; + writeSettings(); + } + }, + }); +}); diff --git a/apps/nightwatch/screenshot.png b/apps/nightwatch/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..194c91b28c327b6ca06d06515b1dd81197411ed9 GIT binary patch literal 3608 zcmds4XH*l+(oRC?B9I6o1YVIY;0q!M3Mw^7l@h5U0wOgCks3-+KoCL`l_H=NsR=z4 zO~|VtNbf;P2pvIs?>C`d6Qq&4x_c!9b`+%44m*3)g zib8H+-ja#C?p#uwCq)yl{XMwYD&NrZ zmri%9rsN}g>A0tTMfT7l-kGd z=uz0M!iz*QZ>ZqY34nN7!FTJ+Pzu^dB}YP*2xWe@DMs#Fz62}7-CJN~wD@t4t4`#~ z%7Px-$5|#h8y4XR>Ni=0<_OXSpyL%M!)1-nH?)DrKB_ADyW z4Iblfg$b#|uShRa^7wUWn|uNg`@3Pv4XjJ75qvqgjlQ>F&m`xWVTJ(kxtt8<#6A@W zE#5%_*uleYC?FQubM=-Cxz)y2mNcUi1O0sklig8K0QB+s>gvC{GzlcxB-(n(GD2uI zM{ON&}#ZT%C98|&LPs&L=3*X(9KtGD$oZg4Lz zwEE}aacHh_91N(-hzne7(7|Nl(>ThTyna~5GP_|KNhIpyNvc^tH$V~bsQKlh)tO}5 zn^61WWS@kpHdk) zBYrxFrXO)%*J#+zIOI5^x{`MIbUdlm5W-xpggz_7iGf#O58Slgahi=uX>qJAoXa_H zzkCNu1Vq#ewJ=f=m#b8^mDx5e-c=Gw zvu4=JDw4&+)yxk78zd=_X0QurFgdq2${NG%q1!GG zF?0WZma7&KGdeJ~R45MVuB%A6Z+~qP_Kr=&12Z=NUU|E6X z=J(8ptu`-Bq06uxEeHpvcoXEU9>O+lF+#$3uqks!)?6mID$Md!IZ~^lhx7bpWmE!??MPI9cp&;?H_o8>k)%>I| zVo@FZ?H8KHW*SO6DEQ;M94GP~prk}+v_{cRAiTKJsbWd$@`rRH5Zs%n_Z z`yBmwzSt5M!NkH5pth~J=`yALK3nnc%AE&}8i3yj-=ZO4_(c2f1;Fb4NKJ+~%y}G} z4rmW$)Uf{-Gz@0He4%Vc6!4S9PNxj8W`s}{C9^z*U{t<+p$(d=cGpN5R?DV4D^Ldy zLu+Fx6FQ&KCM&7!BBJNwZc>4T9M>E@^YRz5MT)y$K4x_e$(fUA|f>zC~??G>dLUaX}4xASD%xrdU8ke zKjMs08OFu2PsB(fN`|4M1TGvO%ph8QB!fSpMsZHh>sdW@_$E`rz7XTENcj6{4@QVR z`Lr9Q{jhAc1$*od^*;VUJ3a}tTV>b;>LYK80+~ufLWra*TJ2$BH4>+G%%2XjP z@Ne;Em7}>8kuCB)rPm7@5%=xO_A%!Og2@1ep&$!`)qhV2JazawbH*#Dp@`pvKDRlB zuc|(}pfkhN9n!+CdA@cTv&PL(y5V9^md3zDEl=tJX(dx^6F4cT)&yRAJhaV#Sw6Kf z64oPi7B%_jdp>*@I?;uGMQY*}t5h%qh_L^b`{aLiY^QEkBqW{fXg77gz~XI|o9?wM z%{o4YGZo^P+!%Rjp_Ll zbD$Lz`vhi(N(01;e=iLve6`O&oyZ=jdAJ}2_RW0vqj$by&l#lLAjdsbm8u|e;<9du zx(0+9#?e8i%4|@qmpI0ldpNnE8P?t~P}4hAG3NqOPUoY8bX42+AJ^HJ%EgRhRo?nm zffyF=hBikD*x12CYv#|P0PF-lho3W_*gDkrLps2!4_X*#(Mao#v?Sx|fKruWAU<5+ z9|{`B)M#$YiSbuxaa*}eu?bcHdW4rT4$-+$R{!CWc&|9V< z`Vw2O_09o}>Nl?spQ5=__$j0=?EFwWg|h#cVcA&$7Qs?bm*% zzcnQBkh@2E9*#S7#RcayJ9e6IJgpCp-P$4j|M}@#BEUgcjbc?4rs8=M6yZ>EZ&NgH z!r*%|d@VgPd*{Ms1FbtMO7$NI$O^f~{|P_0tWb$c!Jj*T*Q7u;8`Y_fR@bHFK2YW7 zf^Q|Bo*b=GC=-o38@tAZIS;9uAD5T}pG-eP!^(=-~rT9M4{xfm}9%gpFHTFa+Q8MzG=!+cXU|bjl5jzOuDeboYw#6tuF3&bLQeJJ)4&i_EWXxjncSh zw_47~mMce1D!utXmuiZIjb74n+acpO_2*DkH&(R%q*U11M58^0z2?IW<;b(_+2WHE zsF@SN(qq-o@CKpORhAGjcp`Sk88f}&-qIR0Pw-5zF<97c%6(E@1)i&X-LA3#ni}`Os%W z`oy7|F&~Pb2{NGYaS`agRPdf??ez{tAhl=UwW%J)*a!X17E0qVRFlg8ZDm+TwJMW zXD|rwg(ibcLGQj_y!#nruBE>^R??LvfXGdlP45BJBFdScgK2h%`~#gbHq~MuYc3+P zA+ggTd@0_Y-ctzv8hHM6&>VDIamX~Sj{3TtyWT|GC#71^A23hUvBbzSnO(YDQbS?4@JgGzqfJZ~zh#22gql>N^S*5U00#fVCGA|=Fxlx6fVgBBs{Aldh=yTp)D%D5Y8Qks(G#+`L$ zY-5RGDlv>DLb6T~%9dq9GQHFDT-Woy@4wHV&mZTU&$+JiUC#IOJ?FYUsprq(cER?+ z007u!ZDnCETq}MJvEPI(CRhQ+}_0R6!1Yu-hQI(6QhP5dki3v?4WrR z$!nilK}q(POn=KPrN!pYyM|cIJod|qztg3>U$^_tw3^;>QCJZ$21Y<4A#D8?twfYA zHk4Zri6or<9Xh;Q;r$9}m%TnbE6}e}rL22Ts;>9wjk<%+oW97>%sl+EqR7`tA>vc1 zGak4Pi`G}0Yn}P3LI-P`sxdc}^5U3Ft|2qjAY{E`L(^z0L=l`!8^pggNeOejKLlvCUtNyInm8_hDM5Sv<~XmDtl0t5W6)Jw?> z-RyWRdXWM~-r7@{3$B1h!QK0U$JbOIiQ*lTD5WF8n$hESM6P?DmRxWOhBJIdq<%(v zji^R!kw&r;JN8LdKuVkk%fHw;U1r3gvyy3S%BV&@?sK}c<9itSd-{m(L!{zK1)kM5 zkMHj%rPM?_%M;(-kbv_hkL1T_GtOVDz$rbK8b#cC#kF(H>eKPH*sV^ibpE|BBQ*!+ ze)Hv*JNVj1U@8lV8@=s5?76=HDVtU~gP-84ItHk-q2A?CR`OBRTF*hI&1NUg&L^0BmfuNlpU=I=ATryAhWwN3Uk*P8 z@DB&JhXyp{b30#|^CWotk|*e2>3H*jt5E#dLD!wB^*40fBxW(({;jSGdFI! zh`STfBUx7K2s#&N8I3We0D4$N!y#x?CaE1&gN^Xz^#>dEW2x7X+Zrad>I4#htbSc+x>3vpa~K4 zU4rQ5lXX(G(hc!!e~^14`Wifm_Hp>h@~ncan$q~9u;B?$lD=0LWHVz6VoDM4r~)@q z9X|e3AuJU>_-xBY!9fvH(c<^!04w}q8j6bm-VI@f*3TW0azTZ(MENNexGtLK$moEq z9Q_PZ(>W6|v}rdj(flCIFIGL+k#Dt9o|Z8LuRlI;tVOHDzhNVu#!!Z6HT-ijM|2+R z(=v9!uRbUUwA{;{a#?tinq&oAb7{@$S+Aok{(l_B!^2DtH1-iF9>CSq%k@LNlnzP}O@J2p{6Mfy?wG|KZr(e(S+j+%f^aTgf=jH~{wWKJYg5hZe)6cKx!ncSB%~g zFKglxBQm@SUAgRWBGGxN|$G+i>XZfPAlMGIoZ+bfv7&TpW`m z;!H5C`93J7u#*{DK8*!Zjf*$BxZtr>TOi;Ml}J^Cp=v-PBbF1B$QblTcaPy-^H6a9*LrkTVZHYyF?6!fS8Nl_#MBIU*0>k9P6%I)# zwrWSELHgmy17SF^6j2i>#HFfB26Jr{(j}WkqJq(rxvsXD@<@+)Z4_WZX(}tZ87a6tjhc+5LDlZ}#?FQ=dgTb#M060zinm zZnJG=%M$e}gAu^0j?r!0LefRbNiH2ZeCP6X9g{Jr%G}lh*wp4i;AKgi&6C`tta< zo~Nhmggxt>UEbhpQ{gVFRq9|_tHr6wsmY|k??3T`^#n!g7eBo?(?7;FN5s8U%1XdL zPYU8Iidz|;^lEVW6S&xif7#6aX%uIwlFWS~=B2{)gY;<99h2U+C?gxoYfRQa;S>E9 zfJl%I;o^EsS%3Z%=v{b(DiI{w=|K9iQ3b}g%(5=~nzC8=tn+|!hYfW4ks$HJEvpro zqTOZ>YnzmISWh>!zwg66;Pey%Seg18XBQRf7oy&_%kwYr7(O$To<}?#k58uA#8yO2QCd$O8iR`WS;c~kUv8nhF0pn$%2l6MKI(kZFObi+771yl?9;6v zulo$ROS2nOb-BuA-hvw&d>fCgn>nXkrm$+U8?n?TJ78J6OH!cQ@%Y2fa6yHxJ+Q3a zCIlS~+-vF)((D)nH8sOpvYM`c)i7~b_Ma&T!StA)afTs`*C^K2+HoMo)fp*!US~C6 ze@@u*iR~iGl*YyvU@MJ1dJz{O-|~JK`IpAaa-y_Su86Wv_E(HP8|hXy2p9)u*-T!U z2ls`oR64L3+;J&`khyYZLW3x`Y2$?hntWbTr@Kf2zv4E6dI&ul@if>|sPtkABU8bk zAwaHonkF9kekwB|o4`=db2&L-uKZc`uLAaK83(J?>yx24MS*s=;`va z4iZ299i><%zx?S$)<{NPrbp~}^~TU!KErm(=%yqqD-A45W79fEga7fSGp9G|TV{bF zB|xdJ2K+AkX9HENX-L727;{E7umN8b6gWY$lwQZBAxFq>1C3{+JtH`Y%>u&q*7sxg z8R_RO6S2K1^u`j!SCqp??+^UYv9US-R$qh{98KDiioqNFeq><;-RAI%>|=@cuZ<6f zxu!mb83#AZ`Nl_{`$fDmIcm}V)8zW6!Au$(=bNEDW0w&Iqg9Hzd$CqP; zd3U5w%(hxOAf+*`xUa& z;pYE7kmgGz6EUj4f-*!3Gn%GySJ9sY+uOHkZ)@JkXEm42mj!7ovL?}O00Y6WtoFSP^6S80*_B>5bo-eyVN-t^ z&qNVQlfx{$okWaL>SfPyH96wm%VVcO+Lz`(BWXs|)&+3neAaoq7TlhN(s~Pe6tL{E z3>lC&+{Ifp@Vu!Cj-+W7JC0U6dmVc=$g)C z5B+~(v057D2q`Sp`z0)_a?|OQB5_)2=Iyy_1BnB7X>`u!2LLw$Wz=bkVBFF@iKx0| zE3zGaz2_4Wwk&}{!97S}j3u!`EqTxn+Fd~Ky6_6L=FiT*0c=DDEsv5ZLUZD1r-R9D zE+LS6PIDPrqOgnMG2E2nq6gkvC6T}mZX}XO)k82D45r%I`aIH9AwDX?NFIfWd0|b` cJm~Dir48`UjWJUbp-Kg;EzeodPkG(=AF|dRn*aa+ literal 0 HcmV?d00001 From 006445c7b298a5d6448e8275f5ab34ea69eb7000 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:29:58 +0000 Subject: [PATCH 46/54] build(deps): bump core from `8cf4d0f` to `431a3fb` Bumps [core](https://github.com/espruino/EspruinoAppLoaderCore) from `8cf4d0f` to `431a3fb`. - [Commits](https://github.com/espruino/EspruinoAppLoaderCore/compare/8cf4d0fbfc310e0d68d616ec779c1888475899a2...431a3fb743da5c370729ab748cb2c177e70a345b) --- updated-dependencies: - dependency-name: core dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 8cf4d0fbf..431a3fb74 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 8cf4d0fbfc310e0d68d616ec779c1888475899a2 +Subproject commit 431a3fb743da5c370729ab748cb2c177e70a345b From 08687fb265b64ca7042c44b4d76e16afa8361a47 Mon Sep 17 00:00:00 2001 From: Niko Komin Date: Tue, 29 Aug 2023 20:22:23 -0700 Subject: [PATCH 47/54] bugfix (enable settings page) --- apps/nightwatch/ChangeLog | 1 + apps/nightwatch/metadata.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/nightwatch/ChangeLog b/apps/nightwatch/ChangeLog index dc179ee9d..829c8c887 100644 --- a/apps/nightwatch/ChangeLog +++ b/apps/nightwatch/ChangeLog @@ -1 +1,2 @@ +1.1: bugfix (enable settings page) 1.0: first working version of App diff --git a/apps/nightwatch/metadata.json b/apps/nightwatch/metadata.json index 90e05214e..fc0150051 100644 --- a/apps/nightwatch/metadata.json +++ b/apps/nightwatch/metadata.json @@ -6,11 +6,12 @@ "supports" : ["BANGLEJS2"], "icon":"nightwatch.icon.png", "screenshots" : [ { "url":"screenshot.png","url":"screenshot2.png" } ], - "version":"1.0", + "version":"1.1", "description":"Logs sensor readings (currently T and p), show min/max and graph.", "tags": "tools,outdoors", "storage": [ {"name":"nightwatch.app.js","url":"nightwatch.app.js"}, + {"name":"nightwatch.settings.js","url":"nightwatch.settings.js"}, {"name":"nightwatch.img","url":"nightwatch.icon.js","evaluate":true} ] } From 72f49eeee71a57b49fa27b8c4eaf7ddd0c1f66e3 Mon Sep 17 00:00:00 2001 From: Niko Komin Date: Tue, 29 Aug 2023 21:00:52 -0700 Subject: [PATCH 48/54] bugfix (enable settings page) --- apps/nightwatch/ChangeLog | 2 +- apps/nightwatch/metadata.json | 3 ++- apps/nightwatch/nightwatch.settings.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/nightwatch/ChangeLog b/apps/nightwatch/ChangeLog index 829c8c887..c854e9e5b 100644 --- a/apps/nightwatch/ChangeLog +++ b/apps/nightwatch/ChangeLog @@ -1,2 +1,2 @@ -1.1: bugfix (enable settings page) 1.0: first working version of App +1.1: bugfix (enable settings page) diff --git a/apps/nightwatch/metadata.json b/apps/nightwatch/metadata.json index fc0150051..4de2a0271 100644 --- a/apps/nightwatch/metadata.json +++ b/apps/nightwatch/metadata.json @@ -13,5 +13,6 @@ {"name":"nightwatch.app.js","url":"nightwatch.app.js"}, {"name":"nightwatch.settings.js","url":"nightwatch.settings.js"}, {"name":"nightwatch.img","url":"nightwatch.icon.js","evaluate":true} - ] + ], + "data": [{"name":"nightwatch.json"}] } diff --git a/apps/nightwatch/nightwatch.settings.js b/apps/nightwatch/nightwatch.settings.js index c543b7343..744ebd8dc 100644 --- a/apps/nightwatch/nightwatch.settings.js +++ b/apps/nightwatch/nightwatch.settings.js @@ -2,7 +2,7 @@ var FILE = "nightwatch.json"; // Load settings var settings = Object.assign({ - dt: 30, + dt: 5, }, require('Storage').readJSON(FILE, true) || {}); function writeSettings() { From 1aa7a4d7b50ae4989faa68f14a870a00c7ef8840 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 30 Aug 2023 18:29:36 +0200 Subject: [PATCH 49/54] gipy: powersaving changes + fix for heights --- apps/gipy/ChangeLog | 2 ++ apps/gipy/README.md | 4 +++- apps/gipy/app.js | 18 +++++++++++------- apps/gipy/settings.js | 20 ++++++++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 73164dbd3..09637df1b 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -105,3 +105,5 @@ * Removed 'lost' indicator (we now change position to purple when lost) * Powersaving fix : don't powersave when lost * Bugfix for negative remaining distance when going backwards + * New settings for powersaving + * Adjustments to powersaving algorithm diff --git a/apps/gipy/README.md b/apps/gipy/README.md index 0df008b38..d96461dfe 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -109,9 +109,11 @@ Colors correspond to slopes. Few settings for now (feel free to suggest me more) : -- lost distance : at which distance from path are you considered to be lost ? - buzz on turns : should the watch buzz when reaching a waypoint ? - disable bluetooth : turn bluetooth off completely to try to save some power. +- lost distance : at which distance from path are you considered to be lost ? +- wake-up speed : if you drive below this speed powersaving will disable itself +- active-time : how long (in seconds) the screen should be turned on if activated before going back to sleep. - brightness : how bright should screen be ? (by default 0.5, again saving power) - power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points, when you touch the screen and when speed is below 13km/h. diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 56137ae58..46e29c359 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -26,6 +26,8 @@ let s = require("Storage"); var settings = Object.assign( { lost_distance: 50, + wake_up_speed: 13, + active_time: 10, brightness: 0.5, buzz_on_turns: false, disable_bluetooth: true, @@ -692,7 +694,7 @@ class Status { if (!this.active || !powersaving) { return; } - if (getTime() - this.last_activity > 30) { + if (getTime() - this.last_activity > settings.active_time) { this.active = false; Bangle.setLCDBrightness(0); if (settings.power_lcd_off) { @@ -772,13 +774,13 @@ class Status { if (in_menu) { return; } - if (this.instant_speed * 3.6 < 13) { + if (this.instant_speed * 3.6 < settings.wake_up_speed) { this.activate(); // if we go too slow turn on, we might be looking for the direction to follow if (!this.default_options) { this.default_options = true; Bangle.setOptions({ - lockTimeout: 10000, + lockTimeout: 0, backlightTimeout: 10000, wakeOnTwist: true, powerSave: true, @@ -798,7 +800,6 @@ class Status { wakeOnTouch: true, powerSave: false, }); - Bangle.setPollInterval(2000); // disable accelerometer as much as we can (a value of 4000 seem to cause hard reboot crashes (segfaults ?) so keep 2000) } } this.check_activity(); // if we don't move or are in menu we should stay on @@ -879,8 +880,10 @@ class Status { // }, time_to_next_point); // } // } - if (this.reaching != next_point && this.distance_to_next_point <= 100) { + if (this.distance_to_next_point <= 100) { this.activate(); + } + 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) { @@ -1029,8 +1032,8 @@ class Status { let distance_per_pixel = displayed_length / graph_width; let start_point_index = 0; - let end_point_index = this.remaining_distances.length - 1; - for (let i = 0; i < this.remaining_distances.length; i++) { + let end_point_index = this.path.len - 1; + for (let i = 0; i < this.path.len; i++) { let point_distance = path_length - this.remaining_distances[i]; if (point_distance <= display_start) { start_point_index = i; @@ -1040,6 +1043,7 @@ class Status { break; } } + end_point_index = Math.min(end_point_index+1, this.path.len -1); let max_height = Number.NEGATIVE_INFINITY; let min_height = Number.POSITIVE_INFINITY; for (let i = start_point_index; i <= end_point_index; i++) { diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js index 395b1ac93..1b030f5cd 100644 --- a/apps/gipy/settings.js +++ b/apps/gipy/settings.js @@ -3,6 +3,8 @@ // Load settings var settings = Object.assign({ lost_distance: 50, + wake_up_speed: 13, + active_time: 10, buzz_on_turns: false, disable_bluetooth: true, brightness: 0.5, @@ -44,6 +46,24 @@ writeSettings(); }, }, + "wake-up speed": { + value: settings.wake_up_speed, + min: 0, + max: 30, + onchange: (v) => { + settings.wake_up_speed = v; + writeSettings(); + }, + }, + "active time": { + value: settings.active_time, + min: 5, + max: 60, + onchange: (v) => { + settings.active_time = v; + writeSettings(); + }, + }, "brightness": { value: settings.brightness, min: 0, From c645b3110504d38e9b05ebd7b5ba8384ca486652 Mon Sep 17 00:00:00 2001 From: Niko Komin Date: Wed, 30 Aug 2023 11:06:26 -0700 Subject: [PATCH 50/54] corrected screenshot --- apps/nightwatch/screenshot.png | Bin 3608 -> 3660 bytes apps/nightwatch/screenshot2.png | Bin 3795 -> 3855 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/nightwatch/screenshot.png b/apps/nightwatch/screenshot.png index 194c91b28c327b6ca06d06515b1dd81197411ed9..3f524eac9e1402c8ae66f48f312f8748d41cb628 100644 GIT binary patch literal 3660 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 literal 3608 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 literal 3795 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 From 30de2cc6b2e9bc2f1fa268f1d3352c3f7f3b0723 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Thu, 31 Aug 2023 09:32:55 +0200 Subject: [PATCH 51/54] gipy: documentation update --- apps/gipy/README.md | 36 +++++++++++++++++++++++++++++++++--- apps/gipy/metadata.json | 2 +- apps/gipy/shot.png | Bin 0 -> 4703 bytes 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 apps/gipy/shot.png diff --git a/apps/gipy/README.md b/apps/gipy/README.md index d96461dfe..f4c68d027 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -42,7 +42,7 @@ also a nice open source option. 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. +Once you have your gpx file you need to convert it to *gps* 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. @@ -82,8 +82,7 @@ On your screen you can see: ### Lost -If you stray away from path we will rescale the display to continue displaying nearby segments and -display the direction to follow as a purple segment. Your main position will also turn to purple. +If you stray away from path we will display the direction to follow as a purple segment. Your main position will also turn to purple. Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you are. On path it just needed to scan a few points ahead and behind. @@ -104,6 +103,11 @@ for the whole path. ![Screenshot](heights.png) Colors correspond to slopes. +Above 15% will be red, above 8% orange, above 3% yellow, below 3% and -3% is green and shades of blue +are for descents. + +You should note that the precision is not very good. The input data is not very precise and you only get the +slopes between path points. Don't expect to see small bumps on the road. ### Settings @@ -118,6 +122,32 @@ Few settings for now (feel free to suggest me more) : - power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points, when you touch the screen and when speed is below 13km/h. +### Powersaving + +Starting with release 0.20 we experiment with power saving. + +There are now two display modes : + +- active : the screen is lit back (default at 50% light but can be configured with the *brightness* setting) +- inactive : by default the screen is not lit but you can also power it off completely (with the *power lcd off* setting) + +The algorithm works in the following ways : + +- some events will *activate* : the display will turn *active* +- if no activation event occur for at least 10 seconds (or *active-time* setting) we switch back to *inactive* + +Activation events are the following : + +- you are near (< 100m) the next point on path +- you are slow (< *wake-up speed* setting (13 km/h by default)) +- you press the button / touch the screen + + +Power saving has been tested on a very long trip with several benefits + +- longer battery life +- waking up near path points will attract your attention more easily when needed + ### Caveats It is good to use but you should know : diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json index 3526e1afa..d6b5e1405 100644 --- a/apps/gipy/metadata.json +++ b/apps/gipy/metadata.json @@ -8,7 +8,7 @@ "icon": "gipy.png", "type": "app", "tags": "tool,outdoors,gps", - "screenshots": [{"url":"splash.png"}], + "screenshots": [{"url":"splash.png"}, {"url":"heights.png"}, {"url":"shot.png"}], "supports": ["BANGLEJS2"], "readme": "README.md", "interface": "interface.html", diff --git a/apps/gipy/shot.png b/apps/gipy/shot.png new file mode 100644 index 0000000000000000000000000000000000000000..c2ffea7241be4305f60e338965c3a4f78131cbad GIT binary patch literal 4703 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 hxtE Date: Thu, 31 Aug 2023 10:36:02 +0200 Subject: [PATCH 52/54] gipy: typo fix --- apps/gipy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index f4c68d027..8539167e1 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -103,7 +103,7 @@ for the whole path. ![Screenshot](heights.png) Colors correspond to slopes. -Above 15% will be red, above 8% orange, above 3% yellow, below 3% and -3% is green and shades of blue +Above 15% will be red, above 8% orange, above 3% yellow, between 3% and -3% is green and shades of blue are for descents. You should note that the precision is not very good. The input data is not very precise and you only get the From 7d636013556d934b9631e4db99a1cdb11c97df22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sarah=20Fjelsted=20Alr=C3=B8e?= Date: Sun, 3 Sep 2023 21:50:54 +0200 Subject: [PATCH 53/54] Added app 'Rebble Agenda' --- apps/rebbleagenda/ChangeLog | 1 + apps/rebbleagenda/README.md | 24 + apps/rebbleagenda/app-icon.js | 1 + apps/rebbleagenda/app.js | 583 ++++++++++++++++++ apps/rebbleagenda/app.png | Bin 0 -> 479 bytes apps/rebbleagenda/metadata.json | 26 + .../screenshot_rebbleagenda_customtheme.png | Bin 0 -> 2878 bytes .../screenshot_rebbleagenda_events.png | Bin 0 -> 2654 bytes .../screenshot_rebbleagenda_sun.png | Bin 0 -> 2430 bytes apps/rebbleagenda/settings.js | 69 +++ 10 files changed, 704 insertions(+) create mode 100644 apps/rebbleagenda/ChangeLog create mode 100644 apps/rebbleagenda/README.md create mode 100644 apps/rebbleagenda/app-icon.js create mode 100644 apps/rebbleagenda/app.js create mode 100644 apps/rebbleagenda/app.png create mode 100644 apps/rebbleagenda/metadata.json create mode 100644 apps/rebbleagenda/screenshot_rebbleagenda_customtheme.png create mode 100644 apps/rebbleagenda/screenshot_rebbleagenda_events.png create mode 100644 apps/rebbleagenda/screenshot_rebbleagenda_sun.png create mode 100644 apps/rebbleagenda/settings.js diff --git a/apps/rebbleagenda/ChangeLog b/apps/rebbleagenda/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/rebbleagenda/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/rebbleagenda/README.md b/apps/rebbleagenda/README.md new file mode 100644 index 000000000..77afd4b48 --- /dev/null +++ b/apps/rebbleagenda/README.md @@ -0,0 +1,24 @@ +# Rebble Agenda + +Agenda app for showing upcoming events in an animated fashion. +Heavily inspired by the inbuilt agenda of the pebble time. +Switch between calendar events by swiping up or down. Click the button to exit. + +![Two events shown using the default light system theme](./screenshot_rebbleagenda_events.png) ![The last event of the agenda shown using a custom red theme](./screenshot_rebbleagenda_customtheme.png) ![An animated sun shows the day of the following events](./screenshot_rebbleagenda_sun.png) + +## Settings + +- *Use system theme* - Use the colors of the system theme. Otherwise use following colors. +- *Accent* - The color of the rightmost accent bar if not following system theme. +- *Background* - The background color to use if not following system theme. +- *Foreground* - The foreground color to use if not following system theme. + +## Notes + +- The weather icon in the top right corner is currently just showing the current weather as provided by [weather](https://github.com/espruino/BangleApps/blob/master/apps/weather/). Closest forecast to be implemented in a future release. +- Events only show as much of their title and description as can be fit on the screen, which is one and four (wrapped) lines respectively. +- Events are loaded from ```android.calendar.json```, which is read in its entirety. If you have a very busy schedule, loading may take a second or two. + +## Creator + +- [Sarah Alrøe](https://github.com/SarahAlroe), August+September 2023 diff --git a/apps/rebbleagenda/app-icon.js b/apps/rebbleagenda/app-icon.js new file mode 100644 index 000000000..2b2773ee7 --- /dev/null +++ b/apps/rebbleagenda/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/ADscjgRhDhgePCKIv1hgAEDoYJFAA4RJFyQvcGBYRGlIdDlIuLCJgvQggdDggvLCJgv/PoOgDgOgR5oRLF6AeBgkEFxgRNF6IAdF/4vpjwAkF/4v/F/4vRjgAEA6Iv/F/4v/F/4RHADov/Q6MMAAgv/F/4v/F/4vJADov/F/4v/F5IwkFxQwjFxgA/AH4A/AH4AZA")) \ No newline at end of file diff --git a/apps/rebbleagenda/app.js b/apps/rebbleagenda/app.js new file mode 100644 index 000000000..3b6eca900 --- /dev/null +++ b/apps/rebbleagenda/app.js @@ -0,0 +1,583 @@ +{ + /* Requires */ + const weather = require('weather'); + require("Font6x12").add(Graphics); + require("Font8x16").add(Graphics); + const SETTINGS_FILE = "rebbleagenda.json"; + const settings = require("Storage").readJSON(SETTINGS_FILE, 1) || {'system':true, 'bg': '#fff','fg': '#000','acc': '#0FF'}; + + /* Layout consts */ + const MARKER_SIZE = 4; + const BORDER_SIZE = 6; + const WIDGET_SIZE = 24; + const PRIMARY_OFFSET = WIDGET_SIZE + BORDER_SIZE + MARKER_SIZE - 20 / 2; + const SECONDARY_OFFSET = g.getHeight() - WIDGET_SIZE - 16 - 20; + const MARKER_POS_UPPER = Uint8Array([g.getWidth() - BORDER_SIZE - MARKER_SIZE, WIDGET_SIZE + BORDER_SIZE + MARKER_SIZE]); + const PIN_SIZE = 10; + const ACCENT_WIDTH = 2 * BORDER_SIZE + 2 * MARKER_SIZE; // �=2r, borders each side. + + const TEXT_COLOR = settings.system?g.theme.fg:settings.fg; + const BG_COLOR = settings.system?g.theme.bg:settings.bg; + const ACCENT_COLOR = settings.system?g.theme.bgH:settings.acc; + const SUN_COLOR_START = 0xF800; + const SUN_COLOR_END = 0xFFE0; + const SUN_FACE = 0x0000; + + /* Animation polygon sets*/ + const CLEAR_POLYS_1 = [ + new Uint8Array([0, 176, 0, 0, 176, 0, 176, 0, 0, 0, 0, 176]), + new Uint8Array([0, 176, 0, 0, 176, 0, 170, 7, 10, 12, 7, 168]), + new Uint8Array([0, 176, 0, 0, 176, 0, 139, 49, 41, 45, 43, 125]), + new Uint8Array([0, 176, 0, 0, 176, 0, 90, 81, 82, 86, 85, 94]), + new Uint8Array([0, 176, 0, 0, 176, 0, 91, 85, 85, 85, 85, 91]) + ]; + + const CLEAR_POLYS_2 = [ + new Uint8Array([0, 176, 176, 176, 176, 0, 176, 0, 176, 176, 0, 176]), + new Uint8Array([0, 176, 176, 176, 176, 0, 170, 7, 162, 161, 7, 168]), + new Uint8Array([0, 176, 176, 176, 176, 0, 139, 49, 130, 126, 43, 125]), + new Uint8Array([0, 176, 176, 176, 176, 0, 90, 81, 95, 89, 85, 94]), + new Uint8Array([0, 176, 176, 176, 176, 0, 91, 85, 91, 91, 85, 91]) + ]; + + const BREATHING_POLYS = [ + new Uint8Array([72, 88, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 84, 88]), + new Uint8Array([63, 88, 64, 73, 78, 73, 78, 73, 78, 73, 78, 73, 92, 73, 93, 88]), + new Uint8Array([60, 88, 56, 76, 78, 60, 78, 60, 78, 60, 78, 60, 100, 76, 96, 88]), + new Uint8Array([56, 88, 50, 78, 64, 54, 78, 54, 78, 54, 92, 54, 106, 78, 100, 88]), + new Uint8Array([53, 88, 47, 80, 52, 53, 78, 41, 78, 41, 104, 53, 109, 80, 103, 88]), + new Uint8Array([50, 88, 43, 81, 43, 51, 63, 32, 92, 32, 113, 51, 113, 81, 106, 88])]; + const SUN_EYE_LEFT_POLY = new Uint8Array([56, 52, 64, 44, 72, 52, 72, 55, 69, 54, 64, 50, 58, 55, 56, 55]); + const SUN_EYE_RIGHT_OFFSET = 30; + const MOUTH_POLY = new Uint8Array([78, 77, 68, 75, 67, 73, 69, 71, 78, 73, 87, 71, 89, 73, 88, 75]); + + /* Animation timings */ + const TIME_CLEAR_ANIM = 400; + const TIME_CLEAR_BREAK = 10; + const TIME_DEFAULT_ANIM = 300; + const TIME_BUMP_ANIM = 200; + const TIME_EXIT_ANIM = 500; + const TIME_EVENT_CHANGE = 150; + const TIME_EVENT_BREAK_IN = 300; + const TIME_EVENT_BREAK_ANIM = 800; + const TIME_EVENT_BREAK_HALT = 500; + const TIME_EVENT_BREAK_OUT = 500; + + /* Utility functions */ + + /** + * Check if two dates occur on the same day + * @param {Date} d1 The first date to compare + * @param {Date} d2 The second date to compare + * @returns {Boolean} The two dates are on the same day + */ + const isSameDay = function (d1, d2) { + return (d1.getDate() == d2.getDate() && d1.getMonth() == d2.getMonth() && d1.getFullYear() == d2.getFullYear()); + }; + + /** + * Apply sinusoidal easing to a value 0-1 + * @param {Number} x Number to ease + * @returns {Number} Ease of x + */ + const ease = function (x) { + "jit"; + return 1 - (Math.cos(Math.PI * x) + 1) / 2; + }; + + /** + * Map from 0-1 to a number interval + * @param {Number} outMin Minimum output number + * @param {Number} outMax Maximum output number + * @param {Number} x Number between 0 and 1 to map from + * @returns {Number} x mapped between min and max + */ + const map = function (outMin, outMax, x) { + "jit"; + return outMin + x * (outMax - outMin); + }; + + /** + * Return [0-1] progress through an interval + * @param {Number} start When the interval was started in ms + * @param {Number} end When the interval is supposed to stop in ms + * @returns {Number} Value between 0 and 1 reflecting progress through interval + */ + const timeProgress = function (start, end) { + "jit"; + const length = end - start; + const delta = Date.now() - start; + return Math.min(Math.max(delta / length, 0), 1); + }; + + /** + * Interpolate between sets of polygon coordinates + * @param {Array} polys An array of arrays, each containing an equally long set of coordinates + * @param {Number} pos Progress through interpolation [0-1] + * @returns {Array} Interpolation between the two closest sets of coordinates + */ + const interpolatePoly = function (polys, pos) { + const span = polys.length - 1; + pos = pos * span; + pos = pos > span ? span : pos; + const upper = polys[Math.ceil(pos)]; + const lower = polys[Math.floor(Math.max(pos - 0.000001, 0))]; + const interp = pos - Math.floor(pos - 0.000001); + return upper.map((up, i) => { + return Math.round(up * interp + lower[i] * (1 - interp)); + }); + }; + + /** + * Repeatedly call callback with progress through an interval of length time + * @param {Function} anim Callback which takes i, animation progress [0-1] + * @param {Number} time How many ms the animation should last + * @returns {void} + */ + const doAnim = function (anim, time) { + const animStart = Date.now(); + const animEnd = animStart + time; + let i = 0; + do { + i = timeProgress(animStart, animEnd); + anim(i); + } while (i < 1); + anim(1); + }; + + /* Screen draw functions */ + + /** + * Draw an event + * @param {Number} index Index in the events array of event to draw + * @param {Number} yOffset Vertical pixel offset of the draw + * @param {Boolean} drawSecondary Should secondary event be drawn if possible? + */ + const drawEvent = function (index, yOffset, drawSecondary) { + g.setColor(TEXT_COLOR); + // Draw the event time + g.setFontAlign(-1, -1, 0); + g.setFont("Vector", 20); + g.drawString(events[index].time, BORDER_SIZE, PRIMARY_OFFSET + yOffset); + + // Draw the event title + g.setFont("8x16"); + g.drawString(events[index].title, BORDER_SIZE, PRIMARY_OFFSET + 20 + yOffset); + + // And the event description + g.setFont("6x12"); + g.drawString(events[index].description, BORDER_SIZE, PRIMARY_OFFSET + 20 + 12 + 2 + yOffset); + + // Draw a secondary event if asked to and exists + if (drawSecondary) { + if (index + 1 < events.length) { + if (events[index].date != events[index + 1].date) { + // If event belongs to another day, draw circle + g.fillCircle((g.getWidth() - ACCENT_WIDTH) / 2, g.getHeight() - MARKER_SIZE - WIDGET_SIZE - BORDER_SIZE + yOffset, MARKER_SIZE); + } else { + // Draw event time and title + g.setFont("Vector", 20); + g.drawString(events[index + 1].time, BORDER_SIZE, SECONDARY_OFFSET + yOffset); + g.setFont("8x16"); + g.drawString(events[index + 1].title, BORDER_SIZE, SECONDARY_OFFSET + 20 + yOffset); + } + } else { + // If no more events exist, draw end + g.setFontAlign(0, 1, 0); + g.setFont("Vector", 20); + g.drawString("End", (g.getWidth() - ACCENT_WIDTH) / 2, g.getHeight() - BORDER_SIZE + yOffset); + } + } + }; + + /** + * Draw a two-line caption beneath a figure (Just beneath centre) + * @param {String} first Top string to draw + * @param {String} second Bottom string to draw + * @param {Number} yOffset Vertical pixel offset of the draw + */ + const drawFigureCaption = function (first, second, yOffset) { + g.setFontAlign(0, -1, 0); + g.setFont("Vector", 18); + g.setColor(TEXT_COLOR); + g.drawString(first, (g.getWidth() - ACCENT_WIDTH) / 2, g.getHeight() / 2 + BORDER_SIZE + yOffset); + g.drawString(second, (g.getWidth() - ACCENT_WIDTH) / 2, g.getHeight() / 2 + BORDER_SIZE + 20 + yOffset); + }; + + /** + * Clear the contents area of the default layout + */ + const clearContent = function () { + g.setColor(BG_COLOR); + g.fillRect(0, 0, g.getWidth() - ACCENT_WIDTH - PIN_SIZE, g.getHeight()); + }; + + /** + * Draw the sun figure (above centre, in content area) + * @param {Number} progress Progress through the sun expansion animation, between 0 and 1 + * @param {Number} yOffset Vertical pixel offset of the draw + */ + const drawSun = function (progress, yOffset) { + const p = ease(progress); + const sunColor = progress == 1 ? SUN_COLOR_END : g.blendColor(SUN_COLOR_START, SUN_COLOR_END, p); + g.setColor(sunColor); + g.fillPoly(g.transformVertices(interpolatePoly(BREATHING_POLYS, p), { y: yOffset })); + + if (progress > 0.6) { + const faceP = ease((progress - 0.6) * 2.5); + g.setColor(g.blendColor(sunColor, SUN_FACE, faceP)); + g.fillPoly(g.transformVertices(SUN_EYE_LEFT_POLY, { y: map(20, 0, faceP) + yOffset })); + g.fillPoly(g.transformVertices(SUN_EYE_LEFT_POLY, { x: SUN_EYE_RIGHT_OFFSET, y: map(20, 0, faceP) + yOffset })); + g.fillPoly(g.transformVertices(MOUTH_POLY, { y: map(10, 0, faceP) + yOffset })); + } + + g.setColor(TEXT_COLOR); + g.fillRect({ + x: map((g.getWidth() - ACCENT_WIDTH) / 2 - MARKER_SIZE, 20, p), + y: map(g.getHeight() / 2 - MARKER_SIZE, g.getHeight() / 2 - MARKER_SIZE / 2, p) + yOffset, + x2: map((g.getWidth() - ACCENT_WIDTH) / 2 + MARKER_SIZE, (g.getWidth() - ACCENT_WIDTH) - 20, p), + y2: map(g.getHeight() / 2 + MARKER_SIZE / 2, g.getHeight() / 2, p) + yOffset + }); + }; + + /* Animation functions */ + + /** + * Animate clearing the screen to accent color with a single dot in the middle + */ + const animClearScreen = function () { + let oldPoly1 = CLEAR_POLYS_1[0]; + let oldPoly2 = CLEAR_POLYS_2[0]; + doAnim(i => { + i = ease(i); + poly1 = interpolatePoly(CLEAR_POLYS_1, i); + poly2 = interpolatePoly(CLEAR_POLYS_2, i); + // Fill in black line + g.setColor(TEXT_COLOR); + g.fillPoly(poly1); + g.fillPoly(poly2); + + // Fill in outer shape + g.setColor(ACCENT_COLOR); + g.fillPoly(oldPoly1); + g.fillPoly(oldPoly2); + g.flip(); + + // Save poly for next loop outer shape + oldPoly1 = poly1; + oldPoly2 = poly2; + }, TIME_CLEAR_ANIM); + + // Draw circle + g.setColor(TEXT_COLOR); + g.fillCircle(g.getWidth() / 2, g.getHeight() / 2, MARKER_SIZE); + g.flip(); + }; + + /** + * Animate from a cleared screen and dot to the default layout + */ + const animDefaultScreen = function () { + doAnim(i => { + // Draw the circle moving into the corner + i = ease(i); + const circleX = map(g.getWidth() / 2, MARKER_POS_UPPER[0], i); + const circleY = map(g.getHeight() / 2, MARKER_POS_UPPER[1], i); + g.setColor(TEXT_COLOR); + g.fillCircle(circleX, circleY, MARKER_SIZE); + + // Move the background poly in from the left + g.setColor(BG_COLOR); + const accentX = map(0, g.getWidth() - ACCENT_WIDTH, i); + g.fillPoly([0, 0, accentX, 0, accentX, MARKER_POS_UPPER[1] - PIN_SIZE, accentX - PIN_SIZE, MARKER_POS_UPPER[1], accentX, MARKER_POS_UPPER[1] + PIN_SIZE, accentX, 176, 0, 176]); + g.flip(); + + // Clear the circle for the next loop + g.setColor(ACCENT_COLOR); + g.fillCircle(circleX, circleY, MARKER_SIZE + 2); + }, TIME_DEFAULT_ANIM); + + // Finish up the circle + const w = weather.get(); + if (w && (w.code || w.txt)) { + doAnim(i => { + weather.drawIcon(w, MARKER_POS_UPPER[0], MARKER_POS_UPPER[1], MARKER_SIZE * 2); + g.setColor(TEXT_COLOR); + g.fillCircle(MARKER_POS_UPPER[0], MARKER_POS_UPPER[1], MARKER_SIZE * ease(1 - i)); + g.flip(); + }, 100); + } else { + g.setColor(TEXT_COLOR); + g.fillCircle(MARKER_POS_UPPER[0], MARKER_POS_UPPER[1], MARKER_SIZE); + } + }; + + /** + * Animate the sun figure expand or shrink fully + * @param {Number} direction Direction in which to animate. +1 = Expand. -1 = Shrink + */ + const animSun = function (direction) { + doAnim(i => { + // Clear and redraw just the sun area + g.setColor(BG_COLOR); + g.fillRect(0, 31, g.getWidth() - ACCENT_WIDTH - PIN_SIZE, g.getHeight() / 2 + 4); + drawSun((direction == 1 ? 0 : 1) + i * direction, 0); + g.flip(); + }, TIME_EVENT_BREAK_ANIM); + }; + + /** + * Animate from centre dot to an event or backwards. Used for entering (forwards) or leaving (backwards) the day-change animation + * @param {Number} index Index of the event to draw animate in or out + * @param {Number} direction Direction of the animation. +1 = Event -> Dot. -1 = Dot -> Event + */ + const animEventToMarker = function (index, direction) { + doAnim(i => { + let ei = direction == 1 ? ease(i) : ease(1 - i); + clearContent(); + drawEvent(index, -(SECONDARY_OFFSET - PRIMARY_OFFSET) * ei, false); + g.fillCircle((g.getWidth() - ACCENT_WIDTH) / 2, map(g.getHeight() - MARKER_SIZE - WIDGET_SIZE - BORDER_SIZE, g.getHeight() / 2, ei), MARKER_SIZE); + g.flip(); + }, TIME_EVENT_BREAK_IN); + + }; + + /** + * Blit the current contents of content area out of screen, replacing it with something. Currently only for moving stuff upwards. + * @param {Function} thing Callback for the new thing to draw on the screen + * @param {Number} time How long the animation should last + */ + const animBlitToX = function (thing, time) { + let oldI = 0; + doAnim(i => { + // Move stuff out of frame, index into frame + g.blit({ + x1: 0, + y1: 0, + w: g.getWidth() - ACCENT_WIDTH - PIN_SIZE, + h: ease(1 - oldI) * g.getHeight(), + x2: 0, + y2: - (ease(i) - ease(oldI)) * g.getHeight(), + setModified: true + }); + g.setColor(BG_COLOR); + // Only clear where old stuff no longer is + g.fillRect(0, g.getHeight() * (1 - ease(i)), g.getWidth() - ACCENT_WIDTH - PIN_SIZE, g.getHeight()); + thing(i); + g.flip(); + oldI = i; + }, time); + }; + + /** + * Transition between one event and another, showing a day-change animation if needed + * @param {Number} startIndex The event index that we are animating out of + * @param {Number} endIndex The event index that we are animating into + */ + const animEventTransition = function (startIndex, endIndex) { + if (events[startIndex].date == events[endIndex].date) { + // If both events are within the same day, just scroll from one to the other. + // First determine which event is on top and which direction we are animating in + let topIndex = (startIndex < endIndex) ? startIndex : endIndex; + let botIndex = (startIndex < endIndex) ? endIndex : startIndex; + let direction = (startIndex < endIndex) ? 1 : -1; + let offset = (startIndex < endIndex) ? 0 : 1; + + doAnim(i => { + // Animate the two events moving towards their destinations + clearContent(); + drawEvent(topIndex, -(SECONDARY_OFFSET - PRIMARY_OFFSET) * ease(offset + direction * i), false); + drawEvent(botIndex, (SECONDARY_OFFSET - PRIMARY_OFFSET) - (SECONDARY_OFFSET - PRIMARY_OFFSET) * ease(offset + direction * i), true); + g.flip(); + }, TIME_EVENT_CHANGE); + + // Finally, reset contents and redraw for good measure + clearContent(); + drawEvent(endIndex, 0, true); + g.flip(); + } else { + // The events are on different days, trigger day-change animation + if (startIndex < endIndex) { + // Destination is later, Stuff moves upwards + animEventToMarker(startIndex, 1); // The day-end dot moves to center of screen + drawFigureCaption(events[endIndex].weekday, events[endIndex].date, 0); // Caption between sun appears, no need to continuously redraw + animSun(1); // Animate the sun expanding + doAnim(i => { }, TIME_EVENT_BREAK_HALT); // Wait for a moment + animBlitToX(i => { drawEvent(endIndex, g.getHeight() - g.getHeight() * ease(i), true); }, TIME_EVENT_BREAK_OUT); // Blit the sun and caption out, replacing with destination event + } else { + // Destination is earlier, content moves downwards + doAnim(i => { + // Can't animBlit, draw sun and figure caption replacing origin event + clearContent(); + drawEvent(startIndex, g.getHeight() * ease(i), true); + drawSun(1, - g.getHeight() * ease(1 - i)); + drawFigureCaption(events[endIndex].weekday, events[endIndex].date, - g.getHeight() * ease(1 - i)); + g.flip(); + }, TIME_EVENT_BREAK_OUT); + doAnim(i => { }, TIME_EVENT_BREAK_HALT); // Wait for a moment + animSun(-1); // Collapse the sun + animEventToMarker(endIndex, -1); // Animate from dot to destination event + } + } + g.flip(); + }; + + /** + * Bump the event because we've reached an end + * @param {Number} index The index of the event which we are currently at (probably last) + * @param {Number} direction Which direction to bump. +1 = content moves down, then up. -1 = content moves up, back down + */ + const animEventBump = function (index, direction) { + doAnim(i => { + clearContent(); + drawEvent(index, Math.sin(Math.PI * i) * 24 * direction, true); + g.flip(); + }, TIME_BUMP_ANIM); + }; + + /** + * Run the exit animation of the application + */ + const animExit = function () { + // First, move out (downwards) the current event + doAnim(i => { + clearContent(); + drawEvent(currentEventIndex, ease(i) * g.getHeight(), true); + g.flip(); + }, TIME_EXIT_ANIM / 3 * 2); + + // Clear the screen leftwards with the accent color + g.setColor(ACCENT_COLOR); + doAnim(i => { + g.fillRect(ease(1 - i) * g.getWidth(), 0, g.getWidth(), g.getHeight()); + g.flip(); + }, TIME_EXIT_ANIM / 3); + }; + + /** + * Animate from empty default screen to the first event to show. + * If the event we're moving to is not later today, show the date first. + */ + const animFirstEvent = function () { + if (!isSameDay(new Date(events[currentEventIndex].timestamp * 1000), new Date())) { + drawFigureCaption(events[currentEventIndex].weekday, events[currentEventIndex].date, 0); + animSun(1); + doAnim(i => { }, TIME_EVENT_BREAK_HALT); + animBlitToX(i => { drawEvent(currentEventIndex, g.getHeight() - g.getHeight() * ease(i), true); }, TIME_EVENT_BREAK_OUT, 1); + } else { + drawEvent(currentEventIndex, 0, true); + } + }; + + /* Setup */ + + /* Load events */ + const today = new Date(); + const tomorrow = new Date(); + const yesterday = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + yesterday.setDate(yesterday.getDate() - 1); + g.setFont("6x12"); + const locale = require("locale"); + + let events = (require("Storage").readJSON("android.calendar.json", true) || []).map(event => { + // Title uses 8x16 font, 8 px wide characters. Limit title to fit on a line. + let title = event.title; + if (title.length > (g.getWidth() - 2 * BORDER_SIZE - ACCENT_WIDTH) / 8) { + title = title.slice(0, ((g.getWidth() - 2 * BORDER_SIZE - ACCENT_WIDTH) / 8) - 3) + "..."; + } + + // Wrap description to fit four lines of content + let description = g.wrapString(event.description, g.getWidth() - 2 * BORDER_SIZE - ACCENT_WIDTH - PIN_SIZE).slice(0, 4).join("\n"); + + // Set weekday text + let eventDate = new Date(event.timestamp * 1000); + let weekday = locale.dow(eventDate); + if (isSameDay(eventDate, today)) { + weekday = /*LANG*/"Today"; + } else if (isSameDay(eventDate, tomorrow)) { + weekday = /*LANG*/"Tomorrow"; + } else if (isSameDay(eventDate, yesterday)) { + weekday = /*LANG*/"Yesterday"; + } + + return { + timestamp: event.timestamp, + weekday: weekday, + date: locale.date(eventDate, 1), + time: locale.time(eventDate, 1) + locale.meridian(eventDate), + title: title, + description: description + }; + }).sort((a, b) => { return a.timestamp - b.timestamp; }); + + // If no events, add a note. + if (events.length == 0) { + events[0] = { + timestamp: Date.now() / 1000, + weekday: /*LANG*/"Today", + date: require("locale").date(new Date(), 1), + time: require("locale").time(new Date(), 1), + title: /*LANG*/"No events", + description: /*LANG*/"Nothing to do" + }; + } + + // We should start at the first event later than now + let currentEventIndex = events.findIndex((event) => { return event.timestamp * 1000 > Date.now(); }); + if (currentEventIndex == -1) currentEventIndex = 0; // Or just first event if none found + + // Setup the UI with remove to support fast load + Bangle.setUI({ + mode: "custom", + btn: () => { animExit(); Bangle.load(); }, + remove: function () { + require("widget_utils").show(); + delete Graphics.prototype.Font6x12; + delete Graphics.prototype.Font8x16; + Bangle.removeListener('swipe', onSwipe); + }, + }); + + /** + * Callback for swipe gesture. Transitions between adjacent events. + * @param {Number} directionLR Unused. + * @param {Number} directionUD Whether swipe direction is up or down + */ + const onSwipe = function (directionLR, directionUD) { + if (directionUD == -1) { + // Swiping up + if (currentEventIndex + 1 < events.length) { + // Animate to the next event + animEventTransition(currentEventIndex, currentEventIndex + 1); + currentEventIndex += 1; + } else { + // We've hit the end, bump + animEventBump(currentEventIndex, -1); + } + } else if (directionUD == 1) { + //Swiping down + if (currentEventIndex > 0) { + // Animate to the previous event + animEventTransition(currentEventIndex, currentEventIndex - 1); + currentEventIndex -= 1; + } else { + // If swiping earlier than earliest event, exit back to watchface + animExit(); + Bangle.load(); + } + } + }; + + // Ready animations for showing the first event, then register swipe listener for switching events + setTimeout(() => { + animDefaultScreen(); + animFirstEvent(); + Bangle.on('swipe', onSwipe); + }, TIME_CLEAR_ANIM + TIME_CLEAR_BREAK); + animClearScreen(); // Start visible changes by clearing the screen + + // Load and hide widgets to background + Bangle.loadWidgets(); + require("widget_utils").hide(); +} \ No newline at end of file diff --git a/apps/rebbleagenda/app.png b/apps/rebbleagenda/app.png new file mode 100644 index 0000000000000000000000000000000000000000..20715656561dcb8df20a8ebd30c1bba33e7e0335 GIT binary patch literal 479 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 literal 0 HcmV?d00001 diff --git a/apps/rebbleagenda/screenshot_rebbleagenda_events.png b/apps/rebbleagenda/screenshot_rebbleagenda_events.png new file mode 100644 index 0000000000000000000000000000000000000000..c94c0d9c4747b84a0d15d63d77328b5974216b05 GIT binary patch literal 2654 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 { + s.system = v; + save(); + }, + }, + /*LANG*/'Accent': { + value: 0 | color_codes.indexOf(s.acc), + min: 0, max: color_codes.length-1, + format: v => color_options[v], + onchange: v => { + s.acc = color_codes[v]; + save(); + }, + }, + /*LANG*/'Background': { + value: 0 | ground_codes.indexOf(s.bg), + min: 0, max: ground_codes.length-1, + format: v => ground_options[v], + onchange: v => { + s.bg = ground_codes[v]; + save(); + }, + }, + /*LANG*/'Foreground': { + value: 0 | ground_codes.indexOf(s.fg), + min: 0, max: ground_codes.length-1, + format: v => ground_options[v], + onchange: v => { + s.fg = ground_codes[v]; + save(); + }, + } + }); +}); \ No newline at end of file From 75f41f705d02b3964cf5f35d4424e3ad67940703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sarah=20Fjelsted=20Alr=C3=B8e?= Date: Sun, 3 Sep 2023 22:19:41 +0200 Subject: [PATCH 54/54] Fixed icon palette, fixed settings key that breaks on minify, disabled emulator. --- apps/rebbleagenda/app-icon.js | 2 +- apps/rebbleagenda/metadata.json | 1 - apps/rebbleagenda/settings.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/rebbleagenda/app-icon.js b/apps/rebbleagenda/app-icon.js index 2b2773ee7..d432f8179 100644 --- a/apps/rebbleagenda/app-icon.js +++ b/apps/rebbleagenda/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/ADscjgRhDhgePCKIv1hgAEDoYJFAA4RJFyQvcGBYRGlIdDlIuLCJgvQggdDggvLCJgv/PoOgDgOgR5oRLF6AeBgkEFxgRNF6IAdF/4vpjwAkF/4v/F/4vRjgAEA6Iv/F/4v/F/4RHADov/Q6MMAAgv/F/4v/F/4vJADov/F/4v/F5IwkFxQwjFxgA/AH4A/AH4AZA")) \ No newline at end of file +require("heatshrink").decompress(atob("mEwxH+AH4A/ADuIUCARRDhgePCKIv13YAEDoYJFAA4RJFyQvcGBYRGy4dDy4uLCJgv/DoOBDgOBF5oRLF6IeBDgIvNCJYvQDwQuNCJovRADov/F9OsAEgv/F/4v/F6OIAAgHRF/4v/F/4v/CI4AdF/6HR3YAEF/4v/F/4v/F5IAdF/4v/F/4vJGEguKGEYuMAH4A/AH4A/ADIA==")) \ No newline at end of file diff --git a/apps/rebbleagenda/metadata.json b/apps/rebbleagenda/metadata.json index e6f1d0a7e..07227d3bc 100644 --- a/apps/rebbleagenda/metadata.json +++ b/apps/rebbleagenda/metadata.json @@ -13,7 +13,6 @@ "tags": "agenda,tool", "supports" : ["BANGLEJS2"], "readme": "README.md", - "allow_emulator": true, "dependencies" : { "weather":"app" }, "storage": [ {"name":"rebbleagenda.app.js","url":"app.js"}, diff --git a/apps/rebbleagenda/settings.js b/apps/rebbleagenda/settings.js index 87ba11cff..8ed2ceae5 100644 --- a/apps/rebbleagenda/settings.js +++ b/apps/rebbleagenda/settings.js @@ -30,7 +30,7 @@ E.showMenu({ '': { 'title': 'Rebble Agenda' }, - ['< '+/*LANG*/'Back']: back, + /*LANG*/'< Back': back, /*LANG*/'Use system theme': { value: !!s.system, onchange: v => {